1

从零实现一个 PHP 微框架 - 服务提供者

 2 years ago
source link: https://blog.ixk.me/post/implement-a-php-microframework-from-zero-5
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
本文最后更新于 268 天前,文中所描述的信息可能已发生改变

考试在两周前就结束了,因为一直在填坑 XK-Java,所以一直没更新 PHP 微框架系列文章 2333,最近 XK-Java 也填的差不多了,后续打算把 XK-PHP 也适配到 Swoole,还有 XK-Blog 的坑还没填 ?。

什么是服务提供者(Provider)?

如果你写过 Laravel,那么你一定对 Provider 不陌生,服务提供者实现了将服务绑定到服务容器(IoC Container),并按需启动的功能。

在 Laravel 中,服务提供者是在 config/app.php 的配置文件中配置的:

1<?php
2return [
3    'providers' => [
4        /*
5         * Laravel Framework Service Providers...
6         */
7        Illuminate\Auth\AuthServiceProvider::class,
8        Illuminate\Broadcasting\BroadcastServiceProvider::class,
9        Illuminate\Bus\BusServiceProvider::class,
10        Illuminate\Cache\CacheServiceProvider::class,
11        Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
12        Illuminate\Cookie\CookieServiceProvider::class,
13        Illuminate\Database\DatabaseServiceProvider::class,
14        Illuminate\Encryption\EncryptionServiceProvider::class,
15        Illuminate\Filesystem\FilesystemServiceProvider::class,
16        Illuminate\Foundation\Providers\FoundationServiceProvider::class,
17        Illuminate\Hashing\HashServiceProvider::class,
18        Illuminate\Mail\MailServiceProvider::class,
19        Illuminate\Notifications\NotificationServiceProvider::class,
20        Illuminate\Pagination\PaginationServiceProvider::class,
21        Illuminate\Pipeline\PipelineServiceProvider::class,
22        Illuminate\Queue\QueueServiceProvider::class,
23        Illuminate\Redis\RedisServiceProvider::class,
24        Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
25        Illuminate\Session\SessionServiceProvider::class,
26        Illuminate\Translation\TranslationServiceProvider::class,
27        Illuminate\Validation\ValidationServiceProvider::class,
28        Illuminate\View\ViewServiceProvider::class,
29
30        /*
31         * Package Service Providers...
32         */
33
34        /*
35         * Application Service Providers...
36         */
37        App\Providers\AppServiceProvider::class,
38        App\Providers\AuthServiceProvider::class,
39        // App\Providers\BroadcastServiceProvider::class,
40        App\Providers\EventServiceProvider::class,
41        App\Providers\RouteServiceProvider::class
42    ]
43];

这些服务提供者会在框架启动后将服务注册到服务容器上,如果你学过 Spring,那么就很好理解了,这些服务提供者就是用编码的方式来注册 BeanComponent 等等,因为 PHP8 才开始支持注解,所以目前跑在 PHP7 的 Laravel 无法像 Spring 一样很方便的绑定服务实例。

光说肯定没法很好的理解,那么我们就来看一个简单的服务提供者吧:

1<?php
2
3namespace Illuminate\Cookie;
4
5use Illuminate\Support\ServiceProvider;
6
7class CookieServiceProvider extends ServiceProvider
8{
9    /**
10     * Register the service provider.
11     *
12     * @return void
13     */
14    public function register()
15    {
16        $this->app->singleton('cookie', function ($app) {
17            $config = $app->make('config')->get('session');
18
19            return (new CookieJar)->setDefaultPathAndDomain(
20                $config['path'], $config['domain'], $config['secure'], $config['same_site'] ?? null
21            );
22        });
23    }
24}

这是 Laravel 用来注册 CookieJar 实例的服务提供者,可以看到在 register 方法中使用了 singleton 方法将一个的回调注册到了服务容器中,这样,当我们使用 $app->make('cookie') 的时候就可以取得 CookieJar 实例了。

当然服务提供者不止可以注册服务,还能在服务注册后启动实例,只要重写 boot 方法即可。

定义 Provider

了解了服务提供者,那么就可以开始编码了。

首先,我们需要定义 Provider 的接口和抽象类来规范 Provider,如下:

1<?php
2
3namespace App\Contracts;
4
5interface Provider
6{
7    // 之所以只定义 register 方法是因为并不是每一个服务提供者都需要预加载(boot),当然你也可以在接口定义 boot 方法,然后在抽象类中提供一个无影响的实现
8    public function register(): void;
9}
1<?php
2
3namespace App\Providers;
4
5use App\Application;
6
7abstract class Provider implements \App\Contracts\Provider
8{
9    /**
10     * @var Application
11     */
12    protected $app;
13
14    /**
15     * @var bool
16     */
17    public $booted = false;
18
19    public function __construct(Application $app)
20    {
21        $this->app = $app;
22    }
23}

可以看到 ProviderBootstrap 其实差不多,所以这里就不对抽象类做说明了,具体请看 Bootstrap 篇

编写 Provider

由于 Provider 数量众多,所以这里就不全部写了,只列举几个比较典型的。

CookieProvider

CookieProvider 是一个简单纯注册服务的 Provider

