繁体   English   中英

Laravel - 用于使用 Blade 渲染视图的大量访问器(修改器)

[英]Laravel - Lot of Accessors (Mutators) for rendering Views with Blade

我有一个 Laravel 7 项目,需要在显示之前从 model 到视图进行大量数据转换。

我考虑过使用Laravel 访问器并直接在我的刀片中使用它们。php 文件。
但是当我处理完一个简单的 html 表时,我查看了我的代码,我认为访问器太多了,甚至其中一些访问器的名称难以阅读。

刀片视图

@foreach($races as $race)
<tr>
    <td>{{ $race->display_dates }}</td>
    <td>{{ $race->display_name }}</td>
    <td>{{ $race->type->name }}</td>
    <td>{{ $race->display_price }}</td>
    <td>{{ $race->display_places }}</td>
    <td>{{ $race->online_registration_is_open ? 'Yes' : 'No' }}</td>
</tr>
@endforeach

Controller

public function show(Group $group)
{
    $races = $group->races;
    $races->loadMissing('type'); // Eager loading
    return view('races', compact('races'));
}

Model

// Accessors
public function getOnlineRegistrationIsOpenAttribute()
{
    if (!$this->online_registration_ends_at && !$this->online_registration_starts_at) return false;
    if ($this->online_registration_ends_at < now()) return false;
    if ($this->online_registration_starts_at > now()) return false;
    return true;
}

public function getNumberOfParticipantsAttribute()
{
    return $this->in_team === true
        ? $this->teams()->count()
        : $this->participants()->count();
}

// Accessors mainly used for displaying purpose
public function getDisplayPlacesAttribute()
{
    if ($this->online_registration_ends_at < now()) {
        return "Closed registration";
    }
    if ($this->online_registration_starts_at > now()) {
        return "Opening date: " . $this->online_registration_starts_at;
    }
    return "$this->number_of_participants / $this->max_participants";
}

public function getDisplayPriceAttribute()
{
    $text = $this->online_registration_price / 100;
    $text .= " €";
    return $text;
}

public function getDisplayDatesAttribute()
{
    $text = $this->starts_at->toDateString();
    if ($this->ends_at) { $text .= " - " . $this->ends_at->toDateString(); }
    return $text;
}

public function getDisplayNameAttribute()
{
    $text = $this->name;
    if ($this->length) { $text .= " $this->length m"; }
    if ($this->elevation) { $text .= " ($this->elevation m)"; }
    return $text;
}

这段代码可以工作,但我认为它有很多缺点:可读性,错误是可能的,例如,如果关联的 DB 表有一个name列,而我在这里创建一个getDisplayNameAttribute访问器。
这只是一个开始,我认为其他视图还需要 30-40 个访问器……另外,我需要多次使用其中的一些,例如getDisplayNameAttribute可以在常规页面和管理页面中使用(也许更多)。

我还查看了JsonResourceViewComposer ,但JsonResource似乎适用于APIs ,而ViewComposer似乎特别适用于Views

我还考虑过在访问器前面加上acc_之类的前缀,以减少现有 db 列的错误:

public function getAccDisplayNameAttribute() { ... };

但我真的不认为这是一个解决方案,我什至不确定我所做的是对还是错。 我还在互联网上搜索了最佳实践,但没有成功。

对于这种情况,我有 2 个解决方案,我在本地机器上进行了测试。 虽然不确定他们是否符合最佳实践或设计原则,但必须看到,但我相信他们会以可管理的方式组织代码。

首先是您可以创建一个访问器,它是一个数组,并将您的所有逻辑都包含在其中,它将计算并返回数组。 这可能适用于 4-5 个属性,您必须更改视图才能访问数组而不是属性。 代码示例1如下

第二种方法是创建一个单独的 class ,它将包含所有不同的计算逻辑作为方法。 假设您制作了一个 ModelAccessor class,然后您可以在您的 Model class 中创建访问器,就像您现在所做的那样,并从每个内部返回 ModelAccessor->someMethod。 这将为您的 Model class 增加一些整洁,您可以轻松地从 class 方法管理您的计算逻辑。 下面的示例代码2可能会更清楚

  1. arrays 作为访问器的示例 1 让我们调用返回的属性$stats

public function getStatsAttribute(){

   $stats = [];


   if (!$this->online_registration_ends_at && !$this->online_registration_starts_at) $o=false;
    if ($this->online_registration_ends_at < now()) $o = false;
    if ($this->online_registration_starts_at > now()) $o = false;
    $o= true;

    $stats['online_registration_is_open'] = $o;

    $stats['number_of_participants'] = $this->in_team === true
        ? $this->teams()->count()
        : $this->participants()->count();

    return $stats;
}

您必须更改视图文件才能使用stats数组

<td>{{ $race->stats['number_of_participants'] }} </td>
<td>{{ $race->stats["online_registration_is_open"] ? 'Yes' : 'No' }}</td>

对于大量属性,这可能会变得混乱。 为避免这种情况,您可以将多个 arrays 分组相似的事物($stats、$payment_details、$race_registration 等),或者您可以使用单独的 class 来管理所有这些事物,如下例所示

  1. 示例 2,使用设置不同属性的方法分隔 class
