Whip Monstrous Code Into Shape 11-Consider View Models

When you find yourself repeating the same view logic over and over, it might be time to extract that code into a reusable method. At first, developers typically reach for the model. However, you may quickly decide that you'd prefer a more dedicated class for this sorts of logic. In such cases, consider creating view presenters - or as some refer to them: view models.


这节视频讲的是使用 view present,或者说 view model。

有些情况下,我们数据库中存储的内容和视图需要展现的内容会有差异,比如说 users 表,很多在设计的时候会设计 first_name 和 last_name,但是视图中我们希望展现的是完整的名称;再比如电话号码,数据库中可能会存储国家码,区号以及电话号码,但是视图中我们希望展现的是一个 "+86-0755-12345678" 这种格式的电话号码。最简单的方式就是在视图中拼接了。

{{ $user->first_name . $user->last_name }}
{{ $user->countryCode . '-' . $user->siteCode . '-' . $user->phone }}

这种方式在少量的时候可以使用,如果在视图中到处是这种代码的话,这个时候可以考虑在模型中添加可以复用的代码。

public function fullName()
{
    return $this->first_name . $this->last_name;
}

public function phoneToHuman()
{
    return $this->countryCode . '-' . $this->siteCode . '-' . $this->phone;
}

这个时候可以在视图中通过 {{ $user->fullName() }} 来使用。

字段比较少的时候可以这样处理,没什么问题。问题是,当模型中有大量的字段需要处理,把这些视图逻辑都放在模型中不是一个很好的选择,模型的业务逻辑和视图逻辑混杂,这个时候可能要考虑使用 view present,也可以叫 view model.

present 是在我们常说的 MVC 模型的 view 层上面增加的一个表现层,通常用来处理一些视图逻辑。Laravel 有很多 present 的包,比如我用过一个比较强大的包 "league/fractal"

这个包我觉得用在 api 服务器上比较强大,可以转换不同的资源,比如单个模型(Item)或者模型集合(Collection);可以自定义不同的转换器(Transformer,即不同的转换规则);可以选择不同的序列方式,比如 api 服务器经常返回的 json 数据会用 {data:{...}} 来包裹,你也可以自定义序列方式;另外针对分页有个专门的处理方式。

越强大带来的就是越复杂,一些小项目中使用反而没什么优势,比如说 User 模型,你可能只是想转换少量的字段,但是在 Transformer 中你不得不把其他字段都写进去,要不 present 就会忽略这些,这就增加了额外的工作;另外一个因为转换后是数组形式,用在 laravel 视图中不是特别方便,所以,下面介绍自定义一个简单的 present 的方法。

思路很简单,把这些视图逻辑放到一个新的类里面就行,然后在模型中返回这个模型。首先在模型中添加 present 方法。

public function present()
{
    return new UserPresent($this);
}

创建 present 类,把视图逻辑写在里面。

class UserPresent
{
    /**
     * @var User
     */
    private $user;

    /**
     * UserPresent constructor.
     * @param User $this
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function fullName()
    {
        return $this->user->first_name . $this->user->last_name;
    }

    public function phoneToHuman()
    {
        return $this->user->countryCode . '-' . $this->user->siteCode . '-' . $this->user->phone;
    }
}

在试图里面就可以很简单的使用了 $user->present()->fullName(),后续的视图逻辑添加在里面就行了。如果觉得想类似的通过属性访问,可以在 present 里面添加 __get 魔术方法。

public function __get($property)
{
    if (method_exists($this, $property)) {
        return call_user_func([$this, $property]);
    }

    $message = '%s does not respond to the "%s" property or method.';

    throw new \Exception(sprintf($message, static::class, $property));
}

这个时候就可以$user->present()->fullName这样使用了。如果在项目中需要使用的地方比较多,也可以把复用部分抽象成一个抽象类。

abstract class Present {
    /**
     * @var Model
     */
    protected $model;

    /**
     * Present constructor.
     * @param Model $model
     */
    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function __get($property)
    {
        if (method_exists($this, $property)) {
            return call_user_func([$this, $property]);
        }

        $message = '%s does not respond to the "%s" property or method.';

        throw new \Exception(sprintf($message, static::class, $property));
    }
}

class UserPresent extends Present
{
    public function fullName()
    {
        return $this->model->first_name . $this->model->last_name;
    }

    public function phoneToHuman()
    {
        return $this->model->countryCode . '-' . $this->model->siteCode . '-' . $this->model->phone;
    }
}

甚至模型的 present 方法也可以创建一个 Presentable 的 trait 来复用。

trait Presentable {
    public function present()
    {
        return new (static::class.'Present')($this);
    }
}
本帖已被设为精华帖!
本帖由 Summer 于 7年前 加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!