Skip to content

Commit

Permalink
Add support to loading components on the fly (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
sidz authored Oct 12, 2023
1 parent c5b3669 commit 15aa983
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 0 deletions.
3 changes: 3 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ services:
- class: ARiddlestone\PHPStanCakePHP2\Service\SchemaService
arguments:
schemaPaths: %SchemaService.schemaPaths%
- class: ARiddlestone\PHPStanCakePHP2\LoadComponentOnFlyMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
parametersSchema:
ModelBehaviorsExtension: structure([
behaviorPaths: listOf(string())
Expand Down
56 changes: 56 additions & 0 deletions src/LoadComponentOnFlyMethodReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace ARiddlestone\PHPStanCakePHP2;

use Component;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Scalar\String_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

class LoadComponentOnFlyMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
private ReflectionProvider $reflectionProvider;

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

public function getClass(): string
{
return \ComponentCollection::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'load';
}

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
{
$arg = $methodCall->getArgs()[0]->value;

if (!$arg instanceof String_) {
return null;
}

$componentName = $arg->value . 'Component';

if (!$this->reflectionProvider->hasClass($componentName)) {
return null;
}

if (!$this->reflectionProvider->getClass($componentName)->is(Component::class)) {
return null;
}

return new ObjectType($componentName);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/LoadComponentOnFlyMethodReturnTypeExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace ARiddlestone\PHPStanCakePHP2\Test\Feature;

use PHPStan\Testing\TypeInferenceTestCase;

class LoadComponentOnFlyMethodReturnTypeExtensionTest extends TypeInferenceTestCase
{
/**
* @return mixed[]
*/
public function dataFileAsserts(): iterable
{
yield from self::gatherAssertTypes(__DIR__ . '/data/loading_component_loaded_on_fly.php');
}

/**
* @dataProvider dataFileAsserts
* @param mixed $args
*/
public function testControllerExtensions(string $assertType, string $file, ...$args): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/data/phpstan.neon',
];
}
}
10 changes: 10 additions & 0 deletions tests/Feature/data/loading_component_loaded_on_fly.php
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 BasicController $controller */
$component = $controller->Components->load('Basic');

assertType('BasicComponent', $component);

0 comments on commit 15aa983

Please sign in to comment.