定制 additionapps/flexible-presenter 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

additionapps/flexible-presenter

Composer 安装命令:

composer require additionapps/flexible-presenter

包简介

Easily define just the right data for your InertiaJS views

README 文档

README

Latest Version on Packagist GitHub Workflow Status StyleCI

Easily define just the right data for your Inertia views (or anywhere else you want to, uh, flexibly present).

This package allows you to define presenter classes that take logic involved in getting data ready for your view layer out of your controller. It also provides an expressive, fluent API that allows you to modify and reuse your presenters on the fly so that you're only ever providing relevant data.

This package was built specifically for use with Inertia (:heart_eyes:) because we didn't like the fact we were sending more data than we needed to our views. That said, you're free to use it however you please - it's not dependant on Inertia in any way.

Installation

You can install the package via composer:

composer require additionapps/flexible-presenter

Usage

The package includes an artisan command to create a new presenter:

php artisan make:presenter PostsPresenter

This presenter will have the App\Presenters namespace and will be saved in app/Presenters.

You can also specify a custom namespace, say, App\Blog

php artisan make:presenter "Blog/Presenters/PostsPresenter"

This presenter will have the App\Blog\Presenters namespace and will be saved in app/Blog/Presenters.

Defining values

A presenter is a class in which you can define all the possible fields that you might want to expose to your Inertia views. When you call the class from a controller method you can use methods such as only or except to define which of these fields you want to expose in that given context. More on the API in a bit.

The only required method in a presenter class is values() which should return an array with all the possible fields you might want to display in a view. These could simply be values directly on your model. Note that you can access model properties directly from the $this variable, just as you can when using Laravel API Resources.

For now, here's a simple presenter class:

class PostPresenter extends FlexiblePresenter
{
    public function values()
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'body' => $this->body,
        ];
    }
}

Once a presenter is defined, you are free to return it as part of an Inertia response or in any other context that you need the presented data:

class PostController extends Controller
{
    public function show(Post $post)
    {
        return Inertia::render('Posts/Show', [
            'post' => PostPresenter::make($post)->get(),
        ]);
    }
}

Lazy evaluation

You can also define fields that are computed in more complex ways - for example you may want to add a presenter value that is derived from some other data like a relationship. In these situations it can be handy to wrap your value definition in a closure. Doing so will ensure that the value is only computed if it's actually asked for:

public function values()
{
    return [
        'id' => $this->id,
        'comment_count' => function () {
            return $this->comments->count();
        },
    ];
}

If you are using PHP >= 7.4 you can make things a little more readable by using the new short closure syntax:

[
    'comment_count' => fn() => $this->comments->count(),
];

Now if we call PostPresenter::make($post)->only(['id']) the comment_count value will not be evaluated meaning we do not need to worry about ensuring that relationship is loaded on the model.

Nested presenters

If necessary you can define a value that returns another presenter. This is handy if you want to only return certain values for a relation of the model you're working with:

public function values()
{
    return [
        'id' => $this->id,
        'comments' => function () {
            return CommentPresenter::collection($this->comments)
                ->except('body', 'updated_at');
        },
    ];
}

Note that in the example above we're using lazy evaluation so that we don't have to worry about the comments relationship being loaded on the Post model. If you know this relationship will always be loaded you could dispense with the closure.

Instantiating presenters

Once you have defined your presenter class you can use it in your controller (or elsewhere) in your application. There are three methods for creating a new presenter instance:

PostPresenter::make($post)

The make method accepts a single resource as a parameter. In the majority of cases this will be an Eloquent model but there is no requirement that you pass a model specifically. For instance you could pass a some other object or even an associative array and then within the values() method in your presenter use it like so:

public function values()
{
    return [
        'id' => $this->resource['id'],
        'title' => $this->resource['title'],
    ];
}

PostPresenter::collection($posts)

The collection method accepts an Eloquent collection (or a plain array) of resources as a parameter. Each member of the collection will be transformed by the presenter as specified. Again the members of that collection can be Eloquent models, other objects or arrays:

$posts = PostPresenter::collection(Post::all())
    ->only('id', 'title')
    ->get();

Using Pagination

As well as passing an Eloquent collection or array, you can also pass a Laravel paginator instance. You are free to pass an instance of either Illuminate\Pagination\Paginator or Illuminate\Pagination\LengthAwarePaginator. You can also pass a custom paginator as long as it extends the Illuminate\Pagination\AbstractPaginator class and implements the Illuminate\Contracts\Support\Arrayable interface.

Here's an example of a presenter used with an eloquent collection using simple pagination:

$posts = PostPresenter::collection(Post::simplePaginate())
    ->only('id', 'title')
    ->get();

Which will output:

