服务
简介
什么是服务?
CodeIgniter 4 中的 服务 提供了创建和共享新类实例的功能。它通过 Config\Services 类实现。
CodeIgniter 内部的所有核心类都以“服务”形式提供。这仅仅意味着,与硬编码要加载的类名不同,需要调用的类被定义在一个非常简单的配置文件中。该文件充当一种工厂,用于创建所需类的新实例。
为什么要使用服务?
一个简单的例子可能更容易理解。假设你需要引入一个 Timer 类的实例。最直接的方法就是创建该类的一个新实例:
<?php
$timer = new \CodeIgniter\Debug\Timer();
这非常有效。直到你决定要用一个不同的计时器类来替代它。也许这个类提供了一些默认计时器不具备的高级功能。为了实现这一点,你现在必须找到应用程序中所有使用了计时器类的地方。如果你为了持续记录应用程序性能而保留了这些代码,那么这种方式会非常耗时且容易出错。服务正是为了解决这个问题而设计的。
我们不再自己创建实例,而是让一个中心类来为我们创建该类的实例。这个类非常简单,它只为每个我们想作为服务使用的类包含一个方法。该方法通常返回该类的一个 共享实例,并将任何可能的依赖项传递给它。然后,我们将计时器创建代码替换为调用这个全局函数或 Services 类的代码:
<?php
$timer = service('timer');
// The code above is the same as the code below.
$timer = \Config\Services::timer();
当你需要更改所使用的实现时,可以修改服务配置文件,更改会自动在整个应用程序中生效,而无需你做任何额外操作。现在你只需利用任何新功能即可。这种方式非常简单且不易出错。
备注
建议仅在控制器中创建服务。其他文件,如模型和库,应通过构造函数或 setter 方法传入依赖项。
如何获取服务
由于许多 CodeIgniter 类都以服务形式提供,你可以像下面这样获取它们:
<?php
$timer = service('timer');
$timer 是 Timer 类的一个实例,如果你再次调用 service('timer'),你将得到完全相同的实例。
服务通常返回该类的一个 共享实例。以下代码在第一次调用时创建一个 CURLRequest 实例,第二次调用则返回完全相同的实例。
<?php
$options1 = [
'baseURI' => 'http://example.com/api/v1/',
'timeout' => 3,
];
$client1 = service('curlrequest', $options1);
$options2 = [
'baseURI' => 'http://another.example.com/api/v2/',
'timeout' => 10,
];
$client2 = service('curlrequest', $options2);
// $options2 does not work.
// $client2 is the exactly same instance as $client1.
因此,$client2 的参数 $options2 不会生效,它会被忽略。
获取新实例
如果你想获取 Timer 类的一个新实例,需要将参数 $getShared 设为 false:
<?php
$timer = \Config\Services::timer(false);
便捷函数
提供了两个用于获取服务的函数,这些函数始终可用。
service()
第一个是 service(),它返回所请求服务的一个实例。唯一必需的参数是服务名称。这与 Services 文件中的方法相同,始终返回该类的一个 共享实例,因此多次调用该函数应始终返回相同的实例:
<?php
$logger = service('logger');
// The code above is the same as the code below.
$logger = \Config\Services::logger();
备注
自 v4.5.0 起,当你不向服务传递参数时,由于性能提升,推荐使用全局函数 service()。
如果创建方法需要额外的参数,可以在服务名称后传递它们:
<?php
$renderer = service('renderer', APPPATH . 'views/');
// The code above is the same as the code below.
$renderer = \Config\Services::renderer(APPPATH . 'views/');
single_service()
第二个函数 single_service() 的工作方式与 service() 类似,但返回一个新实例:
<?php
$logger = single_service('logger');
// The code above is the same as the code below.
$logger = \Config\Services::logger(false);
定义服务
为了使服务正常工作,你必须能够依赖每个类都具有一个稳定的 API 或 接口。CodeIgniter 的几乎所有类都提供了一个它们遵循的接口。当你想要扩展或替换核心类时,你只需确保满足该接口的要求,就能知道这些类是兼容的。
例如,RouteCollection 类实现了 RouteCollectionInterface。当你想要创建一个提供不同路由创建方式的替代类时,你只需创建一个实现 RouteCollectionInterface 的新类:
<?php
namespace App\Router;
use CodeIgniter\Router\RouteCollectionInterface;
class MyRouteCollection implements RouteCollectionInterface
{
// Implement required methods here.
}
最后,将 routes() 方法添加到 app/Config/Services.php 中,以创建 MyRouteCollection 的新实例,而非 CodeIgniter\Router\RouteCollection:
<?php
namespace Config;
use CodeIgniter\Config\BaseService;
class Services extends BaseService
{
// ...
public static function routes()
{
return new \App\Router\MyRouteCollection(static::locator(), config('Modules'));
}
}
允许传入参数
在某些情况下,你可能希望在实例化类时传入一个设置。由于服务文件是一个非常简单的类,实现这一点很容易。
一个很好的例子是 renderer 服务。默认情况下,我们希望该类能在 APPPATH . 'views/' 找到视图。但我们希望开发者能够根据需要更改该路径。因此,该类接受 $viewPath 作为构造函数参数。服务方法如下所示:
<?php
namespace Config;
use CodeIgniter\Config\BaseService;
class Services extends BaseService
{
// ...
public static function renderer($viewPath = APPPATH . 'views/')
{
return new \CodeIgniter\View\View($viewPath);
}
}
这在构造函数方法中设置了默认路径,但也允许轻松更改所使用的路径:
<?php
$renderer = \Config\Services::renderer('/shared/views/');
共享类
有时你需要确保只创建一个服务的实例。这可以通过工厂方法内部调用的 getSharedInstance() 方法轻松处理。该方法会检查该实例是否已在服务类中被创建并保存,如果没有,则创建一个新实例。所有的工厂方法都提供 $getShared = true 作为最后一个参数。你也应该遵循这种方法:
<?php
namespace Config;
use CodeIgniter\Config\BaseService;
class Services extends BaseService
{
// ...
public static function routes($getShared = true)
{
if ($getShared) {
return static::getSharedInstance('routes');
}
return new \App\Router\MyRouteCollection(static::locator(), config('Modules'));
}
}
服务发现
CodeIgniter 可以自动发现你在任何已定义的命名空间内创建的 Config/Services.php 文件。这使得可以轻松地使用任何模块的服务文件。为了使自定义服务文件被发现,它们必须满足以下要求:
其命名空间必须在 app/Config/Autoload.php 中定义
在命名空间内,该文件必须位于 Config/Services.php
它必须继承
CodeIgniter\Config\BaseService
一个小例子可以阐明这一点。
假设你在项目根目录下创建了一个名为 Blog 的新目录。这将包含一个带有控制器、模型等的 博客模块,并且你希望将其中的一些类作为服务提供。第一步是创建一个新文件:Blog/Config/Services.php。该文件的骨架如下:
<?php
namespace Blog\Config;
use CodeIgniter\Config\BaseService;
class Services extends BaseService
{
public static function postManager()
{
// ...
}
}
现在你可以像上面描述的那样使用这个文件。当你想从任何控制器获取文章服务时,只需使用框架的 Config\Services 类来获取你的服务:
<?php
$postManager = service('postManager');
备注
如果多个服务文件具有相同的方法名,将返回找到的第一个实例。
重置服务缓存
在 4.6.0 版本加入.
当在框架初始化过程早期首次调用 Services 类时,通过自动发现找到的服务类会被缓存在一个类属性中,并且不会被更新。
如果稍后动态加载了模块,并且这些模块中有服务,那么必须更新缓存。
这可以通过运行 Config\Services::resetServicesCache() 来实现。这将清除缓存,并在需要时强制重新进行服务发现。