(LARAVEL) Отношение HasManyThrough, вызывающее превышение «Разрешенного объема памяти» - PullRequest
0 голосов
/ 20 января 2020

Для простоты, давайте возьмем пример страницы вопросов stackoverflow , где есть разбитый на страницы список вопросов, и к каждому вопросу прикреплены некоторые теги.

A Метка модель имеет отношение ко многим ко многим с Вопрос модель, т. Е.

  • Метку можно назначить многим вопросам.
  • Вопрос может быть назначен многим тегам.

Для этого отношения я создал реляционную модель с именем QuestionTag (и таблицу для него), которая имеет отношение с и тег и вопрос. Затем я использовал отношение laravel hasManyThrough, чтобы получить список тегов, назначенных на вопрос через модель QuestionTag, как таковых:

class QuestionTag extends Model
{
    public function question()
    {
        return $this->belongsTo(Question::class, 'question_id', 'id');
    }

    public function tag()
    {
        return $this->belongsTo(Tag::class, 'tag_id', 'id');
    }
}

class Question extends Model
{
    public function tags()
    {
        return $this->hasManyThrough(Tag::class, QuestionTag::class, 'question_id', 'id', 'id', 'tag_id');
    }
}

И я создал QuestionResource для возврата ожидаемых разбитых на страницы результатов вопросов как:

QuestionResource

class QuestionResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'subject' => $this->subject,
            'body' => $this->body,
            'tags' => $this->tags // this will call the has many through relations as expected.
        ];
    }
}

Результат

{
    "current_page": 1,
    "data": [
        {
            "id": 1,
            "subject": "Lorem ipsum dolor sir amet!",
            "body": "...",
            tags: [
                {
                    "id": 1,
                    "name": "tag1",
                },
                // ...and so on
            ]
        },
        // ...and so on
    ],
    "first_page_url": "http://127.0.0.1:8000/uv1/questions?page=1",
    "from": 1,
    "last_page": 1,
    "last_page_url": "http://127.0.0.1:8000/uv1/questions?page=1",
    "next_page_url": null,
    "path": "http://127.0.0.1:8000/uv1/questions",
    "per_page": "15",
    "prev_page_url": null,
    "to": 5,
    "total": 5
}

При наконец, в индексной функции я вернул нумерованный список вопросов из QuestionController's index function следующим образом:

public function index(Request $request)
{
    $perPage = $request->input('perPage') ?? 15;
    // execute the query.
    $crawlers = Question::paginate($perPage);

    return QuestionResource::collection($crawlers);
}

Он вернул то, что хотел, но когда я увеличил размер per_page до 100 или больше , возвращается эта ошибка:

Допустимый объем памяти 134217728 байт исчерпан (попытался выделить 20480 байт)

Я нашел много решений, которые предлагают увеличить память в php .ini (memory_limit = 2048M), но кажется, что мы грубо пытаемся добиться результата. Будет некоторый момент, когда снова memory_limit не сможет вернуть то же самое, когда я продолжу увеличивать размер per_page.

Есть ли оптимальный способ в laravel, чтобы получить то же самое ожидаемое результат (вместо вышеупомянутой ошибки) с желаемым выводом без увеличения объема памяти?

1 Ответ

3 голосов
/ 20 января 2020

Я использовал Внутреннее соединение , чтобы достичь этого, и использовал MySQL JSON_ARRAYAGG(JSON_OBJECT()) вместе с ним в операторе SELECT, чтобы создать объединенную json строку тегов и затем преобразовать ее в * Массив 1023 * с использованием php json_decode(). Немного неаккуратно, но он быстро вернул результат, и я мог загрузить тысячи записей в течение миллисекунд.

My QuestionController теперь выглядит так:

public function index(Request $request)
{
    $perPage = $request->input('perPage') ?? 15;
    // execute the query.
    $crawlers = Question::query()
    ->select(['questions.*', DB::raw('JSON_ARRAYAGG(JSON_OBJECT("id", `tags`.`id`, "name", `tags`.`name`)) AS `tags`')])
    ->join('question_tags', 'questions.id', 'question_tags.question_id')
    ->join('tags', 'question_tags.tag_id', 'tags.id')
    ->groupBy('questions.id')
    ->paginate($perPage);

    return QuestionResource::collection($crawlers);
}

И я удалил присоединился к отношениям из моделей и изменил мой QuestionResource следующим образом:

class QuestionResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'subject' => $this->subject,
            'body' => $this->body,
            'tags' => json_decode($this->tags ?? '[]', false, 512, JSON_THROW_ON_ERROR), // convert string JSON to array
        ];
    }
}

В настоящее время я реализовал этот подход, но Я все еще открыт для лучших решений. :)

...