Skip to content

Commit 99d7bfd

Browse files
authored
Merge pull request #2 from sidz/teach-to-see-a-different-between-model-and-components
Teach see a difference between Component and Model especially when bo…
2 parents 2fe3af0 + 30f04ce commit 99d7bfd

10 files changed

+166
-10
lines changed

src/ClassComponentsExtension.php

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,103 @@
44

55
namespace PHPStanCakePHP2;
66

7-
final class ClassComponentsExtension extends ClassPropertiesExtension
7+
use PhpParser\Node\Expr\Array_;
8+
use PhpParser\Node\Expr\ClassConstFetch;
9+
use PhpParser\Node\Name\FullyQualified;
10+
use PhpParser\Node\Scalar\String_;
11+
use PHPStan\Reflection\ClassReflection;
12+
use PHPStan\Reflection\PropertiesClassReflectionExtension;
13+
use PHPStan\Reflection\PropertyReflection;
14+
use PHPStan\Reflection\ReflectionProvider;
15+
16+
final class ClassComponentsExtension implements PropertiesClassReflectionExtension
817
{
9-
protected function getPropertyParentClassName(): string
18+
private ReflectionProvider $reflectionProvider;
19+
20+
public function __construct(ReflectionProvider $reflectionProvider)
1021
{
11-
return 'Component';
22+
$this->reflectionProvider = $reflectionProvider;
23+
}
24+
25+
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
26+
{
27+
if (!array_filter($this->getContainingClassNames(), [$classReflection, 'is'])) {
28+
return false;
29+
}
30+
31+
$isDefinedInComponentsProperty = (bool) array_filter(
32+
$this->getDefinedComponentsAsList($classReflection),
33+
static fn (string $componentName): bool => $componentName === $propertyName
34+
);
35+
36+
if (!$isDefinedInComponentsProperty) {
37+
return false;
38+
}
39+
40+
$propertyClassName = $this->getClassNameFromPropertyName($propertyName);
41+
42+
return $this->reflectionProvider->hasClass($propertyClassName)
43+
&& $this->reflectionProvider->getClass($propertyClassName)
44+
->is('Component');
45+
}
46+
47+
public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
48+
{
49+
return new PublicReadOnlyPropertyReflection(
50+
$this->getClassNameFromPropertyName($propertyName),
51+
$classReflection
52+
);
1253
}
1354

1455
/**
1556
* @return array<string>
1657
*/
17-
protected function getContainingClassNames(): array
58+
private function getContainingClassNames(): array
1859
{
1960
return [
2061
'Controller',
2162
'Component',
2263
];
2364
}
2465

25-
protected function getClassNameFromPropertyName(
26-
string $propertyName
27-
): string {
28-
return $propertyName . 'Component';
66+
private function getClassNameFromPropertyName(string $propertyName): string
67+
{
68+
return str_contains($propertyName, 'Component') ? $propertyName : $propertyName . 'Component';
69+
}
70+
71+
/**
72+
* @return list<string>
73+
*/
74+
private function getDefinedComponentsAsList(ClassReflection $classReflection): array
75+
{
76+
$definedComponents = [];
77+
78+
foreach (array_merge([$classReflection], $classReflection->getParents()) as $class) {
79+
if (!$class->hasProperty('components')) {
80+
continue;
81+
}
82+
83+
$defaultValue = $class->getNativeProperty('components')
84+
->getNativeReflection()
85+
->getDefaultValueExpression();
86+
87+
if (!$defaultValue instanceof Array_) {
88+
continue;
89+
}
90+
91+
foreach ($defaultValue->items as $item) {
92+
if ($item->value instanceof String_) {
93+
$definedComponents[] = $item->value->value;
94+
95+
continue;
96+
}
97+
98+
if ($item->value instanceof ClassConstFetch && $item->value->class instanceof FullyQualified) {
99+
$definedComponents[] = $item->value->class->toString();
100+
}
101+
}
102+
}
103+
104+
return $definedComponents;
29105
}
30106
}

tests/Feature/ControllerExtensionsTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public function dataFileAsserts(): iterable
1616
yield from $this->gatherAssertTypes(__DIR__ . '/data/existing_controller_model.php');
1717
yield from $this->gatherAssertTypes(__DIR__ . '/data/existing_controller_component.php');
1818
yield from $this->gatherAssertTypes(__DIR__ . '/data/invalid_controller_property.php');
19+
yield from $this->gatherAssertTypes(__DIR__ . '/data/existing_controller_component_with_same_method_name_as_model.php');
20+
yield from $this->gatherAssertTypes(__DIR__ . '/data/existing_controller_component_from_parent_controller.php');
1921
}
2022

2123
/**
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
class BaseController extends Controller
6+
{
7+
/**
8+
* @var array<array-key, string>
9+
*/
10+
public $components = ['Basic'];
11+
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
<?php
22

3-
class BasicController extends Controller {}
3+
class BasicController extends Controller
4+
{
5+
/**
6+
* @var array<array-key, string>
7+
*/
8+
public $components = ['Basic'];
9+
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
<?php
22

3-
class BasicComponent extends Component {}
3+
class BasicComponent extends Component
4+
{
5+
/**
6+
* @var array<array-key, string>
7+
*/
8+
public $components = ['Second'];
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
class SameAsModelComponent extends Component
4+
{
5+
public function sameMethod(): int
6+
{
7+
return 1;
8+
}
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
class SameAsModelController extends BaseController
4+
{
5+
/**
6+
* @var array<array-key, string>
7+
*/
8+
public $components = [
9+
'SameAsModel',
10+
BasicComponent::class,
11+
];
12+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
class SameAsModel extends Model
4+
{
5+
public function sameMethod(): string
6+
{
7+
return 'test';
8+
}
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/** @var SameAsModelController $controller */
8+
$component = $controller->Basic;
9+
10+
assertType('BasicComponent', $component);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/** @var SameAsModelController $controller */
8+
$component = $controller->SameAsModel->sameMethod();
9+
10+
assertType('int', $component);
11+
12+
/** @var SameAsModelController $controller */
13+
$component = $controller->BasicComponent;
14+
15+
assertType('BasicComponent', $component);

0 commit comments

Comments
 (0)