简化组件 / 容器
Simpla 框架的容器 IoC,实现了依赖注入
Requires
- php: >=7.0.0
- xtreamwayz/pimple-container-interop: ^1.0
This package is not auto-updated.
Last update: 2024-09-29 05:20:59 UTC
README
服务容器
服务容器的概念本质上是与对象和面向对象编程(OOP)相关联的。实际上,每个服务都可以是一个对象,但并不是每个对象都是一个服务。在现代化的应用程序中,我们有各种对象或对象集合,每个对象都有其特定的功能。因此,我们有帮助我们发送电子邮件、访问和注册数据库信息等的对象。那么,什么是服务呢?
服务
Symfony 的文档对服务进行了相当清晰的定义
服务是任何执行某种类型“全局”任务的 PHP 对象。这是一个在计算机科学中使用的通用术语,用于描述为特定目的而创建的对象(例如,发送电子邮件)。每个服务在其应用中的任何地方都需要该特定功能时都会使用。您不需要做任何事情来构建一个服务:只需编写一个执行特定任务的 PHP 类即可。恭喜,您刚刚创建了一个服务!这个概念存在于面向服务的架构中,其中每个“服务”都可以轻松访问和使用,而不会干扰其他服务或应用程序的运行。
服务容器
服务容器是一个负责管理和实例化服务的 PHP 对象(对象或元素)。
多个框架,如 Laravel、Zend、Symfony、Silex 和 Slim 也实现了服务容器。
Simpla 使用由 Symfony 开发和维护的 Pimple 作为其基础,它是 Silex 使用的,并使用了实现 PSR11 的版本。
为服务容器构建项目结构
在此示例中,我们将创建一个用于使用服务容器的标准结构。这是 Simpla 框架使用的相同结构,但我们将关注下面的示例。
-
以下定义了“Core”位置,其中将存储所有创建的服务。
-
在“Http/Controllers”中,我们将有一个可以访问该服务的类。
-
在“Providers”中,我们将有服务提供者。
-
在“bootstrap”中,我们将有一个服务自动初始化器。
/__app | |__Core | | |__ [Services.php] | |__Http | | |__Controllers | | |__ [UsingServices.php] | |__Providers | |__bootstrap | |__providers.php | |__index.php
创建面向接口的服务
服务不过是一个对象,对象由一个类定义。然而,在我们构建一个类之前,我们可以为对象定义一个接口。
在这个意义上,我们将定义一个简单的计算器服务如下
namespace App\Classes; interface CalculatorInterface { public function sum($a, $b); public function subtract($a, $b); public function multiply($a, $b); public function divide($a, $b); }
接口就像对外部环境提供的必需行为合同,其中声明了公共方法和常量,此外,接口在面向对象应用程序的解耦中扮演着重要的角色。
接口定义了必须由实现它的类以强制方式实现的哪些方法。
这种做法的好处是代码耦合度更低。当应用程序需要执行求和计算时,它不会成为“CalculadoraDoFulano”的奴隶,我们可以用“CalculadorMelhorDoCiclano”替换它,而无需在整个应用程序中更换“CalculadoraDoFulano”中求和的实现,因为“sum”被实现为sum($a, $b = null)
,而“CalculadorMelhorDoCiclano”实现了add($a, $b)
。
因此,让我们实现接口
namespace App\Classes; class Calculator implements CalculatorInterface { public function sum($a, $b) { return $a + $b; } public function subtract($a, $b) { return $a - $b; } public function multiply($a, $b) { return $a * $b; } public function divide($a, $b) { return $a / $b; } }
将服务添加到容器中
一旦定义了服务,我们就可以简单地使用$calc = new Calculator()
来使用它。然而,有时从应用程序管理的角度来看,这可能并不总是可行的。应用程序中存在多少个实例化的对象?我们应该定义服务为单例(Singleton)吗?
为了简化对象管理,我们可以使用以下选项来使用服务容器
Make
让我们在index.php文件中加载Simpla容器并“注入”Pimple容器,如下所示
use Simpla\Container\Container; use Xtreamwayz\Pimple\Container as Pimple; /* @var $app Simpla\Container\Container */ $app = Container::instance(new Pimple);
容器是一个单例,它确保在整个应用程序中只有一个实例,这样我们就保证了整个应用程序中只有一个控制器。
可选地,我们可以将$app
定义为一个全局对象,以便在整个应用程序中访问
global $app;
我们可以使用make
方法添加一个服务
$app->make("calc", function(){ return new \App\Core\Calculator(); });
如果由于某种原因对象已经被定义,我们可以简单地包含它,而不是闭包
$calc = new \App\Classes\Calculator(); $app->make("calc", $calc); // ou diretamente $app->make("calc", new \App\Classes\Calculator());
我们也可以通过类签名信息添加一个服务
$app->make("calc", \App\Classes\Calculator::class);
如果我们不想为服务定义一个名称,我们只需简单地提供类签名为唯一参数。
$app->make(\App\Classes\Calculator::class);
这种选项的缺点是需要提供\App\Classes\Calculator::class
作为服务名称,这可能会有些繁琐。
数组
通过实现ArrayAccess
接口,Simpla容器允许我们以添加数组值的方式添加服务
$app['calc1'] = new \App\Classes\Calculator(); // Ou ainda $app['calc2'] = function(){ return new \App\Classes\Calculator(); };
单例
使用singleton
方法确保一个类只有一个实例(对象)存在。
$app->singleton("calc", function(){ return new Calculator(); }); $calc1 = $app["calc"]; $calc2 = $app["calc"]; $calc3 = $app["calc"]; // $calc1 = $calc2 = $calc3
Pimple的一个特点,适用于这种情况的是将服务作为数组添加。
$app['calc'] = new \App\Classes\Calculator(); $calc1 = $app["calc"]; $calc2 = $app["calc"]; $calc3 = $app["calc"]; // $calc1 = $calc2 = $calc3
恢复服务
服务可以通过get
方法检索。
$calc = $app->get("calc"); $calc=>sum(34,54); //Usando diretamente $app->get("calc")->subtract(32,12);
我们也可以像访问数组一样检索一个服务
$calc = $app['calc']; $calc=>sum(34,54);
向容器添加数据
容器还可以包含任何其他类型的非对象信息。这对于包含可以在整个应用程序中使用的全局信息非常有用。
$app['tz.br.spo'] = "America/Sao_Paulo"; $app['tz'] = new DateTimeZone($app['tz.br.spo']); $app->make("today", new DateTime("now", $app['tz']));
调用服务(依赖注入)
call
方法允许使用“service@method”的语法调用预定义服务的某个方法,传递一个包含所有参数的数组。
$show = $app->call("today@format", ['d/m/Y H:i:s']); // 02/10/2017 21:11:25
这样,我们就向服务的方法中注入了一个依赖。
添加接口
我们可以使用接口名称来定义一个服务。
$app->make(App\Classes\CalculatorInterface::class, \App\Classes\Calculator::class); var_dump($app->get()); $calcs = $app[App\Classes\CalculatorInterface::class];
为了简化,我们可以为该接口建立一个标签,就像一个名称一样。
$app->tagged("ICalc", App\Classes\CalculatorInterface::class); $app->make("ICalc", \App\Classes\Calculator::class); // também podemos definir como array ou singleton $app["ICalc"] = new \App\Classes\Calculator; $app->singleton("ICalc", \App\Classes\Calculator::class);
添加闭包
我们可以在容器中添加闭包作为服务。
$app["sum"] = $app->closure(function ($a, $b) { return $a + $b; }); $rand = $app["sum"]; var_dump($rand(4,12)); // 16
获取服务创建函数
当你访问一个对象时,Pimple会自动调用你定义的匿名函数,为你创建服务对象。如果你想获得对这个函数的原始访问权限,你可以使用raw()
方法。
$calc = $app->raw('calc'); var_dump($calc()->sum(5,65)); // 70
这样,$calc
就获得了包含服务实现的一个匿名函数。当我们像函数一样调用$calc
时,服务就被创建了(创建了对象)。
定义后修改服务
在某些情况下,你可能想在服务定义之后修改服务的定义。你可以使用extend
方法来定义在服务创建后要执行的额外代码。
class Car { private $placa; private $ano; function getPlaca() { return $this->placa; } function getAno() { return $this->ano; } function setPlaca($placa) { $this->placa = $placa; } function setAno($ano) { $this->ano = $ano; } } $app['car'] = function(){ return new Car(); }; $app['car'] = $app->extend('car', function($car){ $car->setPlaca("HNT-2299"); $car->setAno(2010); return $car; }); var_dump($app['car']); /* object(Car)#37 (2) { ["placa":"Car":private]=> string(8) "HNT-2299" ["ano":"Car":private]=> int(2010) } */
第一个参数是要扩展的服务名称,第二个参数是一个函数,该函数可以访问对象实例和容器。
服务提供者
在直接翻译中,Service provider是指服务提供商,也就是说:提供商:提供某物的人或机构。 服务:提供帮助或支持的行为或结果。服务提供商:提供一项服务的人或机构。
Service provider补充了容器(Container)的使用,因为它充当服务启动器,为服务提供所有必需的模块,以便正确启动。在面向对象(Orientação a Objetos)的上下文中,这就像我设置了类构造器启动所需的全部参数。
namespace App\Providers; use Simpla\Contracts\ServiceProviderInterface; use Simpla\Contracts\ContainerInterface; class TodayServiceProvider implements ServiceProviderInterface { /** * Register the services * * @return void */ public function register(ContainerInterface $serviceContainer) { $tz = new \DateTimeZone("America/Sao_Paulo"); $serviceContainer->make("today", function(){ return new DateTime("now"); }); } }
Register 方法
请注意,我们的Service Provider被命名为TodayServiceProvider
,并有一个注册(register)方法,其中我们定义了我们的服务。
为了定义服务,我们使用定义了接口ContainerInterface
的容器作为参数,这样我们就可以使用容器并注册服务。
为了更好地理解,我们将在我们的结构中添加一个提供者。
/__app
| |__Core
| | |__ Calculator.php
| |__Http
| | |__Controllers
| | |__ Home.php
| |__Providers
| |__ CalculatorServiceProvider.php
|__bootstrap
| |__providers.php
| |__bootstrap.php
|
|__index.php
Simpla中的service provider的工作方式与Laravel非常相似。
以下是一个服务提供商的另一个示例:
// CalculatorServiceProvider.php namespace App\Providers; use Simpla\Contracts\ServiceProviderInterface; use Simpla\Contracts\ContainerInterface; class CalculatorServiceProvider implements ServiceProviderInterface { /** * Register the services * * @return void */ public function register(ContainerInterface $serviceContainer) { $serviceContainer->make("calc", function(){ return new \App\Classes\Calculator(); }); } }
该提供者已被创建,但只有在将其添加到我们的容器中时才能使用。为此,我们使用容器的register
方法。
$app->register(new \App\Providers\CalculatorServiceProvider()); $calc = $app['calc']; $calc->sum(13.5,432.3) // 445.8
如果我们需要将所有服务提供商添加到容器中,这可能有些费时,但当我们使用Bootstrap时,这种方法的实用性会变得非常明显。
Boot 方法
我们还可以在Service Provider中添加一个boot
方法。该方法在所有服务注册后调用,允许访问在此之前启动的所有服务。
boot
方法应用于启动服务。在这种情况下,我们可以与服务交互,初始化其依赖项以及应在服务创建/调用之前执行的事件。
// CalculatorServiceProvider.php namespace App\Providers; use Simpla\Contracts\ServiceProviderInterface; use Simpla\Contracts\ContainerInterface; class TestServiceProvider implements ServiceProviderInterface { /** * Bootstrap the services * * @return void */ public function boot(ContainerInterface $app) { echo "Isso é uma mensagem de inicialização"; //Imprimindo serviços disponíveis print_r($app->get()); } /** * Register the services * * @return void */ public function register(ContainerInterface $serviceContainer) { // code } }
Bootstrap
为了更自动化地创建我们的服务,我们可以使用一个初始化器。这样,我们可以使用包含我们希望调用的所有提供者的文件。
/__app
| |__Core
| | |__ [Services.php]
| |__Http
| | |__Controllers
| | |__ [UsingServices.php]
| |__Providers
|
|__bootstrap
| |__providers.php
| |__bootstrap.php
|
|__index.php
采用上述结构,我们可以在providers.php
文件中的数组中定义我们希望调用的所有服务。
<?php $providers = [ App\Providers\CalculatorServiceProvider::class, App\Providers\CarServiceProvider::class, App\Providers\HelloServiceProvider::class ]; $aliases = [ 'calc' => App\Facades\CalculatorFacade::class, 'hello' => App\Facades\HelloFacade::class, 'car' => \App\Facades\CarServiceProvider::class ];
我们还应在同一文件中添加每个提供者的“别名”,这允许使用**门面**(如果存在)。如果不存在门面,我们应在“别名”中添加类或提供者的签名,如上例中的\App\Facades\CarServiceProvider::class
。
有关门面的更多信息。
我们还可以在bootstrap.php
文件中定义我们的容器。
require __DIR__.'/../bootstrap/providers.php'; use Simpla\Container\Container; use Xtreamwayz\Pimple\Container as Pimple; global $app; /* @var $app Simpla\Container\Container */ $app = Container::instance(new Pimple); $app->createAlias($aliases); $app->registerProviders($providers);
我们可以在index.php
中加载我们的bootstrap.php
文件并使用所有可用的服务。
Defer 选项
defer(延迟)选项使Service Provider仅在需要时注册。也就是说,容器仅在需要时注册服务。
这个选项非常有用,因为它可以防止在应用程序中自动加载不必要的服务。
您可以使用容器的getDeferredServices()
命令获取“延迟”服务的列表。
为了定义将延迟加载的服务,我们必须将$defer
属性设置为true
,并在provider
方法中返回一个包含服务名称的数组。
class CalculatorServiceProvider implements ServiceProviderInterface { protected $defer = true; public function register(ContainerInterface $serviceContainer) { $calc = new \App\Classes\Calculator(); $serviceContainer->make("calc", $calc); $serviceContainer->make("calcSum", $serviceContainer->closure(function ($a, $b) { return $a + $b; }) ); } public static function providers() { return ['calc','callSum']; } }
外观
Simpla的Facades与Laravel框架的Facades非常相似。
在这个上下文中,Facades允许我们以简化的方式访问服务,就像它们是具有静态方法的类一样。这并不意味着这些服务实际上是静态的,门面创建了一种新的访问服务的方法,一种“门面”。
外观如何工作
为了使Facades起作用,我们需要定义并添加它们到我们的启动文件。
<?php $providers = [ App\Providers\CalculatorServiceProvider::class, App\Providers\CarServiceProvider::class, App\Providers\HelloServiceProvider::class ]; $aliases = [ 'calc' => App\Facades\CalculatorFacade::class, 'hello' => App\Facades\HelloFacade::class, 'car' => \App\Facades\CarServiceProvider::class ];
$aliases
变量负责存储我们的门面的别名。
对于每个服务,我们都需要创建一个外观文件,在我们的结构中,它被添加到了 App\Facades
,如下例所示:
// CalculatorFacade.php namespace App\Facades; class CalculatorFacade extends Simpla\Container\Facade { public static function createService() { return 'calc'; } }
这样我们就可以像这样调用服务 Calculator
:
use App\Facades\CalculatorFacade; calc::sum(43,21); // 63
在 createService()
方法中定义的名称和在 bootstrap 文件中定义的名称必须一致,否则将抛出 NotFoundException
。