Система плагинов: присвоение каждому уникального идентификатора
Основы работы с плагинами по идентификатору
Как зарегистрировать плагин по уникальному ID и вызвать его метод?
Эффективное решение: интерфейс PluginInterface и менеджер плагинов
Определяется интерфейс, который должен реализовать каждый плагин. Менеджер хранит зарегистрированные плагины в ассоциативном массиве по ID. При вызове происходит проверка существования ID и вызов метода.
interface PluginInterface {
public function execute(array $params): mixed;
}
class PluginManager {
private array $plugins = [];
public function register(string $id, PluginInterface $plugin): void {
if (isset($this->plugins[$id])) {
throw new RuntimeException("Plugin with ID '$id' already registered.");
}
$this->plugins[$id] = $plugin;
}
public function execute(string $id, array $params = []): mixed {
if (!isset($this->plugins[$id])) {
throw new RuntimeException("Plugin with ID '$id' not found.");
}
return $this->plugins[$id]->execute($params);
}
public function getPlugin(string $id): ?PluginInterface {
return $this->plugins[$id] ?? null;
}
}
Plugins php id (плагины php по id)
$manager = new PluginManager();
$manager->register('logger', new class implements PluginInterface {
public function execute(array $params): mixed {
return "Logged: " . json_encode($params);
}
});
echo $manager->execute('logger', ['message' => 'test']);
// Вывод: Logged: {"message":"test"}
Типичные ошибки:
- Попытка вызвать несуществующий плагин – выбрасывается исключение. Решение: проверка через getPlugin() перед вызовом или обработка исключения.
- Дублирование ID – исключение при регистрации. Решение: использовать уникальные префиксы или версионирование в ID.
- Несоответствие интерфейсу – проверка instanceof в методе register.
Как реализовать простой плагин с анонимной функцией (Closure) по ID?
Когда не требуется полный класс, можно хранить callable. При вызове функция выполняется с переданными параметрами.
class SimplePluginManager {
private array $plugins = [];
public function add(string $id, callable $callable): void {
$this->plugins[$id] = $callable;
}
public function run(string $id, ...$args): mixed {
if (!isset($this->plugins[$id])) {
return null;
}
return ($this->plugins[$id])(...$args);
}
}
$manager = new SimplePluginManager();
$manager->add('greet', fn($name) => "Hello, $name");
echo $manager->run('greet', 'Alice'); // Hello, Alice
Проблема: callable не имеет строгой типизации, параметры могут не совпадать. Решение: проверка аргументов через рефлексию или документирование сигнатуры.
Как загрузить плагины из файловой системы, используя имя файла как ID?
Каждый плагин находится в отдельном PHP-файле, который возвращает объект или callable. Менеджер сканирует директорию, подключает файлы и сохраняет по имени файла (без расширения).
class FilePluginLoader {
private array $plugins = [];
public function loadFromDirectory(string $dir): void {
foreach (glob($dir . '/*.php') as $file) {
$id = basename($file, '.php');
$plugin = require $file;
$this->plugins[$id] = $plugin;
}
}
public function get(string $id): mixed {
return $this->plugins[$id] ?? null;
}
}
Пример файла плагина (plugins/uppercase.php):
return function(string $text): string {
return strtoupper($text);
};
$loader = new FilePluginLoader();
$loader->loadFromDirectory(__DIR__ . '/plugins');
$callback = $loader->get('uppercase');
echo $callback('hello'); // HELLO
Проблема: конфликт пространства имен при require, возможны фатальные ошибки, если плагин объявляет классы с одинаковыми именами. Решение: изолировать каждый плагин в своем пространстве имен или использовать автозагрузку Composer.
Как использовать готовый DI-контейнер (например, Pimple) для управления плагинами по ID?
Плагины регистрируются как сервисы с ключом-идентификатором. Контейнер обеспечивает ленивую загрузку и внедрение зависимостей.
use Pimple\Container;
$container = new Container();
// Регистрация плагина как фабрики
$container['plugin.cleaner'] = $container->factory(function ($c) {
return new class {
public function clean(string $data): string {
return trim(strip_tags($data));
}
};
});
// Получение плагина по ID (без префикса 'plugin.' можно настроить)
$plugin = $container['plugin.cleaner'];
echo $plugin->clean("Text "); // Text
Проблема: ID сервисов обычно содержат префиксы, что усложняет обращение. Решение: создать обертку – PluginContainerAdapter, который скрывает префиксы.
Расширенные примеры работы с плагинами по ID
Пример 1: Плагины с приоритетами и цепочка выполнения
interface PrioritablePluginInterface {
public function execute(array $params): mixed;
public function getPriority(): int;
}
class PriorityPluginManager {
private array $plugins = [];
public function register(string $id, PrioritablePluginInterface $plugin): void {
$this->plugins[$id] = $plugin;
}
public function executeAll(array $params): array {
$sorted = $this->plugins;
usort($sorted, fn($a, $b) => $a->getPriority() <=> $b->getPriority());
$results = [];
foreach ($sorted as $id => $plugin) {
$results[$id] = $plugin->execute($params);
}
return $results;
}
}
// Реализация плагина
$pluginManager = new PriorityPluginManager();
$pluginManager->register('filter', new class implements PrioritablePluginInterface {
public function execute(array $params): mixed { return array_filter($params); }
public function getPriority(): int { return 10; }
});
$pluginManager->register('reverse', new class implements PrioritablePluginInterface {
public function execute(array $params): mixed { return array_reverse($params); }
public function getPriority(): int { return 20; }
});
$result = $pluginManager->executeAll([1, 2, 3, 0, 5]);
// Сначала filter (оставит [1,2,3,5]), потом reverse ([5,3,2,1])
Результат выполнения: ['filter'=>[1,2,3,5], 'reverse'=>[5,3,2,1]]
Пример 2: Динамическое включение/выключение плагинов по ID
class TogglePluginManager {
private array $plugins = [];
private array $active = [];
public function register(string $id, callable $plugin): void {
$this->plugins[$id] = $plugin;
$this->active[$id] = true; // по умолчанию активен
}
public function enable(string $id): void {
if (isset($this->plugins[$id])) {
$this->active[$id] = true;
}
}
public function disable(string $id): void {
if (isset($this->plugins[$id])) {
$this->active[$id] = false;
}
}
public function execute(string $id, ...$args): mixed {
if (!isset($this->active[$id]) || !$this->active[$id]) {
return null;
}
return ($this->plugins[$id])(...$args);
}
}
$manager = new TogglePluginManager();
$manager->register('logging', fn($msg) => "LOG: $msg");
$manager->disable('logging');
echo $manager->execute('logging', 'test'); // null (не выводится)
$manager->enable('logging');
echo $manager->execute('logging', 'test'); // LOG: test
Вывод: сначала ничего, затем "LOG: test"
Пример 3: Загрузка плагинов через автозагрузку Composer PSR-4 с автоматической регистрацией
// composer.json:
// "autoload": {"psr-4": {"Plugins\\": "plugins/"}}
// plugins/TextProcessor/ReversePlugin.php
namespace Plugins\TextProcessor;
class ReversePlugin implements \PluginInterface {
public function execute(array $params): mixed {
return strrev($params['text']);
}
}
// В файле инициализации
spl_autoload_register(function ($class) {
// Преобразование namespace в ID плагина (например, 'textprocessor.reverse')
$parts = explode('\\', $class);
if (count($parts) >= 2 && $parts[0] === 'Plugins') {
$id = strtolower(implode('.', array_slice($parts, 1)));
$manager->register($id, new $class());
}
});
// После автозагрузки плагин доступен по ID 'textprocessor.reverse'
echo $manager->execute('textprocessor.reverse', ['text' => 'Hello']); // olleH
Результат: olleH
Пример 4: Плагины с конфигурацией (параметры при регистрации)
class ConfigurablePluginManager {
private array $plugins = [];
public function register(string $id, callable $factory, array $config = []): void {
$this->plugins[$id] = $factory($config);
}
public function get(string $id): mixed {
return $this->plugins[$id] ?? null;
}
}
$manager = new ConfigurablePluginManager();
$manager->register('greeting', function(array $config) {
$prefix = $config['prefix'] ?? 'Hello';
return function(string $name) use ($prefix) {
return "$prefix, $name";
};
}, ['prefix' => 'Hi']);
$greeter = $manager->get('greeting');
echo $greeter('Bob'); // Hi, Bob
Вывод: Hi, Bob