• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

j-schumann / symfony-addons / 15680683335

16 Jun 2025 12:22PM UTC coverage: 53.664%. First build
15680683335

push

github

j-schumann
Merge branch 'develop'

# Conflicts:
#	.php-cs-fixer.dist.php
#	composer.json
#	src/PHPUnit/ApiPlatformTestCase.php
#	src/PHPUnit/AuthenticatedClientTrait.php

103 of 382 new or added lines in 10 files covered. (26.96%)

476 of 887 relevant lines covered (53.66%)

3.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Rector/NamedArgumentsFromArrayRector.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Vrok\SymfonyAddons\Rector;
6

7
use PhpParser\Node;
8
use PhpParser\Node\Arg;
9
use PhpParser\Node\Expr\Array_;
10
use PhpParser\Node\Expr\ArrayItem;
11
use PhpParser\Node\Expr\FuncCall;
12
use PhpParser\Node\Expr\MethodCall;
13
use PhpParser\Node\Expr\StaticCall;
14
use PhpParser\Node\Identifier;
15
use PhpParser\Node\Scalar\String_;
16
use Rector\Contract\Rector\ConfigurableRectorInterface;
17
use Rector\Rector\AbstractRector;
18
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
19
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
20

21
final class NamedArgumentsFromArrayRector extends AbstractRector implements ConfigurableRectorInterface
22
{
23
    /**
24
     * @var array<string|array{string, string}>
25
     */
26
    private array $targets = [];
27

28
    public function getRuleDefinition(): RuleDefinition
29
    {
NEW
30
        return new RuleDefinition(
×
NEW
31
            'Convert associative array arguments to named arguments for specified functions/methods',
×
NEW
32
            [
×
NEW
33
                new ConfiguredCodeSample(
×
NEW
34
                    <<<'CODE_SAMPLE'
×
35
foo([
36
    'a' => $a,
37
    'b' => $b,
38
]);
39

40
MyClass::staticMethod([
41
    'x' => $x,
42
    'y' => $y,
43
]);
44

45
$obj->instanceMethod([
46
    'p' => $p,
47
    'q' => $q,
48
]);
NEW
49
CODE_SAMPLE
×
NEW
50
                    ,
×
NEW
51
                    <<<'CODE_SAMPLE'
×
52
foo(a: $a, b: $b);
53

54
MyClass::staticMethod(x: $x, y: $y);
55

56
$obj->instanceMethod(p: $p, q: $q);
NEW
57
CODE_SAMPLE
×
NEW
58
                    ,
×
NEW
59
                    [
×
NEW
60
                        'targets' => [
×
NEW
61
                            'foo',  // function call
×
NEW
62
                            ['MyClass', 'staticMethod'],  // static method call
×
NEW
63
                            ['MyClass', 'instanceMethod'],  // instance method call
×
NEW
64
                        ],
×
NEW
65
                    ]
×
NEW
66
                ),
×
NEW
67
            ]
×
NEW
68
        );
×
69
    }
70

71
    /**
72
     * @return array<class-string<Node>>
73
     */
74
    public function getNodeTypes(): array
75
    {
NEW
76
        return [FuncCall::class, MethodCall::class, StaticCall::class];
×
77
    }
78

79
    public function refactor(Node $node): ?Node
80
    {
NEW
81
        if (!$this->shouldProcessNode($node)) {
×
NEW
82
            return null;
×
83
        }
84

85
        // Check if there's exactly one argument and it's an array
NEW
86
        if (1 !== count($node->args)) {
×
NEW
87
            return null;
×
88
        }
89

NEW
90
        $firstArg = $node->args[0];
×
NEW
91
        if (!$firstArg instanceof Arg || !$firstArg->value instanceof Array_) {
×
NEW
92
            return null;
×
93
        }
94

NEW
95
        $array = $firstArg->value;
×
96

97
        // Check if all array items have string keys (associative array)
NEW
98
        if (!$this->isAssociativeArray($array)) {
×
NEW
99
            return null;
×
100
        }
101

102
        // Convert array items to named arguments
NEW
103
        $namedArgs = $this->convertArrayItemsToNamedArgs($array);
×
104

NEW
105
        if ([] === $namedArgs) {
×
NEW
106
            return null;
×
107
        }
108

NEW
109
        $node->args = $namedArgs;
×
110

NEW
111
        return $node;
×
112
    }
113

114
    /**
115
     * @param mixed[] $configuration
116
     */
117
    public function configure(array $configuration): void
118
    {
NEW
119
        $this->targets = $configuration['targets'] ?? [];
×
120
    }
121

122
    private function shouldProcessNode(Node $node): bool
123
    {
NEW
124
        if ($node instanceof FuncCall) {
×
NEW
125
            return $this->isFunctionCallTarget($node);
×
126
        }
127

NEW
128
        if ($node instanceof MethodCall) {
×
NEW
129
            return $this->isMethodCallTarget($node);
×
130
        }
131

NEW
132
        if ($node instanceof StaticCall) {
×
NEW
133
            return $this->isStaticCallTarget($node);
×
134
        }
135

NEW
136
        return false;
×
137
    }
138

139
    private function isFunctionCallTarget(FuncCall $funcCall): bool
140
    {
NEW
141
        if (!$funcCall->name instanceof Identifier) {
×
NEW
142
            return false;
×
143
        }
144

NEW
145
        $functionName = $funcCall->name->toString();
×
146

NEW
147
        foreach ($this->targets as $target) {
×
148
            // String targets are function calls
NEW
149
            if (is_string($target) && $target === $functionName) {
×
NEW
150
                return true;
×
151
            }
152
        }
153

NEW
154
        return false;
×
155
    }
156

157
    private function isMethodCallTarget(MethodCall $methodCall): bool
158
    {
NEW
159
        if (!$methodCall->name instanceof Identifier) {
×
NEW
160
            return false;
×
161
        }
162

NEW
163
        $methodName = $methodCall->name->toString();
×
164

NEW
165
        foreach ($this->targets as $target) {
×
NEW
166
            if (is_array($target) && 2 === count($target)) {
×
NEW
167
                [, $targetMethod] = $target;
×
168

NEW
169
                if ($targetMethod === $methodName) {
×
NEW
170
                    return true;
×
171
                }
172
            }
173
        }
174

NEW
175
        return false;
×
176
    }
177

178
    private function isStaticCallTarget(StaticCall $staticCall): bool
179
    {
NEW
180
        if (!$staticCall->name instanceof Identifier) {
×
NEW
181
            return false;
×
182
        }
183

NEW
184
        $methodName = $staticCall->name->toString();
×
185

NEW
186
        $className = null;
×
NEW
187
        if ($staticCall->class instanceof Identifier) {
×
NEW
188
            $className = $staticCall->class->toString();
×
189
        }
190

NEW
191
        foreach ($this->targets as $target) {
×
NEW
192
            if (is_array($target) && 2 === count($target)) {
×
NEW
193
                [$targetClass, $targetMethod] = $target;
×
194

NEW
195
                if ($targetClass === $className && $targetMethod === $methodName) {
×
NEW
196
                    return true;
×
197
                }
198
            }
199
        }
200

NEW
201
        return false;
×
202
    }
203

204
    private function isAssociativeArray(Array_ $array): bool
205
    {
NEW
206
        foreach ($array->items as $item) {
×
NEW
207
            if (!$item instanceof ArrayItem) {
×
NEW
208
                continue;
×
209
            }
210

NEW
211
            if (null === $item->key) {
×
NEW
212
                return false;
×
213
            }
214

NEW
215
            if (!$item->key instanceof String_) {
×
NEW
216
                return false;
×
217
            }
218
        }
219

NEW
220
        return true;
×
221
    }
222

223
    /**
224
     * @return Arg[]
225
     */
226
    private function convertArrayItemsToNamedArgs(Array_ $array): array
227
    {
NEW
228
        $namedArgs = [];
×
229

NEW
230
        foreach ($array->items as $item) {
×
NEW
231
            if (!$item instanceof ArrayItem || !$item->key instanceof String_) {
×
NEW
232
                continue;
×
233
            }
234

NEW
235
            $paramName = $item->key->value;
×
NEW
236
            $namedArgs[] = new Arg(
×
NEW
237
                $item->value,
×
NEW
238
                false, // byRef
×
NEW
239
                false, // unpack
×
NEW
240
                [],    // attributes
×
NEW
241
                new Identifier($paramName) // name
×
NEW
242
            );
×
243
        }
244

NEW
245
        return $namedArgs;
×
246
    }
247
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc