Skip to content

Commit 6e32021

Browse files
authored
IBX-10278: Allowed any object to be used with Ibexa Migrations steps (#42)
1 parent 7d12516 commit 6e32021

17 files changed

+261
-138
lines changed

src/contracts/Sets/ibexa-50.php

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
use Ibexa\Rector\Rule\AddReturnTypeFromParentMethodRule;
1212
use Ibexa\Rector\Rule\AddReturnTypeFromPhpDocRule;
13-
use Ibexa\Rector\Rule\ChangeLimitationTypeValueObjectToObjectRector;
13+
use Ibexa\Rector\Rule\ChangeArgumentTypeRector;
14+
use Ibexa\Rector\Rule\Configuration\ChangeArgumentTypeConfiguration;
1415
use Ibexa\Rector\Rule\Configuration\MethodReturnTypeConfiguration;
1516
use Ibexa\Rector\Rule\ConstToEnumValueRector;
1617
use Ibexa\Rector\Rule\PropertyToGetterRector;
@@ -268,7 +269,40 @@
268269
]
269270
);
270271

271-
$rectorConfig->rule(
272-
ChangeLimitationTypeValueObjectToObjectRector::class,
272+
$rectorConfig->ruleWithConfiguration(
273+
ChangeArgumentTypeRector::class,
274+
[
275+
new ChangeArgumentTypeConfiguration(
276+
'Ibexa\\Migration\\Generator\\StepBuilder\\StepFactoryInterface',
277+
'create',
278+
0,
279+
'Ibexa\\Contracts\\Core\\Repository\\Values\\ValueObject',
280+
),
281+
new ChangeArgumentTypeConfiguration(
282+
'Ibexa\\Migration\\Generator\\StepBuilder\\AbstractStepFactory',
283+
'prepareLogMessage',
284+
0,
285+
'Ibexa\\Contracts\\Core\\Repository\\Values\\ValueObject',
286+
),
287+
new ChangeArgumentTypeConfiguration(
288+
'Ibexa\\Migration\\StepExecutor\\ReferenceDefinition\\ResolverInterface',
289+
'resolve',
290+
1,
291+
'Ibexa\\Contracts\\Core\\Repository\\Values\\ValueObject'
292+
),
293+
new ChangeArgumentTypeConfiguration(
294+
'Ibexa\\Migration\\Generator\\StepBuilder\\StepBuilderInterface',
295+
'build',
296+
0,
297+
'Ibexa\\Contracts\\Core\\Repository\\Values\\ValueObject'
298+
),
299+
300+
new ChangeArgumentTypeConfiguration(
301+
'Ibexa\\Contracts\\Core\\Limitation\\Type',
302+
'evaluate',
303+
2,
304+
'Ibexa\\Contracts\\Core\\Repository\\Values\\ValueObject'
305+
),
306+
]
273307
);
274308
};
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Rector\Rule;
10+
11+
use Ibexa\Rector\Rule\Configuration\ChangeArgumentTypeConfiguration;
12+
use PhpParser\Node;
13+
use PhpParser\Node\Expr\Variable;
14+
use PhpParser\Node\Param;
15+
use PhpParser\Node\Stmt\ClassMethod;
16+
use PHPStan\Type\ObjectType;
17+
use PHPStan\Type\ObjectWithoutClassType;
18+
use PHPStan\Type\Type;
19+
use Rector\Contract\Rector\ConfigurableRectorInterface;
20+
use Rector\Exception\ShouldNotHappenException;
21+
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
22+
use Rector\Rector\AbstractRector;
23+
use Rector\StaticTypeMapper\StaticTypeMapper;
24+
25+
/**
26+
* @see \Ibexa\Rector\Tests\Rule\ChangeArgumentTypeRector\ChangeArgumentTypeRectorTest
27+
*/
28+
final class ChangeArgumentTypeRector extends AbstractRector implements ConfigurableRectorInterface
29+
{
30+
/** @var array<\Ibexa\Rector\Rule\Configuration\ChangeArgumentTypeConfiguration> */
31+
private array $configurations = [];
32+
33+
public function __construct(
34+
private readonly StaticTypeMapper $staticTypeMapper
35+
) {
36+
}
37+
38+
/**
39+
* @return array<class-string<\PhpParser\Node>>
40+
*/
41+
public function getNodeTypes(): array
42+
{
43+
return [
44+
Node\Stmt\Class_::class,
45+
Node\Stmt\Interface_::class,
46+
];
47+
}
48+
49+
/**
50+
* @param \PhpParser\Node\Stmt\Class_|\PhpParser\Node\Stmt\Interface_ $node
51+
*
52+
* @return array<\PhpParser\Node>|null
53+
*/
54+
public function refactor(Node $node): array|null
55+
{
56+
$nodes = [];
57+
foreach ($this->findConfigurationForClass($node) as $configuration) {
58+
$nextNode = $this->doRefactor($node, $configuration);
59+
60+
if ($nextNode !== null) {
61+
$nodes[] = $nextNode;
62+
}
63+
}
64+
65+
return $nodes ?: null;
66+
}
67+
68+
/**
69+
* @param \PhpParser\Node\Stmt\Class_|\PhpParser\Node\Stmt\Interface_ $node
70+
*/
71+
private function doRefactor(Node $node, ChangeArgumentTypeConfiguration $configuration): ?Node
72+
{
73+
$evaluateMethod = $node->getMethod($configuration->getMethod());
74+
if ($evaluateMethod === null) {
75+
return null;
76+
}
77+
78+
if (!isset($evaluateMethod->params[$configuration->getArgumentPosition()])) {
79+
return null;
80+
}
81+
82+
$param = $evaluateMethod->params[$configuration->getArgumentPosition()];
83+
if (!$this->isObjectType($param, new ObjectType($configuration->getArgumentClassName()))) {
84+
return null;
85+
}
86+
87+
if (!isset($param->var->name)) {
88+
throw new ShouldNotHappenException(sprintf(
89+
'Method %s::%s argument should have a name',
90+
$configuration->getInterface(),
91+
$configuration->getMethod(),
92+
));
93+
}
94+
95+
$newType = $configuration->getNewArgumentClass();
96+
$newType = $newType === null ? new ObjectWithoutClassType() : new ObjectType($newType);
97+
98+
$this->addClassMethodParam(
99+
$configuration,
100+
$evaluateMethod,
101+
$newType,
102+
$param->var->name,
103+
);
104+
105+
return $node;
106+
}
107+
108+
/**
109+
* @return iterable<\Ibexa\Rector\Rule\Configuration\ChangeArgumentTypeConfiguration>
110+
*
111+
* @throws \PHPStan\ShouldNotHappenException
112+
*/
113+
private function findConfigurationForClass(
114+
Node\Stmt\Class_|Node\Stmt\Interface_ $class
115+
): iterable {
116+
foreach ($this->configurations as $configuration) {
117+
$interface = $configuration->getInterface();
118+
$objectType = new ObjectType($interface);
119+
120+
$classType = $this->getType($class);
121+
122+
if ($objectType->isSuperTypeOf($classType)->yes()) {
123+
yield $configuration;
124+
}
125+
}
126+
}
127+
128+
private function addClassMethodParam(
129+
Configuration\ChangeArgumentTypeConfiguration $configuration,
130+
ClassMethod $classMethod,
131+
Type $type,
132+
string $paramName
133+
): void {
134+
$param = new Param(new Variable($paramName));
135+
$param->type = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PARAM);
136+
137+
$argumentPosition = $configuration->getArgumentPosition();
138+
$classMethod->params[$argumentPosition] = $param;
139+
}
140+
141+
public function configure(array $configuration): void
142+
{
143+
foreach ($configuration as $c) {
144+
$this->configurations[] = $c;
145+
}
146+
}
147+
}

src/lib/Rule/ChangeLimitationTypeValueObjectToObjectRector.php

Lines changed: 0 additions & 112 deletions
This file was deleted.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Rector\Rule\Configuration;
10+
11+
final readonly class ChangeArgumentTypeConfiguration
12+
{
13+
public function __construct(
14+
private string $interface,
15+
private string $method,
16+
private int $argumentPosition,
17+
private string $argumentClassName,
18+
private ?string $newArgumentClass = null,
19+
) {
20+
}
21+
22+
public function getInterface(): string
23+
{
24+
return $this->interface;
25+
}
26+
27+
public function getMethod(): string
28+
{
29+
return $this->method;
30+
}
31+
32+
public function getArgumentPosition(): int
33+
{
34+
return $this->argumentPosition;
35+
}
36+
37+
public function getArgumentClassName(): string
38+
{
39+
return $this->argumentClassName;
40+
}
41+
42+
public function getNewArgumentClass(): ?string
43+
{
44+
return $this->newArgumentClass;
45+
}
46+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
*/
77
declare(strict_types=1);
88

9-
namespace Ibexa\Rector\Tests\Rule\ChangeLimitationTypeValueObjectToObjectRector;
9+
namespace Ibexa\Rector\Tests\Rule\ChangeArgumentTypeRector;
1010

1111
use PHPUnit\Framework\Attributes\DataProvider;
1212
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
1313

14-
final class ChangeLimitationTypeValueObjectToObjectRectorTest extends AbstractRectorTestCase
14+
final class ChangeArgumentTypeRectorTest extends AbstractRectorTestCase
1515
{
1616
#[DataProvider('provideData')]
1717
public function test(string $filePath): void
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

33
use Ibexa\Contracts\Core\Repository\Values\ValueObject;
4-
use Ibexa\Rector\Tests\Rule\ChangeLimitationTypeValueObjectToObjectRector\Source\AbstractType;
4+
use Ibexa\Rector\Tests\Rule\ChangeArgumentTypeRector\Source\AbstractType;
55

66
class Foo extends AbstractType
77
{
@@ -20,7 +20,7 @@ class Foo extends AbstractType
2020
<?php
2121

2222
use Ibexa\Contracts\Core\Repository\Values\ValueObject;
23-
use Ibexa\Rector\Tests\Rule\ChangeLimitationTypeValueObjectToObjectRector\Source\AbstractType;
23+
use Ibexa\Rector\Tests\Rule\ChangeArgumentTypeRector\Source\AbstractType;
2424

2525
class Foo extends AbstractType
2626
{

0 commit comments

Comments
 (0)