Функции в плагине PHP: как организовать код правильно
Организация функций в PHP плагине
Основное эффективное решение: использование пространств имен и автозагрузки PSR-4
Современный подход к определению функций в плагине заключается в отказе от глобальных функций в пользу методов классов, сгруппированных по пространствам имен. Это полностью устраняет конфликты имен и позволяет использовать автозагрузку, упрощая поддержку кода.
Шаги реализации:
- Создать структуру каталогов:
src/для классов,composer.jsonдля автозагрузки. - Определить класс с методами вместо функций. Например, класс
Plugin\Coreс методомinit(). - Подключить автозагрузку через
require_once __DIR__ . '/vendor/autoload.php';в главном файле плагина. - Зарегистрировать хуки и фильтры, вызывая статические методы класса.
Пример главного файла плагина:
<?php
/**
* Plugin Name: My Secure Plugin
* Description: Пример плагина с пространством имен.
*/
namespace MyPlugin;
require_once __DIR__ . '/vendor/autoload.php';
// Регистрация хука активации
register_activation_hook(__FILE__, [Core::class, 'activate']);
// Инициализация плагина
add_action('plugins_loaded', [Core::class, 'init']);Functions php plugin (функции плагина php)
Файл src/Core.php:
<?php
namespace MyPlugin;
class Core {
public static function activate() {
// Логика активации
}
public static function init() {
// Подключение скриптов, регистрация хуков
add_action('wp_enqueue_scripts', [self::class, 'enqueue_assets']);
}
public static function enqueue_assets() {
wp_enqueue_style('my-plugin-style', plugins_url('assets/style.css', __DIR__));
}
}
Проблемы и их решение:
Типичная ошибка: забыли добавить use для классов в пространстве имен, что приводит к фатальным ошибкам. Решение: всегда проверять импорты и использовать автозагрузку.
Другая проблема: класс не найден, если не настроена автозагрузка. Решение: выполнить composer dump-autoload после изменений в composer.json.
Цели использования: крупные плагины с десятками функций, командная разработка, необходимость переиспользования кода через Composer.
Как избежать конфликтов имен функций без пространств имен?
Если проект использует PHP до версии 5.3 или нельзя применить автозагрузку, применяют префиксы. Каждая глобальная функция начинается с уникального префикса, например myplugin_.
<?php
function myplugin_do_something($data) {
// Обработка данных
}
function myplugin_get_config() {
return get_option('myplugin_config');
}
add_action('init', 'myplugin_do_something');
Проблемы и их решение:
Конфликт возникает, если другой плагин использует такой же префикс. Решение: выбирать уникальный, длинный префикс (например, название компании + плагина).
Ошибка: при рефакторинге легко забыть изменить вызов функции. Решение: использовать статический анализ или IDE с поддержкой переименования.
Цели: небольшие плагины, поддержка старых версий PHP, простота в понимании.
Как передать дополнительные параметры в хук без глобальных переменных?
Использование анонимных функций (замыканий) с захватом переменных через use. Это позволяет избежать глобального состояния.
<?php
$custom_data = ['key' => 'value'];
add_action('wp_footer', function() use ($custom_data) {
echo '<script>console.log(' . json_encode($custom_data) . ');</script>';
});
Проблемы и их решение:
Анонимную функцию нельзя удалить с помощью remove_action, если она не сохранена в переменную. Решение: сохранить замыкание в переменную и передавать её имя.
Отладка затруднена: в стеке вызовов видно {closure}. Решение: использовать именованные функции, если нужна отладка.
Цели: быстрая обработка разовых событий, передача контекста без создания отдельного класса.
Как группировать связанные функции без создания экземпляра класса?
Использование статических методов в классе. Класс выступает как неймспейс, а методы - как функции.
<?php
class MyPlugin_Helper {
public static function format_price($price) {
return number_format($price, 2, '.', ' ');
}
public static function get_user_email($user_id) {
$user = get_userdata($user_id);
return $user ? $user->user_email : '';
}
}
// Использование
$price = MyPlugin_Helper::format_price(1234.5);
Проблемы и их решение:
Имя класса может конфликтовать с другими плагинами. Решение: добавить префикс в имя класса или использовать пространство имен.
Статические методы сложно тестировать (моки). Решение: перейти на экземпляры класса с dependency injection.
Цели: удобная организация кода без пространств имен, простота вызова.
Как переиспользовать функции между разными плагинами?
Использование трейтов. Трейты позволяют включать набор методов в любой класс, избегая дублирования.
<?php
trait Logger {
public function log($message, $level = 'info') {
$log_file = WP_CONTENT_DIR . '/debug.log';
$entry = '[' . strtoupper($level) . '] ' . $message;
error_log($entry, 3, $log_file);
}
}
class MyPlugin_Main {
use Logger;
public function run() {
$this->log('Плагин запущен');
}
}
Проблемы и их решение:
Конфликт имен методов трейта. Решение: использовать insteadof или псевдонимы.
Трейты не могут иметь статические свойства. Решение: использовать методы с возвратом конфигурации.
Цели: повторное использование идентичной логики (логирование, валидация) в нескольких плагинах.
Расширенные примеры функций в плагине PHP
Пример 1: Полный плагин WordPress с автозагрузкой через Composer и пространством имен
Структура каталогов:
my-plugin/
├── src/
│ └── Core.php
├── assets/
│ └── style.css
├── composer.json
└── my-plugin.php
Файл composer.json:
{
"autoload": {
"psr-4": {
"MyPlugin\\": "src/"
}
}
}
Файл src/Core.php (класс с методами-функциями):
<?php
namespace MyPlugin;
class Core {
public static function activate() {
update_option('myplugin_version', '1.0.0');
}
public static function deactivate() {
delete_option('myplugin_version');
}
public static function enqueue_assets() {
wp_enqueue_script('my-plugin-script',
plugins_url('assets/script.js', __DIR__),
['jquery'],
'1.0.0',
true
);
}
public static function add_custom_footer() {
echo '<p>Спасибо за использование плагина!</p>';
}
}
Файл my-plugin.php (главный файл плагина):
<?php
/**
* Plugin Name: My Plugin
*/
namespace MyPlugin;
require_once __DIR__ . '/vendor/autoload.php';
register_activation_hook(__FILE__, [Core::class, 'activate']);
register_deactivation_hook(__FILE__, [Core::class, 'deactivate']);
add_action('wp_enqueue_scripts', [Core::class, 'enqueue_assets']);
add_action('wp_footer', [Core::class, 'add_custom_footer']);
Результат: после установки плагина в админке появляется опция, на фронте подключается скрипт и выводится текст в подвале.
[INFO] Плагин активирован. Опция myplugin_version установлена. [FRONT] Подключен script.js, в footer добавлен текст.
Пример 2: Использование анонимных функций для динамической фильтрации данных
Передача дополнительных аргументов в фильтр через замыкание:
<?php
// Добавляем фильтр, который заменяет плейсхолдер на значение из настройки
$custom_title = get_option('myplugin_title', 'По умолчанию');
add_filter('the_title', function($title) use ($custom_title) {
if (is_page('my-page')) {
return $custom_title;
}
return $title;
});
Результат: на странице с slug 'my-page' заголовок заменяется на значение опции.
[PAGE] my-page: заголовок изменен на значение опции.
Пример 3: Статические методы с префиксом класса для избежания конфликтов
Использование статического класса с уникальным именем:
<?php
class WPMU_MyPlugin_Stats {
public static function track_visit() {
$visits = get_option('myplugin_visits', 0);
update_option('myplugin_visits', $visits + 1);
}
public static function get_visits() {
return get_option('myplugin_visits', 0);
}
}
add_action('wp', ['WPMU_MyPlugin_Stats', 'track_visit']);
Результат: при каждом посещении сайта счетчик увеличивается, можно вывести число через WPMU_MyPlugin_Stats::get_visits().
[DATA] myplugin_visits = 1234
Пример 4: Трейт для логирования в несколько плагинов
Создаем трейт в отдельном файле и подключаем в разных классах:
<?php
// traits/Logger.php
trait Logger {
public function log($message, $level = 'info') {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$caller = $backtrace[1]['function'] ?? 'unknown';
$entry = "[$level] [$caller] $message";
error_log($entry);
}
}
// src/Admin.php
class Admin {
use Logger;
public function save_settings() {
$this->log('Настройки сохранены');
}
}
// src/Frontend.php
class Frontend {
use Logger;
public function render() {
$this->log('Страница отрисована');
}
}
Результат: в лог-файл записываются сообщения с указанием уровня и вызывающего метода.
[info] [Admin::save_settings] Настройки сохранены [info] [Frontend::render] Страница отрисована
Пример 5: Создание собственного хука с использованием do_action
Плагин может определять свои точки расширения:
<?php
namespace MyPlugin;
class Hooks {
public static function init() {
// Собственный хук после сохранения поста
add_action('save_post', [self::class, 'after_save_post'], 10, 3);
}
public static function after_save_post($post_id, $post, $update) {
// Выполняем пользовательское действие
do_action('myplugin_after_save', $post_id, $post, $update);
}
}
// В другом плагине можно подписаться:
add_action('myplugin_after_save', function($post_id, $post, $update) {
// Дополнительная обработка
});
Результат: любой разработчик может расширить функциональность плагина через этот хук.
[HOOK] myplugin_after_save сработал для post_id=10
Пример 6: Использование замыканий с сохранением состояния (статические переменные)
<?php
$counter = function() {
static $count = 0;
$count++;
return $count;
};
echo $counter(); // 1
echo $counter(); // 2
echo $counter(); // 3
Результат:
1 2 3
Применимо в плагинах: подсчет вызовов определенной функции без глобальной переменной.