【问题标题】:Laravel - Lot of Accessors (Mutators) for rendering Views with BladeLaravel - 大量使用 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 可以用于常规页面和管理页面(可能更多)。

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

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

public function getAccDisplayNameAttribute() { ... };

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

【问题讨论】:

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

标签: php laravel view model laravel-blade


【解决方案1】:

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

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

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

  1. 数组作为访问器的示例 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 等),或者您可以使用单独的类来管理所有这些,如下例所示

  1. 示例 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

      【讨论】:

        猜你喜欢
        • 2014-07-23
        • 2019-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-25
        • 2012-08-15
        • 2018-04-05
        相关资源
        最近更新 更多