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);
}
}
推荐文章: