При работе с Laravel нам часто нужны очень специфические функции, но, к сожалению, они не встречаются внутри Laravel.
Хорошей новостью является то, что Laravel позволяет нам добавлять собственные функции благодаря включенной в Laravel функции под названием Macroable.
Но хватит об этом, давайте перейдем к тому, что нас интересует — к «коду».
Для этого примера мы начнем с простой проблемы.
У нас есть приложение, содержащее базу данных пользователей, и мы просим создать поисковую систему для пользователей.
Таким образом, у нас будет путь /users, который будет получать параметры через url следующим образом /users?search[name]=Luis
Первым делом перейдем в файл AppProvidersAppServiceProvider и внутри этого класса найдем функцию register, в которую поместим наш код.
public function register()
{
Builder::macro('search', function () {
$search = request()->get('search');
if (is_null($search)) {
return $this;
}
foreach ($search as $key => $value) {
$this->orWhere($key,'LIKE',"%{$value}%");
}
return $this;
});
}
Но что мы здесь делаем?
На самом деле, понять это довольно просто.
Первое, что мы делаем, это обращаемся к макрофункции класса Builder, который отвечает за построение запросов в Laravel.
Макрофункция принимает два параметра, первый — это имя, которым мы будем называть нашу функцию, а второй параметр — это реализация нашей функции.
$search = request()->get('search');
if (is_null($search)) {
/** var IlluminateDatabaseQueryBuilder */
return $this;
}
Объявленная нами функция довольно проста, в ней мы просто получаем ключ «search» из запроса, а затем проверяем через условие, содержит ли «search» что-то, и если нет, то она возвращает $this. Здесь важно уточнить, что $this в данном контексте относится к классу Builder, поэтому мы возвращаем экземпляр класса Builder, а не AppServiceProvider, в котором мы объявляем наш миксин.
foreach ($search as $key => $value) {
$this->orWhere($key,'LIKE',"%{$value}%");
}
/** var IlluminateDatabaseQueryBuilder */
return $this;
Наконец, мы просто сделаем foreach, чтобы пройти через фильтры поиска, потому что помните, что мы можем отправить n-ное количество фильтров в наш API. И, наконец, мы просто возвращаем экземпляр Builder после добавления всех элементов.
Теперь давайте проверим, все ли работает правильно
Для этого теста мы создадим 1000 пользователей благодаря сеялкам, предоставленным laravel.
AppModelsUser::factory(1000)->create();
Теперь мы просто вызовем новую функцию, которую мы создали, внутри нашего пути /users
Route::get('/users', static function (Request $request) {
return AppModelsUser::search()->get();
});
Обращаясь к пути users, но не передавая никаких параметров внутри него, мы видим, что получим список всех сохраненных пользователей.
Но волшебство происходит, когда мы отправляем параметры внутри нашего пути (/api/users?search[name]=fred&search[lastname]=Stark).
Как мы видим, теперь мы получаем совершенно другой результат. Теперь количество результатов значительно уменьшилось, а полученные результаты соответствуют параметрам поиска, которые мы отправили в url.
На этом этапе мы можем расширять функциональность сколько угодно, но у нас также возникнет проблема, что после объявления нескольких функций в нашем ServiceProvider наш файл вырастет слишком большим, что сделает наш код нечитаемым, а внесение изменений в будущем может стать головной болью.
Поэтому давайте решим эту проблему простым способом.
Для этого мы воспользуемся другим примером.
Давайте представим, что теперь, помимо возможности поиска пользователей, мы также хотим постранично отображать их следующим образом /api/users?page[size]=1&page[number]=1, поскольку, как мы видели, ответы могут стать очень большими и повлиять на производительность нашего приложения.
Вместо объявления функций в ServiceProvider, мы создадим новый класс QueryBuilder внутри нашего проекта.
И внутри этого класса мы определим две наши функции.
<?php
namespace AppMixins;
class QueryBuilder
{
public function search()
{
return function (){
$search = request()->get('search');
if (is_null($search)) {
/** var IlluminateDatabaseQueryBuilder */
return $this;
}
foreach ($search as $key => $value) {
$this->orWhere($key,'LIKE',"%{$value}%");
}
/** var IlluminateDatabaseQueryBuilder */
return $this;
};
}
public function queryPaginate()
{
return function (){
$paginate = request()->get('page');
if (is_null($paginate)) {
/** var IlluminateDatabaseQueryBuilder */
return $this;
}
return $this->paginate(
$perPage = $paginate['size'],
$columns = ['*'],
$pageName = 'page',
$page = $paginate['number']
);
};
}
}
Как мы видим, все функции должны возвращать функцию, содержащую объявление нашей функции.
И, наконец, мы объявим наш миксин в AppServiceProvider.
public function register(): void
{
Builder::mixin(new QueryBuilder());
}
Как мы видим, теперь наш код выглядит более организованным, и мы также должны заметить, что объявление отличается, потому что теперь мы обращаемся не к макрофункции, а к функции mixin, которая получает в качестве параметра только новый экземпляр класса, содержащего наши функции.
И если мы попробуем все это вместе, то получим следующий результат.
Теперь, когда мы отправляем параметры пейджинга в сочетании с фильтрами поиска, мы видим, что получаем те же результаты поиска, но теперь мы получаем только три результата, поскольку мы определяем их в url /api/users?search[name]=fred&search[lastname]=Stark&page[size]=3&page[number]=1
Заключение
Остается только сказать, что использование mixin в наших приложениях — это хороший ресурс, который может сильно помочь нам в повторном использовании кода. И хотя существует множество способов сделать это без использования mixin и даже лучше, хорошо знать, что laravel предлагает нам такие альтернативы.