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

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


person Borassign    schedule 01.01.2020    source источник
comment
Вы определили created_at внутри свойства $ fillable модели?   -  person Chirag Khatri    schedule 01.01.2020
comment
@senty На фабриках он их создает по умолчанию. В любом случае я попробовал и не изменил ошибку.   -  person Borassign    schedule 01.01.2020
comment
@ChiragKhatri Да, уже пробовал. Не имело значения.   -  person Borassign    schedule 01.01.2020
comment
Если вы создаете фиктивную модель User (без Faker), будут ли поля меток времени пустыми?   -  person senty    schedule 01.01.2020
comment
@sentry Я не совсем понимаю, что вы имеете в виду. Я использую 'user_id' => factory(User::class), для создания своих пользователей в NewsFactory.   -  person Borassign    schedule 01.01.2020


Ответы (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.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 единственного ресурса новостей
Этот метод применим только в том случае, если вам нужны метаданные о разбиении на страницы ответов (похоже, это то, чего вы пытаетесь достичь с помощью своего кода).

Вам просто нужен единственный ресурс 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, что позволяет сэкономить время на генерацию разбитых на страницы ответов со ссылками (похоже, этого вы хотите добиться с помощью своего кода).

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

person mdexp    schedule 01.01.2020