Вопрос или проблема
У меня есть модель студента, и в базе данных у меня есть номер социального страхования, из которого я вычисляю настраиваемый атрибут, чтобы получить возраст студента. Как я могу использовать его в запросах Eloquent?
У меня есть этот настраиваемый атрибут в модели:
public function getAgeAttribute(): int
{
if (!$this->deleted) {
$date = substr($this->social_security, 0, 6);
switch (substr($this->social_security, 6, 1)) {
case '-':
$yy = '19';
break;
case 'A':
$yy = '20';
break;
case '+':
$yy = '18';
break;
default:
return 0;
}
$birthday = new \DateTime($yy . substr($date, 4, 2) . '-' . substr($date, 2, 2) . '-' . substr($date, 0, 2));
$today = new \DateTime('today');
return $birthday->diff($today)->y;
}
return 0;
}
Я пытаюсь получить количество студентов в определенном возрастном диапазоне в контроллере следующим образом:
$count = Student::where('deleted', 0)
->whereHas('studentSeasons', function($query) {
$query->whereHas('season', function($query) {
$query->where('start', '<=', now());
$query->where('end', '>=', now());
});
})
->get()
->append('age')
->where('age', '>=', $request->input('start'))
->where('age', '<=', $request->input('end'))
->count();
Я могу добавить возраст в коллекцию, возвращаемую методом get(), но это загружает все модели студентов.
Я хотел бы добавить атрибут возраста в запрос и получить только количество. Как я могу это сделать, или могу ли я?
Как сказал @aynber, вычисляемые атрибуты не могут быть использованы в запросах базы данных, но в вашем случае вы можете использовать поле social_security
для условия вашего запроса.
Вот пример с использованием скопов (добавьте функции скопов в класс Student)
$count = Student::where('deleted', 0)
->belongsToCurrentSeason() //бонус
->ageBetween($request->input('start'), $request->input('end'))
->count();
со скопами:
scopeBelongsToCurrentSeason: используйте это, чтобы не размещать эти where()
повсюду. Обязанностью скопа (и построителей запросов) является преобразование запроса в читаемый код.
//use Illuminate\Database\Eloquent\Builder;
public function scopeBelongsToCurrentSeason(Builder $builder) {
$builder->whereHas('studentSeasons', function($query) {
$query->whereHas('season', function($query) {
$query->where('start', '<=', now());
$query->where('end', '>=', now());
});
});
}
scopeAgeBetween: Здесь мы можем использовать STR_TO_DATE
из mysql, и это помогает, что дата находится в начале номера социального страхования. Здесь я проигнорировал 7-й символ (тот, который указывает на век), так как mysql может автоматически заполнить правильный век. Но если у вас возникнут проблемы, используйте SUBSTR
и CASE для формирования строки, которую нужно преобразовать в дату.
//use Illuminate\Database\Eloquent\Builder;
public function scopeAgeBetween(Builder $builder, $lowAge, $highAge)
{
$builder->whereRaw(
"STR_TO_DATE(social_security, '%d%m%y') BETWEEN :start AND :end",
[
':start' => date('Y-m-d', strtotime('-'.$lowAge.' year')),
':end' => date('Y-m-d', strtotime('-'.$highAge.' year'))
]
);
}
Но я все же рекомендую вам создать дополнительное поле birthdate
и заполнить его в миграции (в той же миграции, что добавляет поле)
Хорошая вещь в скопах заключается в том, что как только вы добавите новое поле, вам нужно будет обновить код вашего скопа только один раз, и он будет работать, если вы не добавите обязательные параметры.
Ответ или решение
Как создать запрос с пользовательскими атрибутами в Laravel Eloquent
В данном ответе мы рассмотрим, как можно использовать пользовательские атрибуты в запросах Eloquent для модели Student
, основываясь на приведённой вами информации о расчете возраста на основе номера социального обеспечения (SSN).
1. Определение пользователского атрибута
Вы уже создали метод в вашей модели, который вычисляет возраст студента на основе SSN. Вот пример вашего кода:
public function getAgeAttribute(): int
{
if (!$this->deleted) {
$date = substr($this->social_security, 0, 6);
switch (substr($this->social_security, 6, 1)) {
case '-':
$yy = '19';
break;
case 'A':
$yy = '20';
break;
case '+':
$yy = '18';
break;
default:
return 0;
}
$birthday = new \DateTime($yy . substr($date, 4, 2) . '-' . substr($date, 2, 2) . '-' . substr($date, 0, 2));
$today = new \DateTime('today');
return $birthday->diff($today)->y;
}
return 0;
}
Этот метод корректно возвращает возраст. Однако, как правильно заметил пользователь, вычисляемые атрибуты не могут быть использованы непосредственно в условиях запроса на уровне базы данных.
2. Как сделать запросы с использованием SSN
Вместо того чтобы полагаться на атрибут age
, вы можете использовать поле social_security
для формирования запросов. Рассмотрим, как это можно сделать, чтобы получить количество студентов в определённом диапазоне возрастов.
2.1. Использование Scopes
Создание Scopes — это отличный способ упорядочить вашу модель и сделать запросы более читабельными. Давайте создадим два скоупа: один для проверки принадлежности к текущему сезону, а другой для фильтрации студентов по возрасту.
// Используйте пространство имен для Builder
use Illuminate\Database\Eloquent\Builder;
// Скоуп для проверки текущего сезона
public function scopeBelongsToCurrentSeason(Builder $builder) {
$builder->whereHas('studentSeasons', function($query) {
$query->whereHas('season', function($query) {
$query->where('start', '<=', now())
->where('end', '>=', now());
});
});
}
// Скоуп для фильтрации по возрасту
public function scopeAgeBetween(Builder $builder, $lowAge, $highAge)
{
$builder->whereRaw(
"STR_TO_DATE(social_security, '%d%m%y') BETWEEN :start AND :end",
[
':start' => date('Y-m-d', strtotime('-'.$highAge.' year')),
':end' => date('Y-m-d', strtotime('-'.$lowAge.' year'))
]
);
}
3. Итоговый запрос
Теперь вы можете использовать ваши новые скоупы в контроллере для получения количества студентов в заданном диапазоне возрастов. Ваш окончательный запрос будет выглядеть следующим образом:
$count = Student::where('deleted', 0)
->belongsToCurrentSeason()
->ageBetween($request->input('start'), $request->input('end'))
->count();
4. Рекомендации по улучшению
Хотя использование social_security
может работать, вы также можете рассмотреть возможность добавления поля birthdate
в вашу модель. Это упростит вычисление возраста, поскольку вам не придётся использовать сложные функции для преобразования строк. Добавление этого поля также обеспечит более высокую эффективность при выполнении запросов.
Следующие шаги:
- Создайте миграцию для добавления поля
birthdate
. - Заполните это поле на основе существующих данных SSN.
- Обновите ваши скоупы для фильтрации по
birthdate
, что будет значительно проще.
Заключение
Использование пользовательских атрибутов в запросах Eloquent в Laravel может быть ограниченным, однако, благодаря применению механизмов, таких как скоупы и низкоуровневая работа с базами данных, можно эффективно извлекать нужные данные. Надеюсь, данные рекомендации помогут вам оптимизировать ваш запрос и сделать его более читаемым и поддерживаемым.