Мне интересно, существует ли что-нибудь вроде следующего для Laravel? Я написал эту черту под названием CarefulCount.
Что она делает: возвращает количество связанных моделей (с использованием любого определенного отношения), но обращается к БД только в случае крайней необходимости. Во-первых, он пробует два варианта, чтобы избежать попадания в базу данных, если информация уже доступна:
- Был ли получен счетчик с использованием
withCount('relation')
при извлечении модели - т.е. существует ли $model->relation_count
? Если да, просто верните это. - Было ли отношение загружено? Если это так, подсчитайте модели в коллекции Eloquent, не обращаясь к базе данных, с помощью
$model->relation->count()
. - Только затем прибегайте к вызову
$model->relation()->count()
, чтобы получить счет из базы данных.
Чтобы включить его для любого класса модели, вам просто нужно включить черту с use CarefulCount
. Затем вы можете вызвать $model->carefulCount('relation')
для любого определенного отношения.
Например, в моем приложении есть таблица suburbs
с отношением «много-много» как к таблице users
, так и к таблицам churches
( т.е. в одном пригороде может быть много пользователей и много церквей). Просто добавив use CarefulCount
к модели Suburb
, я могу затем вызвать как $suburb->carefulCount('users')
, так и $suburb->carefulCount('churches')
.
Мой вариант использования: я сталкивался с этим несколько раз - где мне нужно количество связанных моделей, но это в части нижнего уровня моего приложения, которую можно вызывать из нескольких мест. Поэтому я не могу знать, как была получена модель и есть ли уже информация о счетчике.
В таких ситуациях по умолчанию будет вызывать $model->relation()->count()
. Но это может привести к проблеме запроса N + 1 .
Фактически, специфический триггер c возник в результате добавления превосходного Laravel N + 1 Query Detector от Марселя Посио. пакет к моему проекту. Он обнаружил ряд проблем с запросами N + 1, которые я не заметил, и в большинстве случаев у меня были уже загруженные связанные модели. Но в моих шаблонах Blade я использую политики, чтобы разрешить или запретить удаление записей; а метод delete($user, $suburb)
моего класса SuburbPolicy
включал следующее:
return $suburb->users()->count() == 0 && $suburb->churches()->count() == 0;
Это привело к проблеме N + 1 - и, очевидно, я не могу предположить, что в моем классе политики (или самом моем классе модели ), что пользователи и церкви очень загружены. Но с добавлением черты CarefulCount
получилось:
return $suburb->carefulCount('users') == 0 && $suburb->carefulCount('churches') == 0;
Voila! Повозившись с этим и проверив журнал запросов, он работает. Например, с количеством пользователей:
- Если
$suburb
был получен с использованием Suburb::withCount('users')
, дополнительный запрос не выполняется. - Аналогично, если он был получен с использованием
Suburb::with('users')
, дополнительный запрос не выполняется. - Если ничего из вышеперечисленного не было выполнено, выполняется запрос
select count(*)
для получения счетчика.
Как я уже сказал, я ' Я хотел бы знать, существует ли что-то подобное уже, и я не нашел его (ни в ядре, ни в пакете) - или я упустил что-то очевидное.
Вот код моей черты :
use Illuminate\Support\Str;
trait CarefulCount
{
/**
* Implements a careful and efficient count algorithm for the given
* relation, only hitting the DB if necessary.
*
* @param string $relation
*
* @return integer
*/
public function carefulCount(string $relation): int
{
/*
* If the count has already been loaded using withCount('relation'),
* use the 'relation_count' property.
*/
$prop = Str::snake($relation) . "_count";
if (isset($this->$prop)) {
return $this->$prop;
}
/*
* If the related models have already been eager-loaded using
* with('relation'), count the loaded collection.
*/
if ($this->relationLoaded($relation)) {
return $this->$relation->count();
}
/*
* Neither loaded, so hit the database.
*/
return $this->$relation()->count();
}
}