upmind/provision-provider-base

该库包含创建配置类别和提供者类的所有基本接口、类和逻辑,并注册它们以在 Laravel 应用程序中使用。

v4.2.0 2024-08-19 09:25 UTC

README

Latest Version on Packagist

要查看该库的示例,请参阅 upmind/provision-workbench

该库包含创建配置类别和提供者类的所有基本接口、类和逻辑,并注册它们以在 Laravel 应用程序中使用。

通常此库不会单独使用,因为它将被安装为配置类别/提供者库(如 upmind/provision-provider-shared-hosting)的子依赖项。

安装

composer require upmind/provision-provider-base

Docker

该包提供了 Dockerfile 文件的示例,以支持本地开发和测试。

要使用它们,您需要在您的机器上安装 Docker 和 Docker Compose。

提供了一个 Makefile 以简化 docker-compose 文件的用法。

  • make setup-php82 设置 PHP 8.2 开发环境。
  • make static-analysis 执行静态分析。

用法

以下部分描述了如何创建配置类别和提供者类。非常简单地说,配置 类别 是一个抽象类,它声明了作为抽象公共方法的配置函数。 提供者 类扩展其父类别类,因此必须实现为类别声明的每个抽象配置函数。

此库使用自验证的数据集,下面将进行解释。

创建类别

配置类别是抽象类,必须实现 CategoryInterface。为了方便,存在一个 BaseCategory,它可以被扩展,这样所有提供者都可以访问一些有助于生成配置函数结果的常用方法。

类别类充当配置 '合同' 并负责两件关键事情

  1. 通过实现 AboutData 定义 CategoryInterface::aboutCategory(),其中包含关于类别的可读名称、描述和其他元数据。
  2. 定义构成该类别可用配置函数的抽象公共方法。这些抽象方法应通过类型提示单个 DataSet 类来定义其参数,并通过返回类型来定义 ResultData 类,如果任何给定函数需要参数或返回值。
示例类别
<?php

declare(strict_types=1);

namespace Upmind\ProvisionExample\Category\HelloWorld;

use Upmind\ProvisionBase\Provider\BaseCategory;
use Upmind\ProvisionBase\Provider\DataSet\AboutData;
use Upmind\ProvisionExample\Category\HelloWorld\Data\Greeting;
use Upmind\ProvisionExample\Category\HelloWorld\Data\PersonData;

/**
 * A simple category for 'Hello World' provider implementations. All Providers
 * of this Category must implement the 'greeting' function, using the single
 * parameter 'name'.
 */
abstract class HelloWorldCategory extends BaseCategory
{
    public static function aboutCategory(): AboutData
    {
        return AboutData::create()
            ->setName('Hello World')
            ->setDescription('A demonstration category that doesn\'t actually do anything');
    }

    /**
     * Greet the user by name.
     */
    abstract public function greeting(PersonData $person): Greeting;
}

创建提供者

供应提供者是一类必须扩展其父类别类,并且必须实现 ProviderInterface 的类。由于一个类别中的每个提供者都遵循相同的“合同”,它们将接收并必须根据在抽象类别函数中定义的 DataSet 类型/返回提示返回相同的数据。

每个提供者代表一个类别的不同实现,因此可能需要不同的数据来配置它们,例如 API 凭证等。这是通过提供者类构造函数来支持的,其中通过类型提示一个单个 DataSet 参数来定义所需的配置数据。

提供者类是供应类别的可实例化类实现,负责以下三件事

  1. 通过实现 ProviderInterface::aboutProvider(),定义包含提供者的人类可读名称、描述和其他元数据的 AboutData
  2. 可选地定义一个构造函数,该构造函数通过类型提示DataSet来“配置”(实例化)类的实例。
  3. 实现类别定义的每个供应函数
示例提供者
<?php

declare(strict_types=1);

namespace Upmind\ProvisionExample\Category\HelloWorld;

use Upmind\ProvisionBase\Provider\Contract\ProviderInterface;
use Upmind\ProvisionBase\Provider\DataSet\AboutData;
use Upmind\ProvisionExample\Category\HelloWorld\Data\FooConfiguration;
use Upmind\ProvisionExample\Category\HelloWorld\Data\PersonData;
use Upmind\ProvisionExample\Category\HelloWorld\Data\Greeting;

/**
 * This HelloWorld Provider 'Foo' provides its own implementation of the
 * 'greeting' provision function.
 */
class ProviderFoo extends HelloWorldCategory implements ProviderInterface
{
    /**
     * @var FooConfiguration
     */
    protected $configuration;

    /**
     * Providers always receive their configuration passed to their constructor.
     *
     * @param array $configuration
     */
    public function __construct(FooConfiguration $configuration)
    {
        $this->configuration = $configuration;
    }

