Skip to content

Commit 96b2418

Browse files
authored
Merge pull request #69 from jtojnar/fixes
Fix crashes with Nette 3.2
2 parents a03a912 + 1e3bf35 commit 96b2418

File tree

5 files changed

+117
-55
lines changed

5 files changed

+117
-55
lines changed

composer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@
3434
"php-parallel-lint/php-console-highlighter": "^1.0",
3535
"php-parallel-lint/php-parallel-lint": "^1.4",
3636
"phpstan/phpstan": "^2.1",
37+
"phpstan/phpstan-deprecation-rules": "^2.0",
38+
"phpstan/phpstan-nette": "^2.0",
3739
"rector/rector": "^2.0",
3840
"symplify/easy-coding-standard": "^12.5",
3941
"nette/neon": "^3.4.4"
4042
},
43+
"conflict": {
44+
"nette/component-model": "<3.1.0"
45+
},
4146
"autoload": {
4247
"psr-4": {
4348
"Kdyby\\Replicator\\": "src/Replicator/"

phpstan-baseline.neon

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
parameters:
2+
ignoreErrors:
3+
# https://github.com/phpstan/phpstan-nette/issues/141
4+
-
5+
message: '#^Parameter \#1 \$array of function array_filter expects array, Iterator\<int\|string, Nette\\ComponentModel\\IComponent\> given\.$#'
6+
identifier: argument.type
7+
count: 3
8+
path: src/Replicator/Container.php
9+
10+
-
11+
message: '#^Parameter \#1 \$array of function array_filter expects array, Iterator\<int\|string, Nette\\ComponentModel\\IComponent\>\|list\<Nette\\ComponentModel\\IComponent\> given\.$#'
12+
identifier: argument.type
13+
count: 2
14+
path: src/Replicator/Container.php

phpstan.dist.neon

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# https://phpstan.org/config-reference
22

3+
includes:
4+
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
5+
- vendor/phpstan/phpstan-nette/extension.neon
6+
- vendor/phpstan/phpstan-nette/rules.neon
7+
- phpstan-baseline.neon
8+
39
parameters:
4-
level: 1
10+
level: max
511
paths:
612
- src/
13+
treatPhpDocTypesAsCertain: false

src/Replicator/Container.php

Lines changed: 89 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
use Closure;
1414
use Nette;
1515
use ReflectionClass;
16-
use SplObjectStorage;
17-
use Traversable;
16+
use WeakMap;
1817

1918
/**
2019
* @author Filip Procházka <filip@prochazka.su>
@@ -38,7 +37,7 @@ class Container extends Nette\Forms\Container
3837
public $createDefault;
3938

4039
/**
41-
* @var string
40+
* @var class-string<Nette\Forms\Container>
4241
*/
4342
public $containerClass = Nette\Forms\Container::class;
4443

@@ -53,12 +52,12 @@ class Container extends Nette\Forms\Container
5352
private $submittedBy = FALSE;
5453

5554
/**
56-
* @var array
55+
* @var array<string, Nette\Forms\Container>
5756
*/
5857
private $created = [];
5958

6059
/**
61-
* @var array
60+
* @var ?array<string, array<string, mixed>>
6261
*/
6362
private $httpPost;
6463

@@ -110,19 +109,25 @@ protected function attached(Nette\ComponentModel\IComponent $obj): void
110109
}
111110

112111
/**
113-
* @return iterable<Nette\Forms\Container>
112+
* @return array<Nette\Forms\Container>
114113
*/
115-
public function getContainers(bool $recursive = FALSE): iterable
114+
public function getContainers(bool $recursive = FALSE): array
116115
{
117-
return $this->getComponents($recursive, \Nette\Forms\Container::class);
116+
return array_filter(
117+
$recursive ? $this->getComponentTree() : $this->getComponents(),
118+
fn ($component): bool => $component instanceof \Nette\Forms\Container,
119+
);
118120
}
119121

120122
/**
121-
* @return iterable<Nette\Forms\ISubmitterControl>
123+
* @return array<Nette\Forms\Controls\SubmitButton>
122124
*/
123-
public function getButtons(bool $recursive = FALSE): iterable
125+
public function getButtons(bool $recursive = FALSE): array
124126
{
125-
return $this->getComponents($recursive, Nette\Forms\ISubmitterControl::class);
127+
return array_filter(
128+
$recursive ? $this->getComponentTree() : $this->getComponents(),
129+
fn ($component): bool => $component instanceof Nette\Forms\Controls\SubmitButton,
130+
);
126131
}
127132

128133
/**
@@ -143,10 +148,13 @@ protected function createComponent(string $name): ?Nette\ComponentModel\ICompone
143148

144149
private function getFirstControlName(): ?string
145150
{
146-
$controls = iterator_to_array($this->getComponents(FALSE, Nette\Forms\IControl::class));
151+
$controls = array_filter(
152+
$this->getComponents(),
153+
fn ($component): bool => $component instanceof Nette\Forms\Control,
154+
);
147155
$firstControl = reset($controls);
148156

149-
return $firstControl ? $firstControl->name : NULL;
157+
return $firstControl ? $firstControl->getName() : NULL;
150158
}
151159

152160
protected function createContainer(): Nette\Forms\Container
@@ -179,24 +187,23 @@ public function createOne(?string $name = NULL): Nette\Forms\Container
179187
if ($name === NULL) {
180188
$names = array_keys(iterator_to_array($this->getContainers()));
181189
$name = $names ? max($names) + 1 : 0;
190+
$name = (string) $name;
182191
}
183192

184193
// Container is overriden, therefore every request for getComponent($name, FALSE) would return container
185194
if (isset($this->created[$name])) {
186195
throw new Nette\InvalidArgumentException("Container with name '{$name}' already exists.");
187196
}
188197

189-
return $this[$name];
198+
return $this->getComponent($name);
190199
}
191200

192201
/**
193-
* @param array|Traversable $values
194-
*
195-
* @return Nette\Forms\Container|Container
202+
* @param iterable<string, iterable<string, mixed>> $values
196203
*/
197204
public function setValues(array|object $values, bool $erase = FALSE, bool $onlyDisabled = FALSE): static
198205
{
199-
if (!$this->form->isAnchored() || !$this->form->isSubmitted()) {
206+
if (!$this->form?->isAnchored() || !$this->form->isSubmitted()) {
200207
foreach ($values as $name => $value) {
201208
if ((is_iterable($value)) && !$this->getComponent($name, FALSE)) {
202209
$this->createOne($name);
@@ -234,7 +241,7 @@ protected function createDefault(): void
234241

235242
if (!$this->getForm()->isSubmitted()) {
236243
foreach (range(0, $this->createDefault - 1) as $key) {
237-
$this->createOne($key);
244+
$this->createOne((string) $key);
238245
}
239246

240247
} elseif ($this->forceDefault) {
@@ -245,13 +252,18 @@ protected function createDefault(): void
245252
}
246253

247254
/**
248-
* @return mixed|null
255+
* @return ?array<string, array<string, mixed>>
249256
*/
250-
private function getHttpData()
257+
private function getHttpData(): ?array
251258
{
252259
if ($this->httpPost === NULL) {
253-
$path = explode(self::NAME_SEPARATOR, $this->lookupPath(Nette\Forms\Form::class));
254-
$this->httpPost = Nette\Utils\Arrays::get($this->getForm()->getHttpData(), $path, NULL);
260+
$path = explode(self::NameSeparator, $this->lookupPath(Nette\Forms\Form::class));
261+
/** @var array<string, mixed> */ // See https://github.com/nette/forms/pull/333
262+
$httpData = $this->getForm()
263+
->getHttpData();
264+
/** @var ?array<string, array<string, mixed>> */
265+
$httpPost = Nette\Utils\Arrays::get($httpData, $path, NULL);
266+
$this->httpPost = $httpPost;
255267
}
256268

257269
return $this->httpPost;
@@ -267,7 +279,11 @@ public function remove(Nette\ComponentModel\Container $container, bool $cleanUpG
267279
}
268280

269281
// to check if form was submitted by this one
270-
foreach ($container->getComponents(TRUE, Nette\Forms\ISubmitterControl::class) as $button) {
282+
$buttons = array_filter(
283+
$container->getComponentTree(),
284+
fn ($component): bool => $component instanceof Nette\Forms\SubmitterControl,
285+
);
286+
foreach ($buttons as $button) {
271287
/** @var Nette\Forms\Controls\SubmitButton $button */
272288
if ($button->isSubmittedBy()) {
273289
$this->submittedBy = TRUE;
@@ -276,7 +292,7 @@ public function remove(Nette\ComponentModel\Container $container, bool $cleanUpG
276292
}
277293

278294
/** @var Nette\Forms\Controls\BaseControl[] $components */
279-
$components = $container->getComponents(TRUE);
295+
$components = $container->getComponentTree();
280296
$this->removeComponent($container);
281297

282298
// reflection is required to hack form groups
@@ -287,12 +303,12 @@ public function remove(Nette\ComponentModel\Container $container, bool $cleanUpG
287303
// walk groups and clean then from removed components
288304
$affected = [];
289305
foreach ($this->getForm()->getGroups() as $group) {
290-
/** @var SplObjectStorage $groupControls */
306+
/** @var WeakMap<Nette\Forms\Control, null> $groupControls */
291307
$groupControls = $controlsProperty->getValue($group);
292308

293309
foreach ($components as $control) {
294-
if ($groupControls->contains($control)) {
295-
$groupControls->detach($control);
310+
if ($groupControls->offsetExists($control)) {
311+
unset($groupControls[$control]);
296312

297313
if (!in_array($group, $affected, TRUE)) {
298314
$affected[] = $group;
@@ -303,7 +319,12 @@ public function remove(Nette\ComponentModel\Container $container, bool $cleanUpG
303319

304320
// remove affected & empty groups
305321
if ($cleanUpGroups && $affected) {
306-
foreach ($this->getForm()->getComponents(FALSE, Nette\Forms\Container::class) as $cont) {
322+
$containers = array_filter(
323+
$this->getForm()
324+
->getComponents(),
325+
fn ($component): bool => $component instanceof Nette\Forms\Container,
326+
);
327+
foreach ($containers as $cont) {
307328
if ($index = array_search($cont->currentGroup, $affected, TRUE)) {
308329
unset($affected[$index]);
309330
}
@@ -321,6 +342,9 @@ public function remove(Nette\ComponentModel\Container $container, bool $cleanUpG
321342

322343
/**
323344
* Counts filled values, filtered by given names
345+
*
346+
* @param array<string> $components
347+
* @param array<string> $subComponents
324348
*/
325349
public function countFilledWithout(array $components = [], array $subComponents = []): int
326350
{
@@ -333,31 +357,48 @@ public function countFilledWithout(array $components = [], array $subComponents
333357
$rows = [];
334358
$subComponents = array_flip($subComponents);
335359
foreach ($httpData as $item) {
336-
$filter = function ($value) use (&$filter) {
360+
$filter = function ($value) use (&$filter): bool {
337361
if (is_array($value)) {
338362
return count(array_filter($value, $filter)) > 0;
339363
}
340364

341-
return strlen($value);
365+
if (is_string($value)) {
366+
return strlen($value) > 0;
367+
}
368+
369+
return true;
342370
};
343371
$rows[] = array_filter(array_diff_key($item, $subComponents), $filter) ?: FALSE;
344372
}
345373

346374
return count(array_filter($rows));
347375
}
348376

377+
/**
378+
* @param array<string> $exceptChildren
379+
*/
349380
public function isAllFilled(array $exceptChildren = []): bool
350381
{
351382
$components = [];
352-
foreach ($this->getComponents(FALSE, Nette\Forms\IControl::class) as $control) {
353-
/** @var Nette\Forms\Controls\BaseControl $control */
354-
$components[] = $control->getName();
383+
$controls = array_filter(
384+
$this->getComponents(),
385+
fn ($component): bool => $component instanceof Nette\Forms\Control,
386+
);
387+
foreach ($controls as $control) {
388+
if (($name = $control->getName()) !== null) {
389+
$components[] = $name;
390+
}
355391
}
356392

357393
foreach ($this->getContainers() as $container) {
358-
foreach ($container->getComponents(TRUE, Nette\Forms\ISubmitterControl::class) as $button) {
359-
/** @var Nette\Forms\Controls\SubmitButton $button */
360-
$exceptChildren[] = $button->getName();
394+
$buttons = array_filter(
395+
$container->getComponentTree(),
396+
fn ($component): bool => $component instanceof Nette\Forms\SubmitterControl,
397+
);
398+
foreach ($buttons as $button) {
399+
if (($name = $button->getName()) !== null) {
400+
$exceptChildren[] = $name;
401+
}
361402
}
362403
}
363404

@@ -366,9 +407,9 @@ public function isAllFilled(array $exceptChildren = []): bool
366407
return $filled === iterator_count($this->getContainers());
367408
}
368409

369-
public function addContainer($name): Nette\Forms\Container
410+
public function addContainer(string|int $name): Nette\Forms\Container
370411
{
371-
return $this[$name] = new Nette\Forms\Container();
412+
return $this[(string) $name] = new Nette\Forms\Container();
372413
}
373414

374415
public function addComponent(Nette\ComponentModel\IComponent $component, ?string $name, ?string $insertBefore = NULL): static
@@ -381,14 +422,11 @@ public function addComponent(Nette\ComponentModel\IComponent $component, ?string
381422
return $this;
382423
}
383424

384-
/**
385-
* @var bool
386-
*/
387-
private static $registered = FALSE;
425+
private static ?string $registered = null;
388426

389427
public static function register(string $methodName = 'addDynamic'): void
390428
{
391-
if (self::$registered) {
429+
if (self::$registered !== null) {
392430
Nette\Forms\Container::extensionMethod(self::$registered, function () {
393431
throw new Nette\MemberAccessException();
394432
});
@@ -404,7 +442,7 @@ function (Nette\Forms\Container $_this, string $name, callable $factory, int $cr
404442
}
405443
);
406444

407-
if (self::$registered) {
445+
if (self::$registered !== null) {
408446
return;
409447
}
410448

@@ -415,13 +453,15 @@ function (Nette\Forms\Controls\SubmitButton $_this, ?callable $callback = NULL)
415453
$_this->onClick[] = function (Nette\Forms\Controls\SubmitButton $button) use ($callback) {
416454
/** @var self $replicator */
417455
$replicator = $button->lookup(static::class);
456+
$container = $button->parent;
457+
\assert($container instanceof Nette\ComponentModel\Container);
418458
if (is_callable($callback)) {
419-
$callback($replicator, $button->parent);
459+
$callback($replicator, $container);
420460
}
421461
if ($form = $button->getForm(FALSE)) {
422462
$form->onSuccess = [];
423463
}
424-
$replicator->remove($button->parent);
464+
$replicator->remove($container);
425465
};
426466

427467
return $_this;
@@ -434,13 +474,9 @@ function (Nette\Forms\Controls\SubmitButton $_this, bool $allowEmpty = FALSE, ?c
434474
$_this->onClick[] = function (Nette\Forms\Controls\SubmitButton $button) use ($allowEmpty, $callback) {
435475
/** @var self $replicator */
436476
$replicator = $button->lookup(static::class);
437-
if (!is_bool($allowEmpty)) {
438-
$callback = Closure::fromCallable($allowEmpty);
439-
$allowEmpty = FALSE;
440-
}
441-
if ($allowEmpty === TRUE || $replicator->isAllFilled() === TRUE) {
477+
if ($allowEmpty || $replicator->isAllFilled() === TRUE) {
442478
$newContainer = $replicator->createOne();
443-
if (is_callable($callback)) {
479+
if ($callback !== NULL) {
444480
$callback($replicator, $newContainer);
445481
}
446482
}

src/Replicator/DI/ReplicatorExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void
2626
$init->addBody(Container::class . '::register();');
2727
}
2828

29-
public static function register(Nette\Configurator $configurator): void
29+
public static function register(Nette\Bootstrap\Configurator $configurator): void
3030
{
3131
$configurator->onCompile[] = function ($config, Nette\DI\Compiler $compiler) {
3232
$compiler->addExtension('formsReplicator', new ReplicatorExtension());

0 commit comments

Comments
 (0)