You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:10:51