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

Posted

技术标签:

【中文标题】Laravel - 大量使用 Blade 渲染视图的访问器(修改器)【英文标题】:Laravel - Lot of Accessors (Mutators) for rendering Views with Blade 【发布时间】:2020-08-23 01:28:26 【问题描述】:

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

我考虑过使用Laravel Accessors 并直接在我的blade.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

控制器

public function show(Group $group)

    $races = $group->races;
    $races->loadMissing('type'); // Eager loading
    return view('races', compact('races'));

型号

// 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;

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

我还查看了JsonResource 和ViewComposer,但JsonResource 似乎是针对APIsViewComposer 似乎是特别针对Views

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

public function getAccDisplayNameAttribute()  ... ;

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

【问题讨论】:

为什么不直接使用标准的 php getter? 抱歉,我需要说明为什么我应该使用 getter 或其他东西,以及为什么它更好。 虽然 JsonResource 是为 API 设计的,但是这个概念可以起到类似的作用。您只需要像 DTO(数据传输对象)一样拥有它,它可以调解您的数据(模型)和响应。如果访问器很少,则可以使用访问器,但如果这些逻辑专用于其他类来处理,则绝对是可维护的。 【参考方案1】:

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

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

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

    数组作为访问器的示例 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>

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

    示例 2,使用设置不同属性的方法分隔类
class ModelAccessors

protected $race;

function __construct(\App\Race $race)

     $this->race = $race;


public function displayPrice()

    $text = $race->online_registration_price / 100;
    $text .= " €";
    return $text;


然后在模型内部

public function getDisplayPriceAttribute()

    $m = new ModelAccessor($this);

    return $m->displayPrice();

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

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

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

【讨论】:

【参考方案2】:

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

访问器类

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;
    

模型类

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 用于需要以与此访问器类相同的方式转换某些字段的其他模型。

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

【讨论】:

【参考方案3】:

Accessorsmutators 是 Laravel 中的常规 getter 和 setter 函数。这只是一种更“优雅”的表达方式。

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

那么它有什么用呢?为什么要使用它?

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

那么这有什么帮助呢?

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

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

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

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

这还不是全部!

有时你编写类,天知道谁会使用它。也许它是一个公共包。然后,当开发人员收到包时,他不想关心电子邮件将如何验证。他不想知道为什么在他从文件中获取变量之前,应该从字符串末尾删除特定变量的\n。他不想关心整数验证的实现部分。他只是想用这个包,尽快完成工作。

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

有时你想在你的类中隐藏你的变量,这样其他类就不会依赖它们。它使您可以灵活地更改实现和变量类型。

你应该一直使用它们吗?

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

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

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

更多关于这个话题

Getter and Setter?

Why use getters and setters/accessors?

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

【讨论】:

以上是关于Laravel - 大量使用 Blade 渲染视图的访问器(修改器)的主要内容,如果未能解决你的问题,请参考以下文章

laravel Blade 模板引擎

Laravel Blade 模板引擎会影响性能吗?

如何使用Laravel Blade模板呈现以下类型的表?

如何在 Laravel 中渲染网页和移动视图

如何在 laravel Blade 中渲染 html [重复]

渲染 HTML 标签 Laravel 5 Blade 模板