Создание собственных PHP хелперов в Laravel проекте

Источник: «Creating Your Own PHP Helpers in a Laravel Project»
Если вы новичок в Laravel или PHP, давайте рассмотрим, как можно создать собственные хелперы, автоматически загружаемые Laravel.

Laravel предоставляет множество отличных хелперов, удобных для работы с массивами, путями к файлам, строками, маршрутами и другими функциями, например, с любимой функцией dd().

Также можно создать собственный набор хелперов для приложений Laravel и пакетов PHP, используя Composer для их автоматического импорта.

Ранее уже публиковались статьи о написании хелперов для Laravel проектов от Ian Kumu и Ashley Allen:

Создание файла хелперов в приложении Laravel

Первый сценарий, в котором может возникнуть желание включить свои хелперы, — это контекст приложения Laravel. В зависимости от предпочтений можно организовать расположение файлов хелперов по своему усмотрению, но вот несколько предлагаемых мест:

Я предпочитаю хранить файлы в app/helpers.php в корне пространства имён приложения.

Автозагрузка

Чтобы воспользоваться своими PHP хелпером, необходимо загрузить его в программу во время выполнения. В начале моей карьеры нередко можно было встретить подобный код в начале файла:

require_once ROOT . '/helpers.php';

Функции PHP не могут автоматически загружаться. Однако есть гораздо лучшее решение благодаря Composer, чем использование require или require_once.

При создании нового проекта Laravel в файле composer.json отображаются ключи autoload и autoload-dev:

"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\\": "app/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},

Если нужно добавить файлы хелперы, в composer есть ключ files (представляющий собой массив путей к файлам), который можно задать в autoload:

"autoload": {
"files": [
"app/helpers.php"
],
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\\": "app/"
}
},

После добавления нового пути в массив files необходимо сбросить автозагрузку:

composer dump-autoload

Теперь при каждом запросе файл helpers.php будет автоматически загружаться, поскольку Laravel использует автозагрузку Composer в public/index.php:

require __DIR__.'/../vendor/autoload.php';

Определение функций

Определение функций в классах хелперов — это самая простая часть, но есть несколько нюансов. Все файлы хелперов Laravel обёрнуты в проверку, чтобы избежать коллизий определений функций:

if (! function_exists('env')) {
function env($key, $default = null) {
// ...
}
}

Это может вызвать затруднения, потому что можно столкнуться с ситуацией, когда используется определение функции, которого вы не ожидали, исходя из того, какая из них была определена первой.

Я предпочитаю использовать проверку function_exists в своих хелперах для приложений, но если вы определяете хелперы в контексте своего приложения, то можно обойтись без проверки function_exists.

Пропустив проверку, вы можете столкнуться с коллизиями в любой момент, когда ваши хелперы переопределяют функции, что может быть полезным.

На практике коллизии случаются не так часто, как кажется, и поэтому необходимо убедиться, что имена функций не являются слишком общими. Также можно использовать префиксы в именах функций, чтобы снизить вероятность коллизии с другими зависимостями.

Пример хелпера

Мне нравятся хелперы пути и URL в Rails, предоставляемые при определении ресурсного маршрута. Например, маршрут ресурса photos будет содержать такие хелперы маршрута, как new_photo_path, edit_photo_path и т. д.

При использовании маршрутизации ресурсов в Laravel мне нравится добавлять несколько хелперов, упрощающих определение маршрутов в шаблонах. В своей реализации мне нравится использовать хелпер URL, которой можно передать модель Eloquent и получить в ответ маршрут ресурса, используя определённые конвенции, например:

create_route($model);
edit_route($model);
show_route($model);
destroy_route($model);

Так можно определить show_route в файле app/helpers.php (остальные будут выглядеть аналогично):

if (! function_exists('show_route')) {
function show_route($model, $resource = null)
{
$resource = $resource ?? plural_from_model($model);

return route("{$resource}.show", $model);
}
}

if (! function_exists('plural_from_model')) {
function plural_from_model($model)
{
$plural = Str::plural(class_basename($model));

return Str::kebab($plural);
}
}

Функция plural_from_model() — это функция, используемая хелперами маршрута для определения имени ресурса маршрута на основе предпочтительного для меня соглашения об именовании, представляющего собой множественное число модели в kebab-case.

Например, вот пример имени ресурса, полученного из модели:

$model = new App\LineItem;
plural_from_model($model);
// => line-items

plural_from_model(new App\User);
// => users

Используя это соглашение, можно определить маршрут ресурса в файле routes/web.php следующим образом:

Route::resource('line-items', 'LineItemsController');
Route::resource('users', 'UsersController');

А затем в blade-шаблонах можно сделать следующее:

<a href="{{ show_route($lineItem) }}">
{{ $lineItem->name }}
</a>

В результате сгенерируется что-то вроде следующего HTML-фрагмента:

<a href="http://localhost/line-items/1">
Line Item #1
</a>

Пакеты

Ваши пакеты Composer также могут использовать файл хелперов для любых хелперов, которые вы хотите сделать доступными для проектов, использующих ваш пакет.

Аналогичный подход применяется и в файле composer.json пакета, определяя ключ files с массивом ваших хелперов.

Обязательно оборачивайте свои хелперы в проверку function_exists(), чтобы проекты, использующие ваш код, не сломались из-за коллизий именования.

Необходимо выбирать подходящие имена функций, уникальные для вашего пакета, а также использовать короткий префикс, если опасаетесь, что имя функции будет слишком общим.

Узнайте больше

Ознакомьтесь с документацией Composer по автозагрузке, чтобы узнать больше о подключении файлов и общую информацию о классах автозагрузки.

Другие статьи по написанию хелперов:

Дополнительные материалы

Предыдущая Статья

Property Hooks приближаются к реализации в PHP 8.4

Следующая Статья

Копирование файлов между Docker контейнером и хостом