【问题标题】:Symfony multiple command classes with same dependencies: Avoid code repetitionSymfony 具有相同依赖项的多个命令类:避免代码重复
【发布时间】:2019-02-17 19:23:12
【问题描述】:

在 Symfony 4 项目中,我有两个具有相同依赖关系的命令(可能还会有更多命令)。目前,我的两个命令的代码中有一些代码重复,我知道我应该能够使其更加干燥和高效,但不确定我必须如何组织它。

这就是我目前拥有它们的方式:

# config/services.yaml

parameters:
    api_client_id: '%env(API_CLIENT_ID)%'
    api_client_secret: '%env(API_CLIENT_SECRET)%'
    api_client_id_sandbox: '%env(API_CLIENT_ID_SANDBOX)%'
    api_client_secret_sandbox: '%env(API_CLIENT_SECRET_SANDBOX)%'
    api_env: '%env(API_ENV)%'

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones


    # Registering 3rd Party API Manager.
    app.api_service_factory:
        class: App\Service\APIServiceFactory
        arguments:
            - "%api_client_id%"
            - "%api_client_secret%"
            - "%api_client_id_sandbox%"
            - "%api_client_secret_sandbox%"
            - "%api_env%"  


    # Collect promotions command.
    App\Command\CollectPromotionsCommand:
        arguments:
            - "@app.api_service_factory"
        tags:
            - { name: 'console.command', command: 'app:collect-promotions' }


    # Processes the reserved topups orders.
    App\Command\ProcessReservedTopupsCommand:
        arguments:
            - "@app.api_service_factory"
        tags:
            - { name: 'console.command', command: 'app:process-reserved-topups' }

<?php
// src/Command/CollectPromotionsCommand.php

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use App\Service\APIServiceFactory;
use Doctrine\ORM\EntityManagerInterface;

class CollectPromotionsCommand extends Command
{

  /**
   * @var APIServiceFactory
   */
  protected $apiServiceFactory;

  /**
   * @var EntityManagerInterface
   */
  protected $em;

  protected static $defaultName = 'app:collect-promotions';

  public function __construct(APIServiceFactory $apiServiceFactory, EntityManagerInterface $em) {
    $this->apiServiceFactory  = $apiServiceFactory;
    $this->em                         = $em;

    parent::__construct();
  }

// ...


}

<?php
// src/Command/ProcessReservedTopupsCommand.php

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use App\Service\APIServiceFactory;
use Doctrine\ORM\EntityManagerInterface;

class ProcessReservedTopupsCommand extends Command
{

  /**
   * @var APIServiceFactory
   */
  protected $apiServiceFactory;

  /**
   * @var EntityManagerInterface
   */
  protected $em;

  protected static $defaultName = 'app:collect-promotions';

  public function __construct(APIServiceFactory $apiServiceFactory, EntityManagerInterface $em) {
    $this->apiServiceFactory  = $apiServiceFactory;
    $this->em                         = $em;

    parent::__construct();
  }

// ...


}

非常感谢任何使此代码看起来更体面的帮助。

【问题讨论】:

  • 您可以拥有一个继承自 Command 的父类并执行共享/初始化操作,然后您的子命令将只执行特定逻辑
  • 我认为您可能对 DRY 的概念有点过分了。两个具有相同构造函数参数的类几乎不用担心。如果有额外的共享功能,那么也许可以将其提取为特征。

标签: php symfony dependency-injection symfony4 dry


【解决方案1】:

我个人不会这样做,因为我更喜欢清晰,即使它意味着一些重复。

但是,如果您想知道如何避免服务的构造函数和配置中的代码重复,那么答案是:https://symfony.com/doc/current/service_container/parent_services.html。它是关于为您的命令引入一个基类。 Symfony 还可以让您避免重复配置。

您的示例将如下所示:

class BaseCommand extends Command
{
  /**
   * @var APIServiceFactory
   */
  protected $apiServiceFactory;

  /**
   * @var EntityManagerInterface
   */
  protected $em;

  public function __construct(APIServiceFactory $apiServiceFactory, EntityManagerInterface $em) {
    $this->apiServiceFactory  = $apiServiceFactory;
    $this->em = $em;

    parent::__construct();
  }

  // ...
}

class CollectPromotionsCommand extends BaseCommand
{
    // ...
}

class ProcessReservedTopupsCommand extends BaseCommand
{
    // ...
}

配置:

# Base API command depending on API.
App\Command\BaseCommand:
    abstract: true
    arguments:
        - "@app.api_service_factory"

# Collect promotions command.
App\Command\CollectPromotionsCommand:
    parent: App\Command\BaseCommand
    tags:
        - { name: 'console.command', command: 'app:collect-promotions' }


# Processes the reserved topups orders.
App\Command\ProcessReservedTopupsCommand:
    parent: App\Command\BaseCommand
    tags:
        - { name: 'console.command', command: 'app:process-reserved-topups' }

【讨论】:

  • 我之前完全按照您的编码尝试过这种父服务方法,似乎是做我所寻找的正确方法,但是当我触发我的命令时,我收到了这个警告:`警告:声明App\Command\ProcessReservedTopupsCommand::execute(App\Command\InputInterface $input, App\Command\OutputInterface $output) 应该与 Symfony\Component\Console\Command\Command::execute(Symfony\Component\Console\Input 兼容\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output) `知道为什么或者我错过了其他东西吗?
  • 您忘记在您的ProcessReservedTopupsCommand 中添加use Symfony\Component\Console\Input\InputInterface;Symfony\Component\Console\Input\InputInterface
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-20
  • 2019-02-26
  • 2017-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多