我正在使用 Laravel 的 API 资源功能为客户端很好地格式化我的响应,但我遇到的麻烦在于下面的代码;
/**
* Transform the resource collection into an array.
*
* @param IlluminateHttpRequest $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
不是空。似乎尝试将任何方法链接到任何属性都会导致此空错误,我不确定为什么。
首先请记住,您使用的transform
函数会更改原始$this->collection
属性,您最好使用与转换相同的目的map
而不更改原始数组。
这可能与您的问题有关,因为您正在修改正在迭代的集合,这可能会导致问题。
此外,我建议您继续阅读此答案,并尝试我在下面解释的两种重构替代方案之一。那是因为我认为您没有正确使用 API 资源,正确使用它们实际上可以解决问题。
有关 API 资源结构的建议
正确的方法是有两个单独的文件:News
资源和NewsCollection
资源。
此设置允许定义单个新闻以及新闻集合的渲染结构,并在渲染后者时重用前者。
要正确实现 API 资源,有几种方法(基于您要实现的目标(:
注意:在这两种方法中,我理所当然地认为您已经有一个额外的User
资源,该资源定义了渲染User
模型的结构(News
的$this->user
属性(。
1( 为单个资源和集合资源创建单独的类您必须通过这两个工匠命令在资源
文件夹中创建两个文件:
// Create the single News resource
php artisan make:resource News
// Create the NewsCollection resource
php artisan make:resource NewsCollection
现在,您可以自定义集合逻辑:
新闻集.php
<?php
namespace AppHttpResources;
use IlluminateHttpResourcesJsonResourceCollection;
class NewsCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param IlluminateHttpRequest $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(),
],
];
}
}
以及单个新闻资源逻辑:
新闻.php
<?php
namespace AppHttpResources;
use AppHttpResourcesUser as UserResource;
use IlluminateHttpResourcesJsonJsonResource;
class News extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param IlluminateHttpRequest $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 AppNews;
use AppHttpResourcesNewsCollection;
// ...
return new NewsCollection(News::paginate());
当您转换 NewsCollection 实例进行响应时,Laravel将自动重用News
资源类来呈现NewsCollection
$this->collection
数组的每个元素。
2(利用单个新闻资源的::collection
方法 仅当您需要有关分页响应的元数据时,此方法才适用(这似乎是您尝试通过代码实现的目标(。
您只需要可以使用以下方法生成的单个新闻 api 资源:
// Create the single News resource
php artisan make:resource News
然后根据需要自定义单个资源:
新闻.php
<?php
namespace AppHttpResources;
use AppHttpResourcesUser as UserResource;
use IlluminateHttpResourcesJsonJsonResource;
class News extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param IlluminateHttpRequest $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 AppNews;
use AppHttpResourcesNews as NewsResource;
// ...
return NewsResource::collection(News::paginate());
第一种方法允许对生成的输出结构进行更好的整体控制,但我不会在集合类中构建$this->collection
。
定义每个集合元素的结构的责任属于News
资源类。
第二种方法更快,并且与Laravel分页配合得非常好,允许您节省相当多的时间来生成带有链接的分页响应(这似乎是您想要从代码中实现的(。
对不起,这篇文章很长,如果您需要进一步解释,请询问。