Skip to content

Commit

Permalink
dev: Better version of ContainerExtension for PHPStan and fixes.
Browse files Browse the repository at this point in the history
Also disabled following Larastan extensions because we want to use `Interface` not the actual implementation (it is more safe for package)

* `Larastan\Larastan\ReturnTypes\ContainerArrayAccessDynamicMethodReturnTypeExtension`
* `Larastan\Larastan\ReturnTypes\ContainerMakeDynamicReturnTypeExtension`
  • Loading branch information
LastDragon-ru committed Jan 9, 2024
1 parent a342eae commit ad9af60
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 53 deletions.
10 changes: 8 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,14 @@
},
"autoload-dev": {
"psr-4": {
"LastDragon_ru\\LaraASP\\Dev\\": "dev"
}
"LastDragon_ru\\LaraASP\\Dev\\App\\": "dev/App",
"LastDragon_ru\\LaraASP\\Dev\\PhpStan\\": "dev/PhpStan",
"Larastan\\Larastan\\": "dev/Larastan"
},
"exclude-from-classmap": [
"vendor/larastan/larastan/src/ReturnTypes/ContainerArrayAccessDynamicMethodReturnTypeExtension.php",
"vendor/larastan/larastan/src/ReturnTypes/ContainerMakeDynamicReturnTypeExtension.php"
]
},
"extra": {
"laravel": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types = 1);

namespace Larastan\Larastan\ReturnTypes;

use Illuminate\Contracts\Container\Container;
use Override;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;

/**
* Original class uses {@see Container::make()} it is slow and unwanted
*
* @internal
*
* @see ContainerExtension
*/
class ContainerArrayAccessDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension {
public function __construct(
private readonly string $className = Container::class,
) {
// empty
}

#[Override]
public function getClass(): string {
return $this->className;
}

#[Override]
public function isMethodSupported(MethodReflection $methodReflection): bool {
return false;
}

#[Override]
public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope,
): ?Type {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php declare(strict_types = 1);

namespace Larastan\Larastan\ReturnTypes;

use Illuminate\Contracts\Container\Container;
use LastDragon_ru\LaraASP\Dev\PhpStan\Extensions\Container\ContainerExtension;
use Override;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;

/**
* Original class uses {@see Container::make()} it is slow and unwanted
*
* @internal
*
* @see ContainerExtension
*/
class ContainerMakeDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension {
#[Override]
public function getClass(): string {
return Container::class;
}

#[Override]
public function isMethodSupported(MethodReflection $methodReflection): bool {
return false;
}

#[Override]
public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope,
): ?Type {
return null;
}
}
84 changes: 54 additions & 30 deletions dev/PhpStan/Extensions/Container/ContainerExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,95 @@

namespace LastDragon_ru\LaraASP\Dev\PhpStan\Extensions\Container;

use Illuminate\Contracts\Container\Container;
use Larastan\Larastan\Concerns\HasContainer;
use Illuminate\Container\Container;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container as ContainerContract;
use Override;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\String_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ErrorType;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Throwable;

use function count;
use function in_array;
use function is_object;

