Skip to content

Commit

Permalink
Teach see a difference between Component and Model especially when bo…
Browse files Browse the repository at this point in the history
…th have methods with the same name
  • Loading branch information
sidz committed Jan 29, 2024
1 parent 2fe3af0 commit 698c3b3
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 9 deletions.
82 changes: 75 additions & 7 deletions src/ClassComponentsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,95 @@

namespace PHPStanCakePHP2;

final class ClassComponentsExtension extends ClassPropertiesExtension
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Scalar\String_;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertiesClassReflectionExtension;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\Reflection\ReflectionProvider;

final class ClassComponentsExtension implements PropertiesClassReflectionExtension
{
protected function getPropertyParentClassName(): string
private ReflectionProvider $reflectionProvider;

public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}

public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
{
if (!array_filter($this->getContainingClassNames(), [$classReflection, 'is'])) {
return false;
}

$isDefinedInComponentsProperty = (bool) array_filter(
$this->getDefinedComponentsAsList($classReflection),
static fn (string $componentName): bool => $componentName === $propertyName
);

if (!$isDefinedInComponentsProperty) {
return false;
}

$propertyClassName = $this->getClassNameFromPropertyName($propertyName);

return $this->reflectionProvider->hasClass($propertyClassName)
&& $this->reflectionProvider->getClass($propertyClassName)
->is('Component');
}

public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
{
return 'Component';
return new PublicReadOnlyPropertyReflection(
$this->getClassNameFromPropertyName($propertyName),
$classReflection
);
}

/**
* @return array<string>
*/
protected function getContainingClassNames(): array
private function getContainingClassNames(): array
{
return [
'Controller',
'Component',
];
}

protected function getClassNameFromPropertyName(
string $propertyName
): string {
private function getClassNameFromPropertyName(string $propertyName): string
{
return $propertyName . 'Component';
}

/**
* @return list<string>
*/
private function getDefinedComponentsAsList(ClassReflection $classReflection): array
{
$definedComponents = [];

foreach (array_merge([$classReflection], $classReflection->getParents()) as $class) {
if (!$class->hasProperty('components')) {
continue;
}

$defaultValue = $class->getNativeProperty('components')
->getNativeReflection()
->getDefaultValueExpression();

if (!$defaultValue instanceof Array_) {
continue;
}

foreach ($defaultValue->items as $item) {
if ($item !== null && $item->value instanceof String_) {
$definedComponents[] = $item->value->value;
}
}
}

return $definedComponents;
}
}
2 changes: 2 additions & 0 deletions tests/Feature/ControllerExtensionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/existing_controller_model.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/existing_controller_component.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/invalid_controller_property.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/existing_controller_component_with_same_method_name_as_model.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/existing_controller_component_from_parent_controller.php');
}

/**
Expand Down
11 changes: 11 additions & 0 deletions tests/Feature/classes/Controller/BaseController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

class BaseController extends Controller
{
/**
* @var array<array-key, string>
*/
public $components = ['Basic'];
}
8 changes: 7 additions & 1 deletion tests/Feature/classes/Controller/BasicController.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
<?php

class BasicController extends Controller {}
class BasicController extends Controller
{
/**
* @var array<array-key, string>
*/
public $components = ['Basic'];
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
<?php

class BasicComponent extends Component {}
class BasicComponent extends Component
{
/**
* @var array<array-key, string>
*/
public $components = ['Second'];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

class SameAsModelComponent extends Component
{
public function sameMethod(): int
{
return 1;
}
}
9 changes: 9 additions & 0 deletions tests/Feature/classes/Controller/SameAsModelController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

class SameAsModelController extends BaseController
{
/**
* @var array<array-key, string>
*/
public $components = ['SameAsModel'];
}
9 changes: 9 additions & 0 deletions tests/Feature/classes/Model/SameAsModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

class SameAsModel extends Model
{
public function sameMethod(): string
{
return 'test';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types = 1);

use function PHPStan\Testing\assertType;

/** @var SameAsModelController $controller */
$component = $controller->Basic;

assertType('BasicComponent', $component);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types = 1);

use function PHPStan\Testing\assertType;

/** @var SameAsModelController $controller */
$component = $controller->SameAsModel->sameMethod();

assertType('int', $component);

0 comments on commit 698c3b3

Please sign in to comment.