服务

简介

什么是服务?

CodeIgniter 4 中的服务提供了创建和共享新类实例的功能。它作为 Config\Services 类实现。

CodeIgniter 中的所有核心类都作为“服务”提供。这仅仅意味着,与其硬编码要加载的类名,不如在非常简单的配置文件中定义要调用的类。此文件充当创建所需类的新实例的工厂。

为什么要使用服务?

一个简单的例子可能会让事情更清楚,所以假设您需要拉取 Timer 类的实例。最简单的方法就是创建一个该类的新的实例

<?php

$timer = new \CodeIgniter\Debug\Timer();

这很好用。直到你决定要使用不同的计时器类来代替它。也许这个计时器类有一些默认计时器没有提供的先进报告功能。为了做到这一点,你现在必须找到应用程序中所有使用计时器类的位置。由于你可能为了保持应用程序性能日志的持续运行而将它们保留在原位,这可能是一种耗时且容易出错的方式来处理这个问题。这就是服务派上用场的地方。

我们不再自己创建实例,而是让一个中央类为我们创建该类的实例。这个类保持非常简单。它只包含一个方法,用于我们想要用作服务的每个类。该方法通常返回该类的**共享实例**,并将它可能具有的任何依赖项传递到其中。然后,我们将用调用这个新类的代码替换我们的计时器创建代码。

<?php

$timer = \Config\Services::timer();

当你需要更改使用的实现时,你可以修改服务配置文件,更改会自动在整个应用程序中发生,而无需你做任何事情。现在你只需要利用任何新功能,你就可以开始了。非常简单且不易出错。

注意

建议只在控制器中创建服务。其他文件,如模型和库,应该将依赖项传递到构造函数中或通过 setter 方法传递。

如何获取服务

由于许多 CodeIgniter 类作为服务提供,你可以像下面这样获取它们。

<?php

$typography = \Config\Services::typography();

$typography 是 Typography 类的实例,如果你再次调用 \Config\Services::typography(),你将获得完全相同的实例。

服务通常返回类的**共享实例**。以下代码在第一次调用时创建一个 CURLRequest 实例。第二次调用返回完全相同的实例。

<?php

$options1 = [
    'baseURI' => 'http://example.com/api/v1/',
    'timeout' => 3,
];
$client1 = \Config\Services::curlrequest($options1);

$options2 = [
    'baseURI' => 'http://another.example.com/api/v2/',
    'timeout' => 10,
];
$client2 = \Config\Services::curlrequest($options2);
// $options2 does not work.
// $client2 is the exactly same instance as $client1.

因此,$client2 的参数 $options2 不起作用。它被忽略了。

获取新实例

如果你想获取 Typography 类的新的实例,你需要将 false 传递给参数 $getShared

<?php

$typography = \Config\Services::typography(false);

便捷函数

提供了两个函数用于获取服务。这些函数始终可用。

service()

第一个是 service(),它返回请求服务的新的实例。唯一需要的参数是服务名称。这与 Services 文件中的方法名称相同,始终返回类的共享实例,因此多次调用该函数始终应该返回相同的实例

<?php

$logger = service('logger');

// The code above is the same as the code below.
$logger = \Config\Services::logger();

如果创建方法需要额外的参数,可以在服务名称之后传递它们

<?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 模块**,其中包含控制器、模型等,您希望将其中一些类作为服务提供。第一步是创建一个新文件:**Blog/Config/Services.php**。该文件的骨架应该是

<?php

namespace Blog\Config;

use CodeIgniter\Config\BaseService;

class Services extends BaseService
{
    public static function postManager()
    {
        // ...
    }
}

现在您可以像上面描述的那样使用此文件。当您想从任何控制器中获取帖子服务时,您只需使用框架的 Config\Services 类来获取您的服务

<?php

$postManager = \Config\Services::postManager();

注意

如果多个 Services 文件具有相同的函数名,则找到的第一个文件将是返回的实例。