guanguans / guanguans.github.io

guanguans 博客
https://www.guanguans.cn
Other
55 stars 6 forks source link

laravel 中实现注解注入 #57

Open guanguans opened 3 weeks ago

guanguans commented 3 weeks ago

laravel 中实现注解注入

创建注解类

<?php

declare(strict_types=1);

namespace App\Support\Attributes;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
readonly class Injection
{
    public function __construct(
        public ?string $propertyType = null,
        public array $parameters = []
    ) {}
}

引导解析注解

<?php

declare(strict_types=1);

namespace App\Providers;

use App\Support\Attributes\Injection;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void {}

    public function boot(): void
    {
        $this->injection();
    }

    private function injection(): void
    {
        $this->app->resolving(static function (mixed $object, Application $app): void {
            if (! \is_object($object)) {
                return;
            }

            $class = str($object::class);
            if (
                ! $class->is(config('services.injection.only'))
                || $class->is(config('services.injection.except'))
            ) {
                return;
            }

            $reflectionObject = new \ReflectionObject($object);

            foreach ($reflectionObject->getProperties() as $reflectionProperty) {
                if (! $reflectionProperty->isDefault() || $reflectionProperty->isStatic()) {
                    continue;
                }

                $attributes = $reflectionProperty->getAttributes(Injection::class);
                if ($attributes === []) {
                    continue;
                }

                /** @var Injection $injection */
                $injection = $attributes[0]->newInstance();

                $propertyType = value(static function () use ($injection, $reflectionProperty, $reflectionObject): string {
                    if ($injection->propertyType) {
                        return $injection->propertyType;
                    }

                    $reflectionPropertyType = $reflectionProperty->getType();
                    if ($reflectionPropertyType instanceof \ReflectionNamedType && ! $reflectionPropertyType->isBuiltin()) {
                        return $reflectionPropertyType->getName();
                    }

                    throw new \LogicException(\sprintf(
                        'Attribute [%s] of %s miss a argument, or %s must be a non-built-in named type.',
                        Injection::class,
                        $property = "property [{$reflectionObject->getName()}::\${$reflectionProperty->getName()}]",
                        $property,
                    ));
                });

                $reflectionProperty->isPublic() or $reflectionProperty->setAccessible(true);
                $reflectionProperty->setValue($object, $app->make($propertyType, $injection->parameters));
            }
        });
    }
}

配置解析范围(可选)

config/services.php

<?php

return [
    // ...

    'injection' => [
        'only' => [
            'App\*',
        ],
        'except' => [
            'App\Support\Macros\*',
        ],
    ],
];

使用

示例

<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Support\Attributes\Injection;
use App\Support\HmacSigner;
use Illuminate\Config\Repository;

class TestCommand extends \Illuminate\Console\Command
{
    protected $signature = 'test';

    #[Injection('path.storage')]
    private string $storagePath;

    #[Injection(parameters: ['secret' => 'secret...'])]
    private HmacSigner $hmacSigner;

    #[Injection(Repository::class)]
    private Repository $repositoryOfInjectionPropertyType;

    #[Injection('config')]
    private Repository $repositoryOfInjectionInstanceKey;

    #[Injection]
    private Repository $repositoryOfReflectionPropertyType;

    public function handle(): void
    {
        dump(
            $this->storagePath,
            $this->hmacSigner,
            $this->repositoryOfInjectionPropertyType->get('services.injection'),
            $this->repositoryOfInjectionInstanceKey->get('services.injection.only'),
            $this->repositoryOfReflectionPropertyType->get('services.injection.except'),
        );
    }
}

输出

╰─ ./artisan test                                                                                ─╯
"/Users/yaozm/Documents/wwwroot/laravel-skeleton/storage" // app/Console/Commands/TestCommand.php:32
App\Support\HmacSigner {#2045
  -secret: "secret..."
  -algo: "sha256"
} // app/Console/Commands/TestCommand.php:32
array:2 [
  "only" => array:1 [
    0 => "App\*"
  ]
  "except" => array:1 [
    0 => "App\Support\Macros\*"
  ]
] // app/Console/Commands/TestCommand.php:32
array:1 [
  0 => "App\*"
] // app/Console/Commands/TestCommand.php:32
array:1 [
  0 => "App\Support\Macros\*"
] // app/Console/Commands/TestCommand.php:32

与依赖注入比较

功能 注解注入 依赖注入
标量类型 已支持 未支持
传参 已支持 未支持

相关连接

原文连接