additionapps / flexible-presenter
轻松定义适用于您 InertiaJS 视图的最佳数据
Requires
- php: ^8.1
- illuminate/pagination: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- fakerphp/faker: ^1.12
- laravel/legacy-factories: ^1.0
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^9.0|^10.0
README
轻松定义适用于您 Inertia 视图的最佳数据(或任何其他您想灵活展示的地方)。
此包允许您定义用于从视图层获取数据的逻辑的展示器类,从而将控制器中的逻辑与控制器分离。它还提供了一个表达性强、流畅的 API,允许您动态地修改和重用展示器,以便您始终只提供相关数据。
此包专门为与 Inertia (:heart_eyes:) 使用而构建,因为我们不喜欢发送比我们需要的更多数据到视图。话虽如此,您可以随意使用它 - 它在任何方面都不依赖于 Inertia。
安装
您可以通过 composer 安装此包
composer require additionapps/flexible-presenter
用法
该包包含一个 artisan 命令来创建新的展示器
php artisan make:presenter PostsPresenter
此展示器将具有 App\Presenters
命名空间,并保存在 app/Presenters
中。
您还可以指定自定义命名空间,例如 App\Blog
php artisan make:presenter "Blog/Presenters/PostsPresenter"
此展示器将具有 App\Blog\Presenters
命名空间,并保存在 app/Blog/Presenters
中。
定义值
展示器是一个类,您可以在其中定义所有可能希望暴露给 Inertia 视图的字段。当您从控制器方法中调用该类时,您可以使用 only
或 except
等方法来定义在给定上下文中希望暴露哪些字段。关于 API 的更多信息稍后提供。
展示器类中唯一必需的方法是 values()
,它应返回一个包含所有可能希望显示在视图中的字段的数组。这些可以简单地是模型上的直接值。请注意,您可以直接从 $this
变量访问模型属性,就像使用 Laravel API 资源 一样。
现在,这里有一个简单的展示器类
class PostPresenter extends FlexiblePresenter { public function values() { return [ 'id' => $this->id, 'title' => $this->title, 'body' => $this->body, ]; } }
一旦定义了展示器,您就可以将其作为 Inertia 响应的一部分或在任何需要展示数据的上下文中返回它
class PostController extends Controller { public function show(Post $post) { return Inertia::render('Posts/Show', [ 'post' => PostPresenter::make($post)->get(), ]); } }
懒加载
您还可以定义以更复杂的方式计算的字段 - 例如,您可能想要添加一个从某些其他数据(如关系)派生的展示器值。在这些情况下,将值定义包裹在闭包中可能会有所帮助。这样做将确保只有在实际需要时才会计算值。
public function values() { return [ 'id' => $this->id, 'comment_count' => function () { return $this->comments->count(); }, ]; }
如果您正在使用 PHP >= 7.4,您可以通过使用新的短闭包语法使事情更加易读
[ 'comment_count' => fn() => $this->comments->count(), ];
现在,如果我们调用 PostPresenter::make($post)->only(['id'])
,则 comment_count
值将不会计算,这意味着我们不需要担心确保加载模型上的关系。
嵌套展示器
如果需要,您可以为返回另一个展示器的值定义。如果您只想为正在处理的模型的某个关系返回某些值,这将非常有用。
public function values() { return [ 'id' => $this->id, 'comments' => function () { return CommentPresenter::collection($this->comments) ->except('body', 'updated_at'); }, ]; }
请注意,在上面的示例中,我们正在使用懒加载,这样我们就不必担心在 Post
模型上加载 comments
关系。如果您知道此关系将始终加载,则可以省略闭包。
实例化演示者
定义了您的演示者类后,您可以在应用程序的控制器(或其他地方)中使用它。创建新的演示者实例有三种方法
PostPresenter::make($post)
make
方法接受一个资源作为参数。在大多数情况下,这将是 Eloquent 模型,但您无需传递特定于模型的参数。例如,您可以传递其他对象或关联数组,然后在您的演示者中的 values()
方法中使用它
public function values() { return [ 'id' => $this->resource['id'], 'title' => $this->resource['title'], ]; }
PostPresenter::collection($posts)
collection
方法接受一个 Eloquent 集合(或普通数组)的资源作为参数。集合中的每个成员都将按指定方式由演示者转换。同样,该集合的成员可以是 Eloquent 模型、其他对象或数组
$posts = PostPresenter::collection(Post::all()) ->only('id', 'title') ->get();
使用分页
除了传递 Eloquent 集合或数组外,您还可以传递 Laravel 分页器实例。您可以传递 Illuminate\Pagination\Paginator
或 Illuminate\Pagination\LengthAwarePaginator
的实例。只要它扩展了 Illuminate\Pagination\AbstractPaginator
类并实现了 Illuminate\Contracts\Support\Arrayable
接口,您也可以传递自定义分页器。
以下是一个使用简单分页与 Eloquent 集合一起使用的演示者示例
$posts = PostPresenter::collection(Post::simplePaginate()) ->only('id', 'title') ->get();
这将输出
[ 'current_page' => 1, 'data' => [ [ 'id' => 1, 'title' => 'foo', ], [ 'id' => 2, 'title' => 'bar', ], ], 'first_page_url' => 'http://example.com/list?page=1', 'from' => 1, 'next_page_url' => null, 'path' => 'http://example.com/list', 'per_page' => 2, 'prev_page_url' => null, 'to' => 2, ];
PostPresenter::new()
new
方法不接受任何参数。此方法在您没有资源或集合传递到您的演示者时很有用(例如,因为演示者本身负责收集所需的资源)。
如果您的演示者之一有一个键应返回一个演示的关联作为值,您可以使用便利方法 whenLoaded
条件性地包含该关联
// In PostPresenter return [ 'comments' => CommentPresenter::collection($this->whenLoaded('comments')), ];
在上面的示例中,如果关联已加载,则 comments
键将是一个包含演示评论的集合,如果没有,则为 null
。
配置演示者
有了新的演示者实例,您现在可以根据当前上下文以任何有意义的方式配置它。以下所有方法都可以附加到 make
、collection
或 new
。
only()
only
方法接受您想要从演示者实例中返回的键。您可以将这些键作为数组(->only(['id', 'title'])
)或单个字符串参数(->only('id', 'title')
)传递。
except()
except
方法接受您想要从演示者实例中排除的键。您可以将这些键作为数组(->except(['id', 'title'])
)或单个字符串参数(->except('id', 'title')
)传递。
with()
with
方法接受一个闭包,该闭包仅接受一个参数,即正在转换的资源。您可以使用 with
方法覆盖现有键的值或动态地向演示者添加新键。它应返回一个包含您想要覆盖/添加的键及其值的数组。
PostPresenter::make($post)->with(function($post){ return [ 'title' => strtoupper($post->title), 'new_key' => 'Some value', ]; });
preset()
您可能会发现,在整个应用程序中重复使用演示者值组合。如果是这样,您无需每次都显式请求特定的字段,而是可以在演示者类中添加一个“预设”,然后请求该预设。
PostPresenter::make($post)->preset('summary');
在您的演示者类中,您应该创建一个以您的预设命名的命名方法,前缀为“preset”。以示例为例,我们会创建一个类似这样的方法
public function presetSummary() { return $this->only('title', 'published_at'); }
您的预设方法应使用上述相同的演示者 API 方法来构建一组值。请注意,所有这些 API 方法都可以链接,因此,例如,如果您愿意,您可以在运行时修改预设。
PostPresenter::make($post)->preset('summary')->with(function () { return [ 'comment_count' => $post->comments->count(), ]; });
注意事项:重复的方法调用
请记住,如果在预设方法(例如 only
)中使用 API 方法,然后在使用演示者时将其链式附加另一个 only
方法,则最后一个 only
调用将生效。
// In PostPresenter... public function presetSummary() { return $this->only('title', 'body'); } // In Controller... PostPresenter::make($post)->preset('summary'); // Will return ['title' => 'foo', 'body' => 'bar'] PostPresenter::make($post)->preset('summary')->only('id'); // Will return ['id' => 1]
appends()
如果您正在展示分页的资源集合,您可能希望在包装您数据的顶层数组中添加一些额外的键/值对。为此,您可以使用 appends()
方法来指定要设置的键和值。假设您正在展示一个自定义分页实例,该实例生成以下输出:
[ 'current_page' => 1, 'data' => [ // your presented resources ], 'first_page_url' => 'http://example.com/list?page=1', 'from' => 1, 'next_page_url' => null, 'path' => 'http://example.com/list', 'per_page' => 2, 'prev_page_url' => null, 'to' => 2, 'links' => [ 'create' => 'some-url', ], ];
使用 appends,您可以自由地添加(或覆盖)此数组中的键
$posts = PostPresenter::collection($paginatedCollection) ->only('id') ->appends([ 'foo' => 'bar', 'links' => ['store' => 'some-other-url'], ]) ->get();
这将输出以下内容
[ 'current_page' => 1, 'data' => [ // your presented resources ], 'first_page_url' => 'http://example.com/list?page=1', 'from' => 1, 'next_page_url' => null, 'path' => 'http://example.com/list', 'per_page' => 2, 'prev_page_url' => null, 'to' => 2, 'foo' => 'bar', 'links' => [ 'create' => 'some-url', 'store' => 'some-other-url', ], ];
注意,附加的值是递归合并的(如上例中的 links
)。
返回值
一旦您按所需的方式配置了实例化的展示器,您可以通过链式调用 get
方法来获取值作为数组
PostPresenter::make($post)->only('id', 'title')->get();
如果您希望返回展示器中定义的所有值(无需任何配置),则可以使用 all
方法
PostPresenter::make($post)->all();
这相当于以下内容
PostPresenter::make($post)->get();
最后,所有展示器都实现了 Arrayable
接口,因此当您将展示器传递给寻找此契约的上下文时,您的展示器值将自动转换为数组,而无需使用 get()
(或 all()
)。
以下是一个使用 Inertia 响应的示例
return Inertia::render('Posts/Show', [ 'post' => PostPresenter::make($post)->only('title'), ]);
在上面的示例中,我们手动在数组中定义了我们的 Inertia props。您也可以直接传递展示器实例 - 它将自动转换为属性数组
return Inertia::render('Posts/Show', PostPresenter::make($post)->only('title'));
更新日志
有关最近更改的更多信息,请参阅 更新日志
贡献
有关详细信息,请参阅 贡献指南
安全性
如果您发现任何与安全相关的问题,请通过电子邮件 john@addition.com.au 反馈,而不是使用问题跟踪器。
实物捐赠
您可以使用此包,但如果它进入您的生产环境,我们非常希望您购买或为世界种一棵树。
现在众所周知,解决气候危机并防止我们的气温上升超过1.5C的最好工具之一是种树。如果您为我的森林做出贡献,您将为当地家庭创造就业机会并恢复野生动物栖息地。
您可以在 Offset Earth 购买树木
有关实物捐赠的更多信息,请参阅 实物捐赠
鸣谢
John Wyles
所有贡献者
许可协议
MIT 许可协议(MIT)。有关更多信息,请参阅 许可文件