如何为带构造函数参数的服务创建工厂及员工导入验证DI问题
1. 如何为带有构造函数参数的服务创建工厂?
咱们以Symfony场景为例(毕竟你提到了EntityManager,应该是用Doctrine的开发环境),如果你的服务构造函数需要自定义参数(比如除了框架自带的服务,还要传入自己的配置项),工厂模式是个很靠谱的解决方案。
咱们一步步来实现:
首先,假设你有个需要构造函数参数的员工验证服务:
namespace App\Service; use Doctrine\ORM\EntityManagerInterface; class EmployeeValidatorService { // 构造函数需要EntityManager和自定义的验证规则数组 public function __construct( private EntityManagerInterface $em, private array $validationRules ) {} // 这里编写你的验证逻辑 public function validate(array $employeeData): void { foreach ($this->validationRules as $rule) { match ($rule) { 'name_cannot_be_empty' => $this->validateName($employeeData), 'email_must_be_valid' => $this->validateEmail($employeeData), // 其他规则... }; } } private function validateName(array $data): void { if (trim($data['name']) === '') { throw new \InvalidArgumentException('员工姓名不能为空'); } } private function validateEmail(array $data): void { if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException('邮箱格式无效'); } } }
接下来创建工厂类,专门负责实例化这个服务:
namespace App\Factory; use App\Service\EmployeeValidatorService; use Doctrine\ORM\EntityManagerInterface; class EmployeeValidatorServiceFactory { // 工厂本身依赖EntityManager,通过构造函数注入 public function __construct(private EntityManagerInterface $em) {} // 对外提供创建服务的方法,接收自定义参数 public function create(array $validationRules): EmployeeValidatorService { return new EmployeeValidatorService($this->em, $validationRules); } }
如果用Symfony容器管理服务,在config/services.yaml里配置一下,让容器知道用工厂来创建服务:
services: # 先注册工厂类,容器会自动注入它需要的EntityManager App\Factory\EmployeeValidatorServiceFactory: arguments: $em: '@doctrine.orm.default_entity_manager' # 注册你的验证服务,指定工厂和创建方法 App\Service\EmployeeValidatorService: factory: ['@App\Factory\EmployeeValidatorServiceFactory', 'create'] arguments: $validationRules: - 'name_cannot_be_empty' - 'email_must_be_valid'
要是你不想用容器,直接手动实例化工厂再创建服务就行:
// 比如在命令行或者控制器里 $em = $this->getDoctrine()->getManager(); $factory = new EmployeeValidatorServiceFactory($em); $validator = $factory->create([ 'name_cannot_be_empty', 'email_must_be_valid' ]);
这样就能灵活地给服务传递构造函数参数啦!
2. 导入员工数据时EntityManager为空的问题
你说不想用容器,想用依赖注入,但$this->em里没有EntityManager,大概率是因为你没有手动传递依赖,或者依赖注入的方式不对。
首先要明确:不用容器的话,依赖注入不是自动完成的,得你自己把需要的依赖传给服务。这里推荐用构造函数注入,比setter注入更可靠,能避免忘记传依赖的情况。
咱们改一下你的员工处理服务,用构造函数注入EntityManager:
namespace App\Service; use Doctrine\ORM\EntityManagerInterface; use App\Entity\Employee; class EmployeeImporterService { private EntityManagerInterface $em; // 构造函数强制要求传入EntityManager,这样实例化时就必须给,不会为空 public function __construct(EntityManagerInterface $em) { $this->em = $em; } public function processSingleEmployee(array $employeeData): void { // 先执行验证 $this->validateEmployeeData($employeeData); // 创建或更新员工实体 $employee = $this->em->getRepository(Employee::class) ->findOneBy(['name' => $employeeData['name']]) ?? new Employee(); $employee->setName($employeeData['name']); $employee->setEmail($employeeData['email']); // 其他字段赋值... // 持久化到数据库 $this->em->persist($employee); $this->em->flush(); } private function validateEmployeeData(array $data): void { // 这里编写你的各种验证逻辑 if (empty($data['name'])) { throw new \InvalidArgumentException('员工姓名不能为空'); } if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException('邮箱格式错误'); } // 更多验证... } }
然后当你要使用这个服务时,手动把EntityManager传进去就行:
// 比如在控制台命令里(假设你用Symfony Console) use App\Service\EmployeeImporterService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class ImportEmployeesCommand extends Command { private EntityManagerInterface $em; // 命令本身通过构造函数注入EntityManager public function __construct(EntityManagerInterface $em) { parent::__construct(); $this->em = $em; } protected function configure(): void { $this->setName('app:import-employees') ->addArgument('file', null, '员工数据文件路径'); } protected function execute(InputInterface $input, OutputInterface $output): int { // 加载文件里的员工数据(这里以CSV为例,你可以换成JSON等格式) $employeeDataList = $this->loadCsvData($input->getArgument('file')); // 手动实例化服务,把EntityManager传进去 $importer = new EmployeeImporterService($this->em); foreach ($employeeDataList as $data) { try { $importer->processSingleEmployee($data); $output->writeln("<info>成功处理员工:{$data['name']}</info>"); } catch (\Exception $e) { $output->writeln("<error>处理员工{$data['name']}失败:{$e->getMessage()}</error>"); } } return Command::SUCCESS; } private function loadCsvData(string $filePath): array { $data = []; if (($handle = fopen($filePath, 'r')) !== false) { $header = fgetcsv($handle); while (($row = fgetcsv($handle)) !== false) { $data[] = array_combine($header, $row); } fclose($handle); } return $data; } }
要是你之前用的是setter注入(比如setEntityManager方法),那必须手动调用这个setter才能给$this->em赋值:
$importer = new EmployeeImporterService(); $importer->setEntityManager($this->em); // 这一步绝对不能忘!
但还是强烈推荐构造函数注入,这样依赖关系一目了然,也不会因为忘记调用setter导致EntityManager为空的问题。
内容的提问来源于stack exchange,提问作者Eduardo Fernandes
相关产品推荐
相关产品推荐

