【问题标题】:Laravel API Resource timestamp "Call to a member function format() on null"Laravel API 资源时间戳“在 null 上调用成员函数 format()”
【发布时间】:2020-01-01 18:09:42
【问题描述】:

我正在使用 Laravel 的 API Resource functionality 为客户很好地格式化我的回复,但我遇到的问题是下面的代码;

/**
  * 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])
        );
    });

assertPropCountassertPropValue 等额外功能来自 InertiaJS 演示应用程序,因为我在项目中使用了 InertiaJS

希望有人能够提供帮助,正如我在其他几个地方询问过的那样,似乎没有人知道这是什么原因,并且根据我的调试,这似乎不是一个有效的关于为什么created_at 为空的解释。

请注意,如果我在代码中也将$item->user 转换为$item->user->toArray(),那么抱怨user 不是空时也会失败。似乎尝试将任何方法链接到任何属性都会导致此 null 错误,我不知道为什么。

【问题讨论】:

  • 你是否在Model的$fillable属性中定义了created_at
  • @senty 在工厂上默认创建它们。无论哪种方式,我都尝试过并没有改变错误。
  • @ChiragKhatri 是的,已经尝试过了。没有什么不同。
  • 如果你创建一个 dummy User 模型(没有 Faker),时间戳字段是否为空?
  • @sentry 我不太清楚你的意思。我正在使用 'user_id' => factory(User::class), 在 NewsFactory 上生成我的用户。

标签: php laravel phpunit inertiajs


【解决方案1】:

首先请记住,您正在使用的 transform 函数会更改原始的 $this->collection 属性,您最好使用 map 而不是在不更改原始数组的情况下与转换相同的目的。
这可能与您的问题有关,因为您正在修改要迭代的集合,这可能会导致问题。

此外,我建议您继续阅读此答案并尝试我在下面解释的两个重构替代方案之一。那是因为我认为您没有正确使用 API 资源,而正确使用它们实际上可以解决问题。

关于您的 API 资源结构的建议
正确的方法是拥有两个单独的文件:News 资源和 NewsCollection 资源。
此设置允许定义单个新闻的呈现结构以及新闻的集合,并在呈现后者的同时重用前者。

要正确实现 API 资源,有几种方法(根据您要实现的目标):

注意:在这两种方法中,我想当然地认为您已经有一个额外的 User 资源,它定义了呈现 User 模型的结构(@ 的 $this->user 属性987654329@)。

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());

当您将 NewsCollection 实例转换为响应时,Laravel 将自动重用 News 资源类来呈现 NewsCollection$this-&gt;collection 数组的每个元素。

2) 利用单个 News 资源的::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-&gt;collection
News 资源类负责定义每个集合元素的结构。

第二种方法更快,并且非常适合 Laravel 分页,让您节省相当多的时间来生成带有链接的分页响应(这似乎是您希望从代码中实现的目标)。

抱歉,帖子太长了,如果您需要进一步解释,请询问。

【讨论】:

    猜你喜欢
    • 2020-02-29
    • 2020-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-04
    • 2016-06-29
    • 2018-04-03
    • 2021-10-06
    相关资源
    最近更新 更多