1<?php
2
3namespace App\Providers;
4
5use App\Http\CookieManager;
6
7class CookieProvider extends Provider
8{
9    public function register(): void
10    {
11        // 将 CookieManager 注册到服务容器
12        $this->app->singleton(CookieManager::class, null, 'cookie');
13    }
14}

RouteProvider

RouteProvider 在注册后还会提供预加载,即 boot

1<?php
2
3namespace App\Providers;
4
5use App\Kernel\RouteManager;
6
7class RouteProvider extends Provider
8{
9    public function register(): void
10    {
11        $this->app->singleton(RouteManager::class, null, 'route');
12    }
13
14    // 在所有服务容器注册完毕后,经 BootProvider 这个 Bootstrap 调用该方法来预加载服务
15    public function boot(): void
16    {
17        // 预加载就是在框架启动后立即实例化服务的功能
18        $this->app->make(RouteManager::class);
19    }
20}

AspectProvider

除了简单 Provider,还有一些比较复杂的 Provider,如 AspectProvider

1<?php
2
3namespace App\Providers;
4
5use App\Kernel\AspectManager;
6use function array_keys;
7use function config;
8
9class AspectProvider extends Provider
10{
11    /**
12     * @var string[]
13     */
14    protected $aspects;
15
16    public function register(): void
17    {
18        $this->app->singleton(
19            AspectManager::class,
20            function () {
21                return new AspectManager($this->app);
22            },
23            'aspect.manager'
24        );
25        $this->aspects = config('aspect');
26        foreach (array_keys($this->aspects) as $aspect) {
27            $this->app->singleton($aspect, null);
28        }
29    }
30
31    public function boot(): void
32    {
33        /* @var AspectManager $manager */
34        $manager = $this->app->make(AspectManager::class);
35        foreach ($this->aspects as $aspect => $points) {
36            $manager->putPoint($points, $this->app->make($aspect));
37        }
38    }
39}

AspectProvider 中不止注册了 AspectManager,还会读取配置文件将切面也加载进服务容器,同时在启动时把切面依次放入 AspectManager

加载 Provider

有了 Provider,那么 Provider 是如何被创建和执行的呢?

Bootstrap 有两个 Provider 相关的 Bootstrap,分别是 RegisterProvidersBootProviders,上一篇文章只是简单介绍了这两个 Bootstrap 的功能,这里就详细的讲解一下。

1<?php
2
3namespace App\Bootstrap;
4
5use App\Kernel\ProviderManager;
6use function config;
7
8class RegisterProviders extends Bootstrap
9{
10    public function boot(): void
11    {
12        $this->app->setProviderManager(new ProviderManager($this->app));
13        $this->app->getProviderManager()->registers(config('app.providers'));
14        $this->app->instance(
15            ProviderManager::class,
16            $this->app->getProviderManager(),
17            'provider_manager'
18        );
19    }
20}

然后我们看看 ProviderManager

1<?php
2
3namespace App\Kernel;
4
5use App\Application;
6use App\Providers\Provider;
7use function array_map;
8use function array_walk;
9use function is_string;
10use function method_exists;
11
12class ProviderManager
13{
14    /**
15     * @var Application
16     */
17    protected $app;
18
19    /**
20     * @var Provider[]
21     */
22    protected $providers;
23
24    public function __construct(Application $app)
25    {
26        $this->app = $app;
27    }
28
29    /**
30     * @param string|Provider $provider
31     * @return Provider|null
32     */
33    public function getProvider($provider): ?Provider
34    {
35        if (is_string($provider)) {
36            return $this->providers[$provider] ?? null;
37        }
38        return $provider;
39    }
40
41    public function setProvider($name, $provider): void
42    {
43        $this->providers[$name] = $provider;
44    }
45
46    public function register($provider, $force = false): Provider
47    {
48        if (!$force && ($reg = $this->getProvider($provider)) !== null) {
49            return $reg;
50        }
51        if (is_string($provider)) {
52            $name = $provider;
53            $provider = new $provider($this->app);
54            $this->setProvider($name, $provider);
55        }
56        if (method_exists($provider, 'register')) {
57            $provider->register();
58        }
59        return $provider;
60    }
61
62    public function registers(array $providers): array
63    {
64        return array_map(function ($provider) {
65            return $this->register($provider);
66        }, $providers);
67    }
68
69    public function boot(): void
70    {
71        array_walk($this->providers, function (Provider $provider) {
72            if (!$provider->booted && method_exists($provider, 'boot')) {
73                $this->app->call('boot', [], $provider);
74                $provider->booted = true;
75            }
76        });
77    }
78}

其实看起来复杂,不过 ProviderManager 做的事情也很简单。

首先利用 Class 创建对应 Provider 实例,然后将 Provider 添加到 ProviderManagerProvider 数组中,最后判断有没有 register 方法,如果有则执行该方法。

有了注册,那么还需要一个启动:

1<?php
2
3namespace App\Bootstrap;
4
5class BootProviders extends Bootstrap
6{
7    public function boot(): void
8    {
9        $this->app->getProviderManager()->boot();
10    }
11}

BootProvidersboot 方法会从服务容器中取得 ProviderManager,然后调用其 boot 方法,依次执行 Providerboot 方法。

到这里 Provider 的部分就说完了。现在也放假了,PHP 微框架的文章会逐步更新。不过最近还是比较忙的,更新速度会慢一点。?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK