从零实现一个 PHP 微框架 - 服务提供者
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.
考试在两周前就结束了,因为一直在填坑 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,那么就很好理解了,这些服务提供者就是用编码的方式来注册 Bean
、Component
等等,因为 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}
可以看到 Provider
和 Bootstrap
其实差不多,所以这里就不对抽象类做说明了,具体请看 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
,分别是 RegisterProviders
和 BootProviders
,上一篇文章只是简单介绍了这两个 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
添加到 ProviderManager
的 Provider
数组中,最后判断有没有 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}
在 BootProviders
中 boot
方法会从服务容器中取得 ProviderManager
,然后调用其 boot
方法,依次执行 Provider
的 boot
方法。
到这里 Provider 的部分就说完了。现在也放假了,PHP 微框架的文章会逐步更新。不过最近还是比较忙的,更新速度会慢一点。?
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK