Система плагинов: присвоение каждому уникального идентификатора

Раздел: Разработка на PHP -> Плагины и расширения

Основы работы с плагинами по идентификатору

Как зарегистрировать плагин по уникальному 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

Плагины PHP по ID - comments

En
Plugins php id (php)