    public static function aboutProvider(): AboutData
    {
        return AboutData::create()
            ->setName('Hello Foo')
            ->setDescription('A demonstration of a provision function success case');
    }

    public function greeting(PersonData $person): Greeting
    {
        $apiKey = $this->configuration->api_key;
        $apiSecret = $this->configuration->api_secret;

        // $this->authenticateWithSomeApi($api_key, $api_secret);
        // do something with configuration data

        return Greeting::create()
            ->setMessage('Greeting generated successfully')
            ->setSentence(sprintf('Hello %s! From your friend, Foo.', $person->name));
    }
}

注册类别和提供者

在 laravel 应用程序的 Service Provider 的 boot() 方法中注册类别和提供者。您的服务提供者应该扩展此库的 ProvisionServiceProvider,这使其能够访问 bindCategory()bindProvider() 这两个方法。在应用程序中注册的每个供应类别都必须在 bindCategory() 的第一个参数中指定一个唯一的标识符,每个提供者必须在 bindProvider() 的第二个参数中指定其类别内的唯一标识符。

示例服务提供者
<?php

declare(strict_types=1);

namespace Upmind\ProvisionExample\Category\HelloWorld\Laravel;

use Upmind\ProvisionBase\Laravel\ProvisionServiceProvider;
use Upmind\ProvisionExample\Category\HelloWorld\HelloWorldCategory;
use Upmind\ProvisionExample\Category\HelloWorld\ProviderFoo;
use Upmind\ProvisionExample\Category\HelloWorld\ProviderBar;

class ServiceProvider extends ProvisionServiceProvider
{
    /**
     * Bind the HelloWorld Category and its Providers.
     */
    public function boot()
    {
        $this->bindCategory('hello-world', HelloWorldCategory::class);

        $this->bindProvider('hello-world', 'foo', ProviderFoo::class);
        $this->bindProvider('hello-world', 'bar', ProviderBar::class);
    }
}

使用配置注册表

供应 Registry 使用反射检查和验证每个类别和提供者注册,列出它们的 AboutData 和可用的供应函数以及相应的数据集和验证规则。

使用上面的 HelloWorld 类别示例,下面的代码片段展示了如何获取“greeting”函数参数的验证规则和返回数据集

<?php

$registry = Upmind\ProvisionBase\Registry\Registry::getInstance();

$greetingRegister = $registry->getCategory('hello-world')
    ->getFunction('greeting');
// => Instance of Upmind\ProvisionBase\Registry\Data\FunctionRegister

$parameterRules = $greetingRegister->getParameter()
    ->getRules()
    ->expand();
// => Array of portable laravel validation rules:
// [
//     'name' => [
//         'required',
//         'string'
//     ]
// ]

$returnRules = $greetingRegister->getReturn()
    ->getRules()
    ->expand();
// => Array of portable laravel validation rules:
// [
//     'sentence' => [
//         'required',
//         'string'
//     ]
// ]

使用上面的 Foo 提供者示例,下面的代码片段展示了如何获取提供者构造函数数据集的验证规则

<?php

use Upmind\ProvisionBase\Registry\Registry;

$registry = Upmind\ProvisionBase\Registry\Registry::getInstance();

$fooRegister = $registry->getCategory('hello-world')
    ->getProvider('foo');
// => Instance of Upmind\ProvisionBase\Registry\Data\ProviderRegister

$fooConfigurationRules = $fooRegister->getConstructor()
    ->getParameter()
    ->getRules()
    ->expand();
// => Array of portable laravel validation rules:
// [
//     'api_key' => [
//         'required',
//         'string'
//     ],
//     'api_secret' => [
//         'required',
//         'string'
//     ]
// ]

缓存配置注册表

由于验证/枚举每个类别和提供者所需的操作相对昂贵,因此可以缓存注册表以减少开销。

如果注册表已缓存,则尝试注册新的类别/提供者将透明地推送到缓冲区,以免影响 laravel 应用程序的性能。

在缓存注册表时,缓冲注册表将在返回注册表的序列化形式之前进行评估、验证和枚举。

以下是如何在 laravel 应用程序中缓存注册表的示例,这可以作为一个 artisan 命令来实现

use Illuminate\Support\Facades\Cache;
use Upmind\ProvisionBase\Registry\Registry;

$cacheKey = 'upmind-provision-registry';
$serializedRegistry = serialize(Registry::getInstance());

Cache::forever($cacheKey, $serializedRegistry);

以下是如何从缓存中解析注册表并将其绑定到 laravel 服务提供者中的单例的示例

<?php

declare(strict_types=1);

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Cache;
use Upmind\ProvisionBase\Registry\Registry;

/**
 * Bind the Provision Registry to the container
 */
