additionapps / flexible-presenter

轻松定义适用于您 InertiaJS 视图的最佳数据

v5.0.0 2024-04-26 08:48 UTC

README

Latest Version on Packagist GitHub Workflow Status StyleCI

轻松定义适用于您 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 视图的字段。当您从控制器方法中调用该类时,您可以使用 onlyexcept 等方法来定义在给定上下文中希望暴露哪些字段。关于 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\PaginatorIlluminate\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

配置演示者

有了新的演示者实例,您现在可以根据当前上下文以任何有意义的方式配置它。以下所有方法都可以附加到 makecollectionnew

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)。有关更多信息,请参阅 许可文件