在Zend Expressive 3中使用AbstractPluginManager的示例求助
实现AbstractPluginManager + Zend Expressive 3动态注入接口实现类
我完全懂你的需求——要把所有实现特定通用接口的类实例,通过AbstractPluginManager动态注入到目标服务里,这在Zend生态里确实是标准解决方案。下面给你一个完整的可运行示例,一步步拆解:
1. 定义通用接口
首先我们先定义一个所有插件都要实现的接口,比如MyPluginInterface:
// src/Plugin/MyPluginInterface.php namespace App\Plugin; interface MyPluginInterface { public function getName(): string; public function execute(): string; }
2. 编写实现接口的插件类
接下来创建几个示例插件,都实现上面的接口:
// src/Plugin/FooPlugin.php namespace App\Plugin; class FooPlugin implements MyPluginInterface { public function getName(): string { return 'foo-plugin'; } public function execute(): string { return 'Foo plugin executed!'; } }
// src/Plugin/BarPlugin.php namespace App\Plugin; class BarPlugin implements MyPluginInterface { public function getName(): string { return 'bar-plugin'; } public function execute(): string { return 'Bar plugin executed!'; } }
3. 创建自定义PluginManager
现在创建继承AbstractPluginManager的自定义管理器,用来管控所有实现MyPluginInterface的插件:
// src/Plugin/MyPluginManager.php namespace App\Plugin; use Zend\ServiceManager\AbstractPluginManager; use Zend\ServiceManager\Exception\InvalidServiceException; class MyPluginManager extends AbstractPluginManager { // 限制这个管理器只能管理实现MyPluginInterface的类 protected $instanceOf = MyPluginInterface::class; /** * 验证服务是否符合要求(可选,但推荐) * @param mixed $instance * @throws InvalidServiceException */ public function validate($instance) { if (!$instance instanceof $this->instanceOf) { throw new InvalidServiceException(sprintf( '插件必须实现 %s 接口,当前传入的是 %s', $this->instanceOf, get_class($instance) )); } } }
4. 在DI容器中注册PluginManager和插件
打开config/dependencies.php,注册我们的插件管理器,同时把所有插件注册进去:
// config/dependencies.php return [ 'dependencies' => [ 'factories' => [ // 注册自定义PluginManager App\Plugin\MyPluginManager::class => Zend\ServiceManager\Factory\InvokableFactory::class, // 注册需要注入插件的服务 App\Service\MyPluginConsumer::class => App\Service\Factory\MyPluginConsumerFactory::class, ], // 重点:给MyPluginManager配置它的服务(也就是我们的插件) App\Plugin\MyPluginManager::class => [ 'factories' => [ App\Plugin\FooPlugin::class => Zend\ServiceManager\Factory\InvokableFactory::class, App\Plugin\BarPlugin::class => Zend\ServiceManager\Factory\InvokableFactory::class, // 可选:给插件加别名,方便调用 'foo' => App\Plugin\FooPlugin::class, 'bar' => App\Plugin\BarPlugin::class, ], ], ], ];
如果插件数量多,不想手动逐个注册,可以用自动发现:创建一个ConfigProvider类,扫描指定目录下的插件类并自动注册:
// src/Plugin/ConfigProvider.php namespace App\Plugin; class ConfigProvider { public function __invoke(): array { return [ 'dependencies' => $this->getDependencies(), ]; } public function getDependencies(): array { // 扫描src/Plugin目录下所有实现MyPluginInterface的类 $plugins = []; $directory = __DIR__; $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory)); foreach ($iterator as $file) { if (!$file->isFile() || $file->getExtension() !== 'php') { continue; } $className = 'App\\Plugin\\' . $file->getBasename('.php'); if (is_subclass_of($className, MyPluginInterface::class)) { $plugins['factories'][$className] = \Zend\ServiceManager\Factory\InvokableFactory::class; } } return [ App\Plugin\MyPluginManager::class => $plugins, ]; } }
然后把这个ConfigProvider添加到config/config.php的config_providers数组里即可。
5. 创建消费插件的服务
现在创建一个服务,它会接收MyPluginManager,并自动获取所有已注册的插件实例:
// src/Service/MyPluginConsumer.php namespace App\Service; use App\Plugin\MyPluginInterface; use App\Plugin\MyPluginManager; class MyPluginConsumer { /** @var MyPluginInterface[] */ private array $plugins; public function __construct(MyPluginManager $pluginManager) { // 获取所有已注册的插件名称 $registeredServices = $pluginManager->getRegisteredServices()['factories']; // 过滤出我们的插件类 $pluginNames = array_filter(array_keys($registeredServices), function ($name) { return str_starts_with($name, 'App\\Plugin\\'); }); // 初始化所有插件实例 $this->plugins = array_map(function ($name) use ($pluginManager) { return $pluginManager->get($name); }, $pluginNames); } /** * 执行所有插件 * @return string[] */ public function runAllPlugins(): array { $results = []; foreach ($this->plugins as $plugin) { $results[$plugin->getName()] = $plugin->execute(); } return $results; } }
然后创建这个服务的工厂类(因为它依赖MyPluginManager):
// src/Service/Factory/MyPluginConsumerFactory.php namespace App\Service\Factory; use App\Service\MyPluginConsumer; use App\Plugin\MyPluginManager; use Interop\Container\ContainerInterface; use Zend\ServiceManager\Factory\FactoryInterface; class MyPluginConsumerFactory implements FactoryInterface { public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) { return new MyPluginConsumer( $container->get(MyPluginManager::class) ); } }
6. 测试使用
最后,我们可以在一个路由处理程序里测试这个功能:
// src/Handler/PluginTestHandler.php namespace App\Handler; use App\Service\MyPluginConsumer; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Zend\Diactoros\Response\JsonResponse; class PluginTestHandler implements RequestHandlerInterface { private MyPluginConsumer $consumer; public function __construct(MyPluginConsumer $consumer) { $this->consumer = $consumer; } public function handle(ServerRequestInterface $request): ResponseInterface { $pluginResults = $this->consumer->runAllPlugins(); return new JsonResponse([ 'status' => 'success', 'plugins' => $pluginResults ]); } }
别忘了在config/dependencies.php里注册这个处理器的工厂,然后在config/routes.php里添加路由指向它。访问对应的路由,你就能看到所有插件执行后的结果了!
常见问题排查
- 确保你的PluginManager正确设置了
$instanceOf属性,否则会允许非接口实现类被注入 - 检查插件的命名空间和类名是否正确,DI容器里的注册是否匹配
- 如果使用自动发现,确保扫描目录的路径正确,并且类文件的命名符合PSR-4规范
内容的提问来源于stack exchange,提问作者Frank
相关产品推荐
相关产品推荐