[
    '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()

The new method accepts no parameters. This method is useful for when you have no resource or collection to pass into your presenter (perhaps because the presenter itself is responsible for gathering the resources it needs).

If one of your presenters has a key should return a presented relation as a value, you can use the convenience method whenLoaded to conditionally include the relation:

// In PostPresenter
return [
    'comments' => CommentPresenter::collection($this->whenLoaded('comments')),
];

In the above example the comments key will be a collection of presented comments if the relation is loaded and null if they are not.

Configuring presenters

With a new presenter instance you are now free to configure it in whatever way makes sense for your the current context. All the following methods can be chained onto make(), collection() or new().

only()

The only method accepts keys that you want to return from your presenter instance. You can pass these keys as an array (->only(['id', 'title'])) or as individual string arguments (->only('id', 'title')).

except()

The except method accepts keys that you want to exclude from your presenter instance. You can pass these keys as an array (->except(['id', 'title'])) or as individual string arguments (->except('id', 'title')).

with()

The with method accepts a closure that takes as it's only argument the resource that is being transformed. You can use the with method to overwrite the values of existing keys or to add entirely new keys to your presenter on-the-fly. It should return an array with the keys you want to overwrite/add along with their values.

PostPresenter::make($post)->with(function($post){
    return [
        'title' => strtoupper($post->title),
        'new_key' => 'Some value',
    ];
});

preset()

You may find that there are combinations of presenter values that you use repeatedly throughout your application. If so, rather than explicitly asking for those particular fields each time you can add a 'preset' to your presenter class and ask for that preset instead:

PostPresenter::make($post)->preset('summary');

Within your presenter class you should create a method with the name of your preset, prefixed with 'preset'. Given the example above we would create a method like this:

public function presetSummary()
{
    return $this->only('title', 'published_at');
}

Your preset method should use the same presenter API methods above to build up a set of values. Note that all of these API methods can be chained, so, for example, you can modify a preset on the fly if you wish:

PostPresenter::make($post)->preset('summary')->with(function () {
    return [
        'comment_count' => $post->comments->count(),
    ];
});

Caveat: repeated method calls

Just bear in mind that if you use an API method in your preset method (for example only) and then chain another only method onto it when using your presenter, that the last call to only will be the one to take effect:

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

If you are presenting a paginated collection of resources you may want to add some additional key/value pairs to the outer array that wraps your data. To do this, you can use the appends() method to specify the keys and values you wish to set. Let's say you're presenting a custom paginator instance that produces this output:

[
    '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',
    ],
];

Using appends you are free to add (or overwrite) the keys in this array:

$posts = PostPresenter::collection($paginatedCollection)
    ->only('id')
    ->appends([
        'foo' => 'bar',
        'links' => ['store' => 'some-other-url'],
    ])
    ->get();

This will now output:

[
    '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',
    ],
];

Note that appended values are merged recursively (as in the links example above).

Returning values

Once you've configured an instantiated presenter the way you want, you can get the values as an array by chaining the get method:

PostPresenter::make($post)->only('id', 'title')->get();

If you wish to return all the defined values in your presenter (without any configuration) then you can use the all method:

PostPresenter::make($post)->all();

This is equivalent to the following:

PostPresenter::make($post)->get();

Finally, all presenters implement the Arrayable interface, so you are passing your presenter to a context that looks for this contract, your presenter values will be automatically converted to an array without you having to use get() (or all()).

Here's an example using an Inertia response:

return Inertia::render('Posts/Show', [
    'post' => PostPresenter::make($post)->only('title'),
]);

In the example above we're defining our Inertia props manually in an array. You can also pass a presenter instance directly - it will be converted into an array of props automatically:

return Inertia::render('Posts/Show', PostPresenter::make($post)->only('title'));

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email john@addition.com.au instead of using the issue tracker.

Treeware

You're free to use this package, but if it makes it to your production environment we would highly appreciate you buying or planting the world a tree.

It’s now common knowledge that one of the best tools to tackle the climate crisis and keep our temperatures from rising above 1.5C is to plant trees. If you contribute to my forest you’ll be creating employment for local families and restoring wildlife habitats.

You can buy trees at Offset Earth

Read more about Treeware at Treeware

Credits

John Wyles

All Contributors

License

The MIT License (MIT). Please see License File File for more information.

additionapps/flexible-presenter 适用场景与选型建议

additionapps/flexible-presenter 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 98.91k 次下载、GitHub Stars 达 123, 最近一次更新时间为 2020 年 02 月 28 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 additionapps/flexible-presenter 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 additionapps/flexible-presenter 我们能提供哪些服务?
定制开发 / 二次开发

基于 additionapps/flexible-presenter 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 98.91k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 123
  • 点击次数: 16
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 123
  • Watchers: 5
  • Forks: 9
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2020-02-28