在Symfony的第三方捆绑中即时创建服务
#php #symfony #bundle

在开发符合聚会的束缚时,我们可能需要根据某些条件创建服务。在这种情况下,我们必须使用我们的捆绑扩展类类实现它,而不是在服务文件上定义服务。

让我们看看一个简单的示例。想象一下,我们正在创建一个可以根据其配置的多种方式连接到Redis的捆绑包:

  • 案例1 :捆绑配置定义主机和端口
  • 案例2 :捆绑配置定义URI Connection
  • 案例3 :捆绑配置定义了redis袜子路径

按照我们刚刚定义的情况,我们将创建一项服务,该服务将连接到REDIS,将其考虑在内。我们的服务看起来像这样:

class RedisWrapper {

    public function __construct(
        public readonly Predis\Client $client
    ){ }

    // .......
}

配置类

配置类使用 symfony \ component \ config \ definition \ builder \ treebuilder 类定义捆绑配置参数。它保留在de depentencyIndoction 文件夹中,该文件夹必须位于您的束根dir中。

让我们看看它的外观:


use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class MyBundleConfiguration implements ConfigurationInterface
{

    public function getConfigTreeBuilder(): TreeBuilder
    {
        $tb = new TreeBuilder('ict_api_one_endpoint');

        $tb
            ->getRootNode()
            ->children()
            ->scalarNode('host')
                ->defaultNull()
            ->end()
            ->scalarNode('port')
                ->defaultValue(6379)
            ->end()
            ->scalarNode('uri')
                ->defaultNull()
            ->end()
            ->scalarNode('sock_path')
                ->defaultNull()
            ->end()
        ;
        return $tb;
   }
}

方法 getConfigtreeBuilder 将捆绑配置返回为 treebuilder 对象。如我们所见,所有参数都是可选的。每种情况的配置将为以下:

情况1

my_bundle_name:
   host: 195.230.65.145
   port: 6379

案例2

my_bundle_name:
   uri: 'tcp://195.230.65.145:6379'

案例3

my_bundle_name:
   sock_path: '/path/to/my.sock'

您可以探索有关配置的更多信息here

现在,是时候根据最后可能的配置来创建服务。

扩展文件

扩展文件将加载捆绑到安装项目的服务和参数。作为配置类,扩展类也保留在 depentencyInjection 文件夹中。

通常,它首先使用Bundle Services文件加载容器(通常位于 Resources/config 文件夹下),然后根据配置的参数值加载配置。

让我们看看它的外观

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;

class IctApiOneEndpointExtension extends Extension
{

    /**
     * @throws \Exception
     */
    public function load(array $configs, ContainerBuilder $container): void
    {
        $loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));
        $loader->load('services.xml');

        $configuration = new IctApiOneEndpointConfiguration();
        $config = $this->processConfiguration($configuration, $configs);

        if(!empty($config['host']) && !empty($config['port'])) {
            $container
                ->register('my.redis_client', Predis\Client::class)
                ->addArgument(['scheme' => 'tcp', 'host' => $config['host'], 'port' => $config['port']])
            ;
        }
        else if(!empty($config['uri'])) {
            $container
                ->register('my.redis_client', Predis\Client::class)
                ->addArgument($config['uri'])
            ;
        }
        else{
            if(empty($config['sock_path'])) {
                throw new \RuntimeException('Missing arguments for loading bundle');
            }

            $container
                ->register('my.redis_client', Predis\Client::class)
                ->addArgument(['scheme' => 'unix', 'host' => $config['sock_path']])
            ;
        }

        $container
            ->register('my.redis_wrapper', RedisWrapper::class)
            ->addArgument(new Reference('my.redis_client'))
        ;

    }
}

让我们逐步探索扩展代码:

$loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));
$loader->load('services.xml');

$configuration = new IctApiOneEndpointConfiguration();
$config = $this->processConfiguration($configuration, $configs);

这是所有扩展类通常开始的。它只需从XML或YAML服务文件加载容器即可。然后,它从项目安装程序配置的值处理捆绑配置。

if(!empty($config['host']) && !empty($config['port'])) {
   $container
      ->register('my.redis_client', Predis\Client::class)
      ->addArgument(['scheme' => 'tcp', 'host' => $config['host'], 'port' => $config['port']])
   ;
}

如果 host port 参数不是空的,它会从 predis \ client \ client 类中登录服务,以:

  • 方案:通常TCP
  • 主机:主机配置参数的值
  • 端口:端口配置参数的值
else if(!empty($config['uri'])) {
    $container
       ->register('my.redis_client', Predis\Client::class)
       ->addArgument($config['uri'])
    ;
}

如果 uri 参数不是空的,则我们注册相同的服务,但作为参数通过REDIS URI连接。

else{
    if(empty($config['sock_path'])) {
        throw new \RuntimeException('Missing arguments for loading bundle');
    }

    $container
       ->register('my.redis_client', Predis\Client::class)
       ->addArgument(['scheme' => 'unix', 'host' => $config['sock_path']])
    ;
}

作为最后一个选项,如果 sock_path 参数为空A \ runtimeTimeExeption ,因为没有更多选项登记连接。否则,我们注册服务,但通过以下方式将其作为参数作为参数。

  • 方案:unix
  • 主机 sock_path
$container
    ->register('my.redis_wrapper', RedisWrapper::class)
    ->addArgument(new Reference('my.redis_client'))
;

最后,我们注册 rediswrapper 服务添加为参数a 参考 my.redis_client

因此,我们允许开发人员配置此捆绑包,然后给出3个选项:

  • 使用主机和端口
  • 使用URI连接
  • 使用袜子路径

如果您阅读了predis docs,您将在“ 连接到redis ”部分上看到 predis \ client \ client class可以作为参数接受我们刚刚看到的三种情况。这篇文章。

您可以在我的secrets bundle

中看到类似的情况