Метка времени ресурса Laravel API "Вызов функции-члена format() при нулевом значении"

Я использую функциональность ресурсов API Laravel для удобного форматирования моих ответов для клиента, но проблема, с которой я столкнулся, связана с приведенным ниже кодом;

/**
  * Transform the resource collection into an array.
  *
  * @param  \Illuminate\Http\Request  $request
  * @return array
  */
public function toArray($request)
{
    return [
        'data' => $this->collection->transform(function ($item)
        {
            return [
                'id' => $item->id,
                'title' => Str::limit($item->title, 32),
                'body' => Str::limit($item->body, 32),
                'created_at' => $item->created_at->format('d M Y, H:i a'),
                'user' => $item->user
            ];
        }),
        'links' => [
            'current_page' => $this->currentPage(),
            'total' => $this->total(),
            'per_page' => $this->perPage(),
        ],

    ];
}

При использовании этого кода я получаю сообщение об ошибке; "Call to a member function format() on null" на created_at атрибут.

Но я уже использовал dd($this->collection) чтобы подтвердить, что ни один из атрибутов на самом деле не nullи я не совсем уверен, что могло быть причиной этого. Моя миграция содержит$table->timestamps();, а внутри моей фабрики я вообще не переопределяю временные метки, поэтому я не совсем уверен, в чем проблема.

Вот тест, который я провожу ниже, чтобы получить эту ошибку;

factory(News::class, 10)->create();

$user = factory(User::class)->create();

$this->actingAs($user)
    ->get('/news')
    ->assertOk()
    ->assertPropCount('news.data', 10)
    ->assertPropValue('news.data', function ($news)
    {
        $this->assertEquals(
            [
                'id', 'title', 'body', 'created_at',
                'user',
            ],
            array_keys($news[0])
        );
    });

Дополнительные функции, такие как assertPropCount а также assertPropValueполучены из демонстрационного приложения InertiaJS, поскольку я использую InertiaJS в своем проекте.

Надеюсь, кто-то сможет помочь, так как я спросил в нескольких других местах, и никто, похоже, не знает, в чем причина этого, и, основываясь на моей отладке, на самом деле, похоже, нет веского объяснения относительно ПОЧЕМУ created_at нулевой.

В качестве примечания, если я повернусь $item->user к $item->user->toArray() в коде тоже не жалуется, что userимеет значение null, когда это не так. Кажется, что попытка привязать любой метод к любому атрибуту вызывает эту нулевую ошибку, и я не уверен, почему.

1 ответ

Прежде всего имейте в виду, что transform функция, которую вы используете, измените исходную $this->collection собственность, вам лучше использовать mapвместо этого это служит той же цели, что и преобразование, без изменения исходного массива.
Это может быть связано с вашей проблемой, потому что вы изменяете коллекцию, которую повторяете, и это может вызвать проблемы.

Кроме того, я бы посоветовал вам продолжить чтение этого ответа и попробовать одну из двух альтернатив рефакторинга, которые я объяснил ниже. Это потому, что я думаю, что вы неправильно используете ресурсы API, и их правильное использование действительно может решить проблему.

Предложение по структуре ресурсов API
. Правильный способ - иметь два отдельных файла:News ресурс и NewsCollectionресурс.
Эта настройка позволяет определять структуру рендеринга отдельных новостей, а также коллекцию новостей и повторно использовать первые при рендеринге вторых.

Чтобы правильно реализовать ресурсы API, есть несколько способов (в зависимости от того, чего вы пытаетесь достичь):

Примечание: в обоих методах я считаю само собой разумеющимся, что у вас уже есть дополнительныйUser ресурс, который определяет структуру для рендеринга User модель ($this->user собственность News).

1) Создайте отдельные классы для отдельных ресурсов и ресурсов коллекции.
Вам необходимо создать два файла в папке ресурсов с помощью этих двух команд мастера:

// Create the single News resource
php artisan make:resource News

// Create the NewsCollection resource
php artisan make:resource NewsCollection

Теперь вы можете настроить логику сбора:

NewsCollection.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class NewsCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            // Each $this->collection array item will be rendered automatically
            // with the News resource definition, so you can leave data as it is
            // and just customize the links section/add more data as you wish.
            'data' => $this->collection,
            'links' => [
                'current_page' => $this->currentPage(),
                'total' => $this->total(),
                'per_page' => $this->perPage(),
            ],
        ];
    }
}

а также логика единого ресурса News:

News.php

<?php

namespace App\Http\Resources;

use App\Http\Resources\User as UserResource;
use Illuminate\Http\Resources\Json\JsonResource;

class News extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => Str::limit($this->title, 32),
            'body' => Str::limit($this->body, 32),
            'created_at' => $this->created_at->format('d M Y, H:i a'),
            'user' => new UserResource($this->user)
        ];
    }
}

Для рендеринга новостной коллекции вам нужно всего лишь:

use App\News;
use App\Http\Resources\NewsCollection;

// ...

return new NewsCollection(News::paginate());

Laravel автоматически повторно использует News класс ресурсов для отображения каждого отдельного элемента NewsCollectionс $this->collection массив, когда вы конвертируете экземпляр NewsCollection для ответа.

2) Используйте ::collectionметод единственного ресурса News.
Этот метод применим только в том случае, если вам нужны метаданные о разбиении на страницы ответов (похоже, это то, чего вы пытаетесь достичь с помощью своего кода).

Вам просто нужен единственный ресурс API новостей, который вы можете создать с помощью:

// Create the single News resource
php artisan make:resource News

Затем настройте единый ресурс в соответствии со своими потребностями:

News.php

<?php

namespace App\Http\Resources;

use App\Http\Resources\User as UserResource;
use Illuminate\Http\Resources\Json\JsonResource;

class News extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => Str::limit($this->title, 32),
            'body' => Str::limit($this->body, 32),
            'created_at' => $this->created_at->format('d M Y, H:i a'),
            'user' => new UserResource($this->user)
        ];
    }
}

Затем, чтобы отобразить коллекцию новостей с разбивкой на страницы, просто выполните:

use App\News;
use App\Http\Resources\News as NewsResource;

// ...

return NewsResource::collection(News::paginate());

Первый метод позволяет лучше контролировать результирующую структуру вывода, но я бы не стал структурировать $this->collectionвнутри класса коллекции.
Ответственность за определение того, как должен быть структурирован каждый элемент коллекции, принадлежитNews ресурсный класс.

Второй метод работает быстрее и отлично работает с разбиением на страницы в Laravel, что позволяет сэкономить время на генерацию ответов с разбивкой на страницы со ссылками (похоже, этого вы хотите достичь с помощью своего кода).

Извините за длинный пост, если вам нужны дополнительные объяснения, просто спросите.

Другие вопросы по тегам