class ModelAccessors
{
protected $race;

function __construct(\App\Race $race){

     $this->race = $race;
}

public function displayPrice()
{
    $text = $race->online_registration_price / 100;
    $text .= " €";
    return $text;
}

然后在model里面

public function getDisplayPriceAttribute()
{
    $m = new ModelAccessor($this);

    return $m->displayPrice();
}

使用它,您不必更新刀片文件

3. 如果您有 30-40 个访问器,那么我认为使用所有方法维护一个单独的 class 会简单得多。 除此之外,您可以从 class 本身创建属性数组并像这样调用它,

class ModelAccessors
{
protected $race;
protected $attributes;

function __construct(\App\Race $race){

     $this->race = $race;
     $this->attributes = [];

}

public function displayPrice()
{
    $text = $race->online_registration_price / 100;
    $text .= " €";
    $this->attributes['display_price'] = $text;
}

public function allStats(){

    $this->displayPrice();
    $this->someOtherMethod();
    $this->yetAnotherMethod();
    // ..
    // you can further abstract calling of all the methods either from 
    // other method or any other way

    return $this->attributes;
}
// From the model class
public function getStatsAccessor()
{
    $m = new ModelAccessor($this);

    // Compute the different values, add them to an array and return it as
    // $stats[
    //         "display_price"= "234 €",
    //         "number_of_participants" = 300;
    //  ]
    return $m->allStats() 
}

您可以创建专门处理您的转换的 class。 然后,您可以在 Model 构造函数中加载此 class 的实例。 如果你愿意,你也可以让你的访问器类使用 PHP 魔法吸气剂。

访问器 Class

class RaceAccessors {
    // The related model instance
    private $model;

    // Already generated results to avoid some recalculations
    private $attributes = [];

    function __construct($model)
    {
        $this->model = $model;
    }

    // Magic getters
    public function __get($key)
    {
        // If already called once, just return the result
        if (array_key_exists($key, $this->attributes)) {
            return $this->attributes[$key];
        }

        // Otherwise, if a method with this attribute name exists
        // Execute it and store the result in the attributes array
        if (method_exists(self::class, $key)) {
            $this->attributes[$key] = $this->$key($value);
        }

        // Then return the attribute value
        return $this->attributes[$key];
    }

    // An example of accessor 
    public function price()
    {
        $text = $this->model->online_registration_price  / 100;
        $text .= " €";
        return $text;
    }
}

Model Class

class Race {
    // The Accessors class instance
    public $accessors;

    function __construct()
    {
        $this->accessors = new \App\Accessors\RaceAccessors($this);
    }
}

看法

@foreach($races as $race)
<tr>
    <td>{{ $race->accessors->price }}</td>
    [...]
</tr>
@endforeach

我没有在这里为$model变量设置类型,因为您可以将相同的accessors class用于需要以与此访问器 class 相同的方式转换某些字段的其他模型。

例如,价格访问器可以用于 Race(在您的情况下),但它也可以用于其他模型,这样您就可以想象创建许多模型使用的访问器组,而无需更改太多代码来处理它。

访问和修改器是Laravel中的常规 getter 和 setter 函数。 这只是一种更“优雅”的说法。

我可以说,getter 和 setter 函数是为了让您更好地控制 class 中的变量,但这对理解它没有多大帮助。

那么它是干什么用的呢? 为什么要使用它?

我们已经熟悉 DRY 原则(不要重复自己),它指出重复是一种逻辑,应该消除。 或厨房用语; 不要两次编写相同的代码,因为它会无缘无故地占用您的时间。

那么这有什么帮助呢?

例如,您有一个拼写检查器 class,它正在使用服务器上的另一个应用程序 - 例如 hunspell。 对于小字符串,您可以直接使用它,当请求到来时,您可以通过使用 shell_exec 调用命令来处理请求,然后获得结果并返回 - 每个人都很高兴。

但是当文本太长时,php 将超过 30 秒(默认)限制,您将不会得到任何结果。 在这种情况下,您可以创建一个由守护进程执行的脚本,因此 30 秒的限制不会成为问题。 当请求到达时,您将使用 unix 套接字将文本传递给守护进程,然后后台进程为您进行拼写检查并将结果发回。

在这两种情况下,您都必须为拼写检查器 class 设置某些变量,这可能需要验证或格式化。 而且你不想开发它两次,因为它需要时间,弄得一团糟,并且增加了你可能搞砸的地方。

在某些情况下,当您需要使用准备工作来设置或获取数据的地方多于 2 个时,即使仅在 2 个地方也可以帮助避免代码重复。 这也有助于防止代码复杂性的增加。

这还不是全部!

有时你编写类,天知道谁会使用它。 也许它适用于公共 package。 那么当开发者拿到 package 时,他不想关心 email 将如何被验证。 在他从文件中获取变量之前,他不想知道为什么应该从字符串末尾删除特定变量的\n 他不想关心 integer 验证的实现部分。 他只是想使用package,尽快完成工作。

您对 setter 和 getter 所做的事情除了通过这些方法控制类变量来保护您的类变量之外别无其他。

有时您想将变量隐藏在 class 中,因此其他类不会依赖它们。 它使您可以灵活地更改实现和变量类型。

你应该总是使用它们吗?

编号Function 调用在 php 中很慢。 只需检查 Laravel 与原生 PHP 代码相比有多慢。 有时编写 getter 和 setter 完全没有意义,而且只是浪费时间。

那我应该什么时候使用呢?

当您需要限制对变量的访问时使用它。 为每个字段制作 getter 和 setter 是多余的。 这真的取决于情况,所以只在需要使用的地方使用它。

有关此主题的更多信息

Getter 和 Setter?

为什么要使用 getter 和 setter/accessor?

https://dev.to/scottshipp/avoid-getters-and-setters-whenever-possible-c8m

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM