Как создать запрос с пользовательскими атрибутами в Laravel Eloquent?

Вопрос или проблема

У меня есть модель студента, и в базе данных у меня есть номер социального страхования, из которого я вычисляю настраиваемый атрибут, чтобы получить возраст студента. Как я могу использовать его в запросах 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 в вашу модель. Это упростит вычисление возраста, поскольку вам не придётся использовать сложные функции для преобразования строк. Добавление этого поля также обеспечит более высокую эффективность при выполнении запросов.

Следующие шаги:

  1. Создайте миграцию для добавления поля birthdate.
  2. Заполните это поле на основе существующих данных SSN.
  3. Обновите ваши скоупы для фильтрации по birthdate, что будет значительно проще.

Заключение

Использование пользовательских атрибутов в запросах Eloquent в Laravel может быть ограниченным, однако, благодаря применению механизмов, таких как скоупы и низкоуровневая работа с базами данных, можно эффективно извлекать нужные данные. Надеюсь, данные рекомендации помогут вам оптимизировать ваш запрос и сделать его более читаемым и поддерживаемым.

Оцените материал
Добавить комментарий

Капча загружается...