Учитывая следующие две таблицы и отношения:
Контейнеры моделей: id, name, desc, created_at, updated_at
public function items()
{
return $this->hasMany('App\Models\Items', 'container_id', 'id');
}
Элементы модели: id, container_id, type_id, name, created_at, updated_at
public function containers()
{
return $this->belongsTo('App\Models\Containers', 'id', 'container_id');
}
Как я могу получить все items
, принадлежащие container
, где все, что у меня есть, это container:name
, не делая что-то вроде:
//Route::get('/api/v1/containers/{container}/items', 'ItemsController@index');
public function index($container)
{
//Show all items in a specific container
return Containers::where('name', $container)
->firstOrFail()
->items()
->get()
->toJson();
}
... и просматривать конкретный элемент в конкретном контейнере без необходимости делать что-то еще более неприятное, например:
//Route::get('/api/v1/containers/{container}/items/{item}', 'ItemsController@show');
public function show($container, $item)
{
//Show specific item in a specific container
return Containers::where('name', $container)
->firstOrFail()
->items()
->where('id', $item)
->firstOrFail()
->toJson();
}
В этом случае у меня есть только имя контейнера в качестве удобного URL-слага, который помогает предотвратить манипулирование URL-адресами.
Есть ли способ, даже если это вторичная принадлежность отношения, добиться этого, указав имя контейнера, чтобы я мог просто сделать Items::find($containerName)
без изменения поля primaryKey в моделях.
Я ценю обратную связь, но я здесь, потому что после прочтения документации мне не ясно, как это решить. Если вы захотите расширить свой ответ, я, безусловно, буду признателен.
Возможно, вы сможете добиться этого, сделав обратное тому, что вы сделали (вызов Item
, а не Contractor(s)
. Это будет выглядеть примерно так:
$items = Item::whereHas(['container' => function($query) use ($container) {
$query->where('name', $container);
}])->get()->toJson();
Метод ::whereHas()
вернет только те items
, у которых есть container
с указанным вами именем.
Если этого недостаточно, вы можете создать область запроса на своей модели Item
, переместив в нее функциональность, указанную выше:
// App\Item.php - or where the model is
public function scopeInContainer($query, $container, $id = null)
{
$query = $query->whereHas(['container' => function($q) use ($container) {
$q->where('name', $name);
}]);
if ($id) {
$query = $query->where('id', $id)->firstOrFail();
}
return $query;
}
Затем использовать его:
$items = Item::inContainer($container)->get()->toJson();
Тогда при вызове Item
через $id
вам нужно будет только вызвать:
$item = Item::find($id);
Надеюсь, это то, что вы искали.
Область запроса работает фантастически! Единственное, что меня беспокоит $item = Item::find($id);
, это (если я что-то не упустил) вы можете изменить номер идентификатора в URL-адресе и увидеть элемент не в контейнере, в котором вы просматриваете - вот почему я хотел использовать обратное отношение для обеспечения соблюдения ограничение item:id в контейнере. Возможно, это просто еще одна область запроса, в которой я передаю идентификатор элемента вместе с зоной?
Извините, я не увидел требуемого ограничения. Чтобы ответить на ваш вопрос, да, это можно сделать как область запроса, но не обязательно отдельную. Я обновлю свой ответ, как только смогу.
Спасибо, я попробовал: $item) { $q->where('name', $container)->where('items.id', $item); }); } return $query->whereHas('контейнеры', function ($q) use ($container) { $q->where('name', $container); }); } Это работает, но нарушает DRY, от чего у меня мурашки по коже
@ironchefbadass Я обновил свой ответ, чтобы удовлетворить ваши «сухие» потребности. $id
теперь является необязательным аргументом в области запроса ->inContainer()
.
Это прекрасно работает - большое спасибо. Единственное редактирование, которое мне пришлось сделать, это удалить "->firstOrFail()" - в результате было возвращено гораздо больше элементов, чем просто переданный элемент: идентификатор.
Попробуйте использовать
with()
. Просто прочитайте документы @ironchefbadass