/**
* @internal
*/
final class ContainerExtension implements DynamicMethodReturnTypeExtension {
use HasContainer;
public function __construct() {
// empty
}

#[Override]
public function getClass(): string {
return Container::class;
return ContainerContract::class;
}

#[Override]
public function isMethodSupported(MethodReflection $methodReflection): bool {
return $methodReflection->getName() === 'make'
|| $methodReflection->getName() === 'get';
return in_array($methodReflection->getName(), ['make', 'makeWith', 'resolve'], true);
}

#[Override]
public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope,
): Type {
$arg = $methodCall->args[0] ?? null;
$expr = $arg instanceof Arg ? $arg->value : null;
): ?Type {
// Type?
$arg = $methodCall->args[0] ?? null;
$argExpr = $arg instanceof Arg ? $arg->value : null;
$argType = $argExpr instanceof Expr ? $scope->getType($argExpr) : null;

if ($expr instanceof String_) {
try {
$resolved = $this->resolve($expr->value);
if ($argType === null) {
return null;
}

if ($resolved === null) {
return new ErrorType();
}
// Return
return match (true) {
$argType->isClassStringType()->yes() => $argType->getClassStringObjectType(),
$argType->getConstantStrings() !== [] => $this->getFromContainer($argType->getConstantStrings()),
default => null,
};
}

return is_object($resolved)
? new ObjectType($resolved::class)
: new ErrorType();
} catch (Throwable) {
return new ErrorType();
}
/**
* @param list<ConstantStringType> $constants
*/
protected function getFromContainer(array $constants): ?ObjectType {
// Unions are not supported
if (count($constants) !== 1) {
return null;
}

if ($expr instanceof ClassConstFetch && $expr->class instanceof FullyQualified) {
return new ObjectType($expr->class->toString());
// In the most cases, `$abstract` is the class/interface, but there are
// few of them which are not.
$abstract = $constants[0]->getValue();
$strings = [
'migration.repository',
'migrator',
];
$type = null;

if (in_array($abstract, $strings, true)) {
try {
$concrete = Container::getInstance()->make($abstract);

if (is_object($concrete)) {
$type = new ObjectType($concrete::class);
}
} catch (BindingResolutionException) {
// ignore
}
}

return new NeverType();
return $type;
}
}
16 changes: 13 additions & 3 deletions packages/core/src/Utils/Scheduler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Container\Container;
use Illuminate\Contracts\Queue\ShouldQueue;
use InvalidArgumentException;
use LastDragon_ru\LaraASP\Core\Contracts\Schedulable;

use function array_intersect_key;
use function is_callable;
use function is_int;
use function sprintf;

/**
* @phpstan-type SchedulableSettings array{
Expand Down Expand Up @@ -39,9 +42,16 @@ public function register(Schedule $schedule, string $class): bool {
}

// Register
$event = $instance instanceof ShouldQueue
? $schedule->job($instance)
: $schedule->call($instance);
$event = match (true) {
$instance instanceof ShouldQueue => $schedule->job($instance),
is_callable($instance) => $schedule->call($instance),
default => throw new InvalidArgumentException(
sprintf(
'The `%s` must be a callable.',
$class,
),
),
};

$event->cron($settings['cron']);

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/Utils/SchedulerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public function getSchedule(): array {
'withoutOverlapping' => 123,
];
}

public function __invoke(): void {
// empty
}
};

$this->override(Schedule::class, static function (MockInterface $schedule) use ($job): void {
Expand Down
11 changes: 2 additions & 9 deletions packages/graphql/src/Builder/Manipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
use Illuminate\Support\Str;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scope;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeProvider;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource;
use LastDragon_ru\LaraASP\GraphQL\Builder\Directives\OperatorsDirective;
Expand Down Expand Up @@ -80,15 +79,9 @@ public function getBuilderInfo(): BuilderInfo {
// =========================================================================
#[Override]
public function getType(string $definition, TypeSource $source): string {
// Instance (phpstan is not so smart yet...)
$instance = Container::getInstance()->make($definition);

if (!($instance instanceof TypeDefinition)) {
throw new TypeDefinitionImpossibleToCreateType($definition, $source);
}

// Exists?
$name = $instance->getTypeName($this, $source);
$instance = Container::getInstance()->make($definition);
$name = $instance->getTypeName($this, $source);

if ($this->isTypeDefinitionExists($name)) {
return $name;
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/src/Stream/Types/OffsetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function testSerialize(Exception|string|int $expected, mixed $value): voi

$scalar = new Offset();
$actual = $scalar->serialize($value);
$actual = is_string($expected)
$actual = is_string($actual)
? Container::getInstance()->make(Encrypter::class)->decrypt($actual, false)
: $actual;

Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/src/Utils/AstManipulatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public function testGetDirectives(): void {
// Field
$schema = Container::getInstance()->make(SchemaBuilder::class)->schema();
$query = $schema->getQueryType();
$field = $manipulator->getField($query, 'test');
$field = $query ? $manipulator->getField($query, 'test') : null;
$expected = [
AllDirective::class,
AstManipulatorTest_BDirective::class,
Expand Down
3 changes: 2 additions & 1 deletion packages/migrator/src/Seeders/SeederService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace LastDragon_ru\LaraASP\Migrator\Seeders;

use Illuminate\Container\Container;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\Connection;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Eloquent\Model;
Expand All @@ -21,7 +22,7 @@ public function isSeeded(): bool {
$seeded = false;
$tables = $this->getConnection()->getDoctrineSchemaManager()->listTableNames();
$skipped = [
Container::getInstance()->make('config')->get('database.migrations'),
Container::getInstance()->make(Repository::class)->get('database.migrations'),
];

foreach ($tables as $table) {
Expand Down
17 changes: 12 additions & 5 deletions packages/serializer/src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function config;

use const JSON_BIGINT_AS_STRING;
Expand Down Expand Up @@ -72,12 +71,20 @@ protected function make(
array $context,
string $format,
): SerializerContract {
$factory = static fn ($class) => Container::getInstance()->make($class);
$encoders = array_map($factory, $encoders);
$normalizers = array_map($factory, $normalizers);
$container = Container::getInstance();
$encoderInstances = [];
$normalizerInstances = [];

foreach ($encoders as $class) {
$encoderInstances[] = $container->make($class);
}

foreach ($normalizers as $class) {
$normalizerInstances[] = $container->make($class);
}

return new Serializer(
new SymfonySerializer($normalizers, $encoders),
new SymfonySerializer($normalizerInstances, $encoderInstances),
$format,
$context,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/testing/src/Concerns/Override.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,6 @@ protected function override(string $class, mixed $factory = null): mixed {
);

// Return
return $mock;
return $mock; // @phpstan-ignore-line `ContainerExtension` is not so smart yet.
}
}

0 comments on commit ad9af60

Please sign in to comment.