Возможно вы обратили внимание, что на прошлом уроке мы добавили в таблицы категорий и товаров строку поиска, но она не работает?
Это потому, что никто не написал сам функционал поиска в контроллерах. Мы просто расширили базовый ModelController
и больше ничего не меняли.
Помимо этого в категориях мы указали колонку для вывода общего количества товаров - и она тоже пуста, по той же причине.
Предлагаю сегодня доработать наши контроллеры и добавить недостающие функции.
В качестве dev-зависимости Vesp устанавливает очень полезный пакет для отладки запросов - Clockwork, вам нужно только установить его расширение для браузера.
И вот, что мы сейчас видим при обычном выводе таблицы категорий
Мы видим всего 3 запроса:
Куда же делась загрузка самого пользователя и его токена, спросите вы? Они не выводятся по умолчанию, потому что слежение Clockwork включено только для маршрутов, а пользователь загружается перед контроллером, в middleware.
Но если хотите, можно изменить файл core/routes.php
и заменить
$group->add(Vesp\Middlewares\Clockwork::class);
$app->add(Vesp\Middlewares\Clockwork::class);
Тогда вы будете видеть вообще все запросы:
Вот теперь, давайте посмотрим, что происходит во время изменения строки поиска? Правильно, запросы уходят на сервер, но результаты не меняется - ведь наш контроллер их просто не обрабатывает.
Так как поиск меняет количество результатов, мы должны обрабатывать его в методе beforeCount
, чтобы выдавалось верное общее количество результатов:
protected function beforeCount(Builder $c): Builder
{
// Если указан параметр query
if ($query = $this->getProperty('query')) {
// Делаем вложенный запрос
$c->where(static function (Builder $c) use ($query) {
// В котором ищем по title
$c->where('title', 'LIKE', "%$query%");
// Или description
$c->orWhere('description', 'LIKE', "%$query%");
});
}
return $c;
}
Как видите, вложенный запрос помещается в скобки, так что если вы добавите потом еще одно отдельное условие, например $c->where('active', true);
всё будет работать корректно.
С поиском понятно, а как выводить количество товаров, привязанных к категории? Тут Eloquent предлагает очень удобную функцию withCount()
, которы мы используем в :
core/src/Controllers/Admin/Categories.php
:
protected function afterCount(Builder $c): Builder
{
// Подсчёт количества товаров категории
$c->withCount('products');
return $c;
}
withCount()
требует указать имя связанных моделей, а мы прописывали товары категории в модели Product
, помните? Вот через эту связь всё и работает.
Давайте теперь примерно так же выведем и категорию у товара, тоже через связь. Для этого используется следующая функция в котроллере core/src/Controllers/Admin/Products.php
:
protected function afterCount(Builder $c): Builder
{
// Присоединение 2х колонок категории товара
$c->with('category:id,title');
return $c;
}
Смотрим на запрос и видим, что добавилась выборка категорий с определёнными id
.
Eloquent не очень любит использовать join
таблиц и вместо этого предлагает довольно хитрую штуку - он сначала выбирает наши товары, потом смотрит в их category_id
, выбирает отдельным запросом только нужные категории и затем проходит циклом по товарам, добавляя в них родительскую категорию.
И вот, что мы видим в ответе - вложенный массив с категорией, который содержит всего 2 запрошенных колонки:
Согласитесь, очень кратко и удобно. Главное, не забывать просписывать связи в моделях и выбирать соответствующие колонки в запросе. Если бы я не указал id
в списке колонок категории, Eloquent бы не знал, к каким товарам её присоединить.
Вообще, таких присоединений можно делать довольно много, и выборки не начинают тормозить при большом количестве результатов, в отличие от join
. Минус только один - по таким таблицам нельзя делать сортировку, для этого необходим всё-таки join
.
Так что, если нужно просто выбрать данные - используем with()
, если нужны и данные и сортировка - то join()
.
Теперь осталось только вывести новую колонку в таблице с товарами в админке. Редактируем src/admin/pages/products.vue
fields() {
return [
{key: 'id', label: this.$t('components.table.columns.id'), sortable: true},
{key: 'sku', label: this.$t('models.product.sku'), sortable: true},
// Через точку можно указывать вложенные ключи в массиве!
{key: 'category.title', label: this.$t('models.product.category')},
// ...
Пересобираем фронт и любуемся на результат:
Вот такой небольшой, но очень полезный урок по отладке наших контроллеров.
Теперь вы всегда будете видеть, как именно проходит запрос и почему контроллер возвращает не то, что вам нужно.
Все изменения можно посмотреть на Github. Я там нашёл и поправил опечатку с указанием даты изменения в таблице товаров.
На следующем уроке освоим библиотеку Faker, нагенерируем кучу товаров с категориями и выведем их на фронтенде.
У меня магия так и не сработала, так и не вывелось название категории:
Перепробовал различные варианты. Не понял, почему
category.title
, а не напримерcategories.title
- это хотя бы название привязанной таблицы. Магия сcategory_id
у меня не завелась.Незначительные опечатки:
src/admin/products.vue
должно быть:
src/admin/pages/products.vue
{key: 'category.title', label: this.$t('models.products.category')}
должно быть:
{key: 'category.title', label: this.$t('models.product.category')}
Разобрался, откуда данные должны подтягиваться в category.title - берутся из json. Но все равно Категория без данных.
Посмотрел через DevTools откуда подгружается json и он грузится по ссылке: http://vesp-shop.test/api/admin/products где отсутствует category вот в этом и проблема:
а по ссылке http://vesp-shop.test/api/web/products все в порядке:
Осталось понять как формируется json?
Все контроллеры Vesp выдают JSON по умолчанию, а результат формируется из условий запроса.
Думаю, ты поторопился и пропустил в этой заметке раздел с выборкой категории, где рассказано, что в контроллере
core/src/Controllers/Admin/Products.php
должна быть вот такая функция:protected function afterCount(Builder $c): Builder { // Присоединение 2х колонок категории товара $c->with('category:id,title'); return $c; }
Немного доработал заметку, чтобы было понятнее.
Василий, спасибо большое, за такие развернутые ответы! Как всегда у тебя, все классно запроектировано, спасибо, что делишься опытом - узнаю много нового!
Спасибо, что читаешь!
Потому что мы используем название связи, а не привязанной таблицы. В модели
Product
прописан методcategory()
- вот она и используется для работы.public function category(): BelongsTo { return $this->belongsTo(Category::class); }
Связь указывается между моделями, а в какой таблице модель хранит данные никого не интересует.
Спасибо, поправил!