class ProvisionRegistryServiceProvider extends ServiceProvider
{
    /**
     * @var string
     */
    public const REGISTRY_CACHE_KEY = 'upmind-provision-registry';

    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Load the Provision Registry from cache, or obtain a fresh instance and bind
     * the Registry to the container.
     *
     * @return void
     */
    public function register()
    {
        // Attempt to set the Registry instance from cache
        if ($cachedRegistry = Cache::get(self::REGISTRY_CACHE_KEY)) {
            $registry = unserialize($cachedRegistry);

            if ($registry instanceof Registry) {
                Registry::setInstance($registry);
            }
        }

        // Bind registry as singleton to container
        $this->app->singleton(Registry::class, function () {
            return Registry::getInstance();
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [Registry::class];
    }
}

执行供应函数

提供了一些便利类,以便简化提供者对象的实例化,并通过提供规范化的供应 Result 对象来执行供应函数。

提供了一个用于创建FactoryProvider实例包装器,该包装器可以创建一个ProviderJob对象,该对象封装了配置函数的执行。

以下示例展示了Foo提供者

<?php

use Upmind\ProvisionBase\ProviderFactory;
use Upmind\ProvisionBase\Registry\Registry;

$fooConfiguration = [
    'api_key' => 'foo api key here',
    'api_secret' => 'foo api secret here',
];

$factory = new ProviderFactory(Registry::getInstance());
$foo = $factory->create('hello-world', 'foo', $fooConfiguration);
// => Instance of Upmind\ProvisionBase\Provider

$greetingParameters = [
    'name' => 'Harry',
];
$greeting = $foo->makeJob('greeting', $greetingParameters);
// => Instance of Upmind\ProvisionBase\ProviderJob

$greetingResult = $greeting->execute();
// => Instance of Upmind\ProvisionBase\Result\ProviderResult

$data = $greetingResult->getData();
// Greeting result data:
// [
//     'sentence' => 'Hello Harry! From your friend, Foo.'
// ]

数据集

配置函数的参数和返回值,以及配置提供者设置值,都包含在自我验证的数据集对象中。

这些类扩展了基础DataSet类,并且只需简单地实现抽象的公共静态rules()方法,返回一个Rules对象。

在从数据集返回任何数据之前,数据集会自我验证,从而确保任何给定的DataSet实例始终包含有效的数据。如果数据集在尝试从中获取任何值时包含无效数据,将抛出InvalidDataSetException,其中包含通常的Laravel格式的验证错误。

在您的提供者代码中,不需要自行调用数据集上的->validate(),因为这将在ProviderFactory和ProviderJob对象中完成,在数据集被喂入您的提供者类/代码之前。

以下示例定义了PersonData,它包含HelloWorld::greeting()配置函数的参数。可以使用相同的方法来定义提供者配置数据结构。

示例参数数据集
<?php

declare(strict_types=1);

namespace Upmind\ProvisionExample\Category\HelloWorld\Data;

use Upmind\ProvisionBase\Provider\DataSet\DataSet;
use Upmind\ProvisionBase\Provider\DataSet\Rules;

/**
 * Data set encapsulating a person.
 *
 * @property-read string $name Name of the person
 */
class PersonData extends DataSet
{
    public static function rules(): Rules
    {
        return new Rules([
            'name' => ['required', 'string'],
        ]);
    }
}

对于配置函数返回数据,有一个可扩展的替代基础ResultData类,它提供了方便的方法来可选地设置Result成功消息和/或调试数据,您可以在上面的Foo提供者示例中看到这些方法的作用。

以下示例定义了Greeting,它包含HelloWorld::greeting()配置函数的返回数据

示例返回数据集
<?php

declare(strict_types=1);

namespace Upmind\ProvisionExample\Category\HelloWorld\Data;

use Upmind\ProvisionBase\Provider\DataSet\ResultData;
use Upmind\ProvisionBase\Provider\DataSet\Rules;

/**
 * Data set encapsulating a greeting.
 *
 * @property-read string $sentence Greeting sentence
 */
class Greeting extends ResultData
{
    public static function rules(): Rules
    {
        return new Rules([
            'sentence' => ['required', 'string'],
        ]);
    }

    public function setSentence(string $sentence): self
    {
        $this->setValue('sentence', $sentence);
        return $this;
    }
}

变更日志

有关最近更改的更多信息,请参阅CHANGELOG

贡献

有关详细信息,请参阅CONTRIBUTING

鸣谢

许可

GNU通用公共许可证版本3(GPLv3)。有关更多信息,请参阅许可证文件

Upmind

使用Upmind.com - 终极网络托管计费和管理解决方案,销售、管理和支持网络托管、域名、SSL证书、网站构建器等。