具有相同依赖关系的 Symfony 多命令 类:避免代码重复
Symfony multiple command classes with same dependencies: Avoid code repetition
在 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();
}
// ...
}
非常感谢任何使此代码看起来更体面的帮助。
我个人不会这样做,因为我更喜欢清晰明了,即使这意味着有点重复。
但是如果您想知道如何避免服务的构造函数和配置中的代码重复,答案是:https://symfony.com/doc/current/service_container/parent_services.html。它是关于为您的命令引入一个基础class。 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' }
在 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();
}
// ...
}
非常感谢任何使此代码看起来更体面的帮助。
我个人不会这样做,因为我更喜欢清晰明了,即使这意味着有点重复。
但是如果您想知道如何避免服务的构造函数和配置中的代码重复,答案是:https://symfony.com/doc/current/service_container/parent_services.html。它是关于为您的命令引入一个基础class。 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' }