Макросы в Laravel
О макросах в Laravel сказано явно недостаточно, и мы постараемся исправить эту досадную оплошность. Если говорить о макросах вкратце, то речь идёт о способе расширения метода класса, однако не через наследование, а через замыкание.
Использование макросов позволит вам улучшить свою работу, снизить дублирование кода, сделать код более читабельным, да и просто решить ряд проблем, которые иногда возникают при тестировании.
Как сделать и куда положить макросы в Laravel?
Итак, есть несколько мест, куда можно положить макросы: 1.Использование простого PHP-файла и его загрузка через Composer. Нужно создать новый файл macros.php в папке app. После этого следует отредактировать autoload в composer.json, добавив в свойство files относительный путь к файлу app/macros.php. Далее останется запустить composer dump-autoloader, чтобы файл загружался и выполнялся, настраивая макросы для всего нашего приложения.
"autoload": { "psr-4": { "App\\": "app/" }, "classmap": [ "database/seeds", "database/factories" ], "files": [ "app/macros.php" ] }
2.Второе место — это метод boot в сервис-провайдере. По правде говоря, это не самый оптимальный способ, т. к. при росте приложения придётся добавлять всё больше макросов. Но тем не менее.
Коллекции
Большинство людей познакомились с макросами именно с помощью коллекций в Laravel, поэтому не будем об этом много рассказывать. Приведём лишь простой, но хороший пример небольшого макроса — это преобразование ключей для массива. Если вы захотите написать преобразование как функцию, это будет довольно утомительно. Лучше использовать макрос. Сначала его необходимо создать, чтобы всё отмаппить и управлять рекурсивно:
<?php \Illuminate\Support\Collection::macro( 'mapKeysWith', function ($callable) { /* @var $this \Illuminate\Support\Collection */ return $this->mapWithKeys(function ($item, $key) use ($callable) { if (is_array($item)) { $item = collect($item) ->mapKeysWith($callable) ->toArray(); } return [$callable($key) => $item]; }); } );
А потом создать ещё один, чтобы красиво всё обернуть:
<?php \Illuminate\Support\Collection::macro( 'mapKeysToCamelCase', function () { /* @var $this \Illuminate\Support\Collection */ return $this->mapKeysWith('camel_case'); } );
Вуаля, теперь можно использовать наш макрос, к примеру, так:
<?php collect(['test_key' => ['second_layer' => true]])->mapKeysToCamelCase(); // Создает массив ['TestKey' => ['SecondLayer' => true]]
Запросы Eloquent
Здесь применение макросов имеет действительно важное значение. К примеру, вы хотите напрямую использовать БД. Скорее всего, вы будете делать это, используя «сырые» запросы (raw queries):
<?php $query->whereRaw( "ST_Distance_Sphere(`table`.`column`, POINT(?, ?)) < ?", [$long, $lat, $distance], $boolean );
Если в вашем приложении вы будете делать это слишком часто, код станет повторяемым, не говоря уже о попытках соединить несколько where в одном выражении. Да, можно обойти это с помощью скоупов (scopes), но их тогда придётся добавлять в каждую модель. Проще всего — создать макрос. В нашем примере мы создаём макрос для фильтрации результатов в MySQL, применяя встроенные геопространственные функции сервера:
<?php \Illuminate\Database\Query\Builder::macro( 'whereSpatialDistance', function ($column, $operator, $point, $distance, $boolean = 'and') { $this->whereRaw( "ST_Distance_Sphere(`{$this->from}`.`$column`, POINT(?, ?)) $operator ?", [$point[0], $point[1], $distance], $boolean ); });
А чтобы мы могли использовать условие orWhere, добавим ещё один макрос:
<?php \Illuminate\Database\Query\Builder::macro( 'orWhereSpatialDistance', function ($column, $operator, $point, $distance) { $this->whereSpatialDistance($column, $operator, $point, $distance, 'or'); } );
Теперь, если нужно отфильтровать запрос, можно вызвать макрос напрямую точно так же, как и любой другой оператор where.
<?php $query->whereSpatialDistance('coordinates', [1, 1], 10) ->orWhereSpatialDistance('coordinates', [0, 0], 1);
Написано по материалам статьи «Laravel Tip: 5 examples of why you should be using Macros».