diff --git a/composer.json b/composer.json index addf9ca38..c9c61db06 100644 --- a/composer.json +++ b/composer.json @@ -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": { diff --git a/dev/Larastan/ReturnTypes/ContainerArrayAccessDynamicMethodReturnTypeExtension.php b/dev/Larastan/ReturnTypes/ContainerArrayAccessDynamicMethodReturnTypeExtension.php new file mode 100644 index 000000000..1e5ab6c12 --- /dev/null +++ b/dev/Larastan/ReturnTypes/ContainerArrayAccessDynamicMethodReturnTypeExtension.php @@ -0,0 +1,43 @@ +getName() === 'make' - || $methodReflection->getName() === 'get'; + return in_array($methodReflection->getName(), ['make', 'makeWith', 'resolve'], true); } #[Override] @@ -43,30 +43,54 @@ 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 $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; } } diff --git a/packages/core/src/Utils/Scheduler.php b/packages/core/src/Utils/Scheduler.php index 6c1a4365c..dd09c1b31 100644 --- a/packages/core/src/Utils/Scheduler.php +++ b/packages/core/src/Utils/Scheduler.php @@ -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{ @@ -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']); diff --git a/packages/core/src/Utils/SchedulerTest.php b/packages/core/src/Utils/SchedulerTest.php index 4db67bdf0..9157d7378 100644 --- a/packages/core/src/Utils/SchedulerTest.php +++ b/packages/core/src/Utils/SchedulerTest.php @@ -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 { diff --git a/packages/graphql/src/Builder/Manipulator.php b/packages/graphql/src/Builder/Manipulator.php index bb034f63f..3c0d15c1e 100644 --- a/packages/graphql/src/Builder/Manipulator.php +++ b/packages/graphql/src/Builder/Manipulator.php @@ -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; @@ -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; diff --git a/packages/graphql/src/Stream/Types/OffsetTest.php b/packages/graphql/src/Stream/Types/OffsetTest.php index 93703dd52..6f78d695c 100644 --- a/packages/graphql/src/Stream/Types/OffsetTest.php +++ b/packages/graphql/src/Stream/Types/OffsetTest.php @@ -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; diff --git a/packages/graphql/src/Utils/AstManipulatorTest.php b/packages/graphql/src/Utils/AstManipulatorTest.php index c6f7458e7..e5f7410d6 100644 --- a/packages/graphql/src/Utils/AstManipulatorTest.php +++ b/packages/graphql/src/Utils/AstManipulatorTest.php @@ -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, diff --git a/packages/migrator/src/Seeders/SeederService.php b/packages/migrator/src/Seeders/SeederService.php index 57c893e41..c3a7549cf 100644 --- a/packages/migrator/src/Seeders/SeederService.php +++ b/packages/migrator/src/Seeders/SeederService.php @@ -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; @@ -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) { diff --git a/packages/serializer/src/Factory.php b/packages/serializer/src/Factory.php index 050e07204..ef8242fca 100644 --- a/packages/serializer/src/Factory.php +++ b/packages/serializer/src/Factory.php @@ -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; @@ -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, ); diff --git a/packages/testing/src/Concerns/Override.php b/packages/testing/src/Concerns/Override.php index f7c1e10c2..ecdd388f3 100644 --- a/packages/testing/src/Concerns/Override.php +++ b/packages/testing/src/Concerns/Override.php @@ -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. } }