Repository navigation
Expand file tree
/
Copy pathArrayMergeFromArraySpreadFactory.php
More file actions
129 lines (108 loc) · 4.17 KB
/
Copy pathArrayMergeFromArraySpreadFactory.php
File metadata and controls
129 lines (108 loc) · 4.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php
declare(strict_types=1);
namespace Rector\DowngradePhp81\NodeFactory;
use PhpParser\Node\Arg;
use PhpParser\Node\ArrayItem;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Name;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Type\IterableType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
final readonly class ArrayMergeFromArraySpreadFactory
{
public function createFromArray(Array_ $array, MutatingScope $mutatingScope): FuncCall
{
$newArrayItems = $this->dissolveArrayItems($array);
return $this->createArrayMergeFuncCall($newArrayItems, $mutatingScope);
}
/**
* Iterate all array items:
*
* 1. If they use the spread, remove it
* 2. If not, make the item part of an accumulating array,
* to be added once the next spread is found, or at the end
* @return ArrayItem[]
*/
private function dissolveArrayItems(Array_ $array): array
{
$newItems = [];
$accumulatedItems = [];
foreach ($array->items as $item) {
if ($item instanceof ArrayItem && $item->unpack) {
if ($accumulatedItems !== []) {
// If previous items were in the new array, add them first
$newItems[] = $this->createArrayItemFromArray($accumulatedItems);
// Reset the accumulated items
$accumulatedItems = [];
}
// Add the current item, still with "unpack = true" (it will be removed later on)
$newItems[] = $item;
continue;
}
// Normal item, it goes into the accumulated array
$accumulatedItems[] = $item;
}
// Add the remaining accumulated items
if ($accumulatedItems !== []) {
$newItems[] = $this->createArrayItemFromArray($accumulatedItems);
}
return $newItems;
}
/**
* @param ArrayItem[] $arrayItems
*/
private function createArrayMergeFuncCall(array $arrayItems, MutatingScope $mutatingScope): FuncCall
{
$args = array_map(function (ArrayItem $arrayItem) use ($mutatingScope): Arg {
if ($arrayItem->unpack) {
// Do not unpack anymore
$arrayItem->unpack = false;
return $this->createArgFromSpreadArrayItem($mutatingScope, $arrayItem);
}
return new Arg($arrayItem->value);
}, $arrayItems);
return new FuncCall(new Name('array_merge'), $args);
}
/**
* @param array<ArrayItem> $items
*/
private function createArrayItemFromArray(array $items): ArrayItem
{
$array = new Array_($items);
return new ArrayItem($array);
}
private function createArgFromSpreadArrayItem(MutatingScope $mutatingScope, ArrayItem $arrayItem): Arg
{
$type = $mutatingScope->getType($arrayItem->value);
$iteratorToArrayFuncCall = new FuncCall(new Name('iterator_to_array'), [new Arg($arrayItem->value)]);
// If we know it is an array, then print it directly
// Otherwise PHPStan throws an error:
// "Else branch is unreachable because ternary operator condition is always true."
if ($type->isArray()->yes()) {
return new Arg($arrayItem->value);
}
// If it is iterable, then directly return `iterator_to_array`
if ($this->isIterableType($type)) {
return new Arg($iteratorToArrayFuncCall);
}
// Print a ternary, handling either an array or an iterator
$inArrayFuncCall = new FuncCall(new Name('is_array'), [new Arg($arrayItem->value)]);
return new Arg(new Ternary($inArrayFuncCall, $arrayItem->value, $iteratorToArrayFuncCall));
}
/**
* Iterables: objects declaring the interface Traversable,
* For "iterable" type, it can be array
*/
private function isIterableType(Type $type): bool
{
if ($type instanceof IterableType) {
return false;
}
$traversableObjectType = new ObjectType('Traversable');
return $traversableObjectType->isSuperTypeOf($type)
->yes();
}
}