PHP 和 Laravel 的特性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30922175/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 11:48:24  来源:igfitidea点击:

Traits with PHP and Laravel

phplaravellaravel-5traits

提问by whoacowboy

I am using Laravel 5.1 and would like to access an array on the Model from the Trait when the Model before the model uses the appendsarray.

我正在使用 Laravel 5.1,当模型之前的模型使用appends数组时,我想从特征访问模型上的数组。

I would like to add certain items to the appends array if it exists from my trait. I don't want to edit the model in order to achieve this. Are traits actually usable in this scenario or should I use inheritance?

如果我的特征中存在某些项目,我想将某些项目添加到 appends 数组。我不想为了实现这一点而编辑模型。在这种情况下,traits 是否真的可用,还是应该使用继承?

array_push($this->appends, 'saucedByCurrentUser');

Here is how my current setup works.

这是我当前设置的工作方式。

Trait

特征

<?php namespace App;

trait AwesomeSauceTrait {

  /**
   * Collection of the sauce on this record
   */
  public function awesomeSauced()
  {
    return $this->morphMany('App\AwesomeSauce', 'sauceable')->latest();
  }
  public function getSaucedByCurrentUserAttribute()
  {
    if(\Auth::guest()){
        return false;
    }
    $i = $this->awesomeSauced()->whereUserId(\Auth::user()->id)->count();
    if ($i > 0){
        return true;
    }
    return false;
  }
}

Model

模型

<?php namespace App;

use App\AwesomeSauceTrait;
use Illuminate\Database\Eloquent\Model;

class FairlyBlandModel extends Model {
    use AwesomeSauceTrait;

    protected $appends = array('age','saucedByCurrentUser');

}

What I would like to do is something to achieve the same effect as extending a class. I have a few similar traits, so using inheritance gets somewhat ugly.

我想做的是达到与扩展类相同的效果。我有一些类似的特征,所以使用继承有点难看。

trait AwesomeSauceTrait {
 function __construct() {
     parent::__construct();
     array_push($this->appends, 'saucedByCurrentUser');
 }
}

I have seen some workarounds for this, but none of them seem better/cleaner than just adding the item to the array manually. Any ideas are appreciated.

我已经看到了一些解决方法,但没有一个比手动将项目添加到数组更好/更干净。任何想法表示赞赏。

Update

更新



I discovered this way of accomplishing what I need for one trait, but it only works for one trait and I don't see an advantage of using this over inheritance.

我发现了这种方式来完成我需要的一个特征,但它只适用于一个特征,我没有看到使用它而不是继承的优势。

trait

特征

protected $awesomeSauceAppends = ['sauced_by_current_user'];

protected function getArrayableAppends()
{
    array_merge($this->appends, $this->awesomeSauceAppends);
    parent::getArrayableAppends();
}

How I am currently handling my Model, for what it is worth.

我目前如何处理我的模型,它的价值。

model

模型

public function __construct()
{
    array_merge($this->appends, $this->awesomeSauceAppends);
}

回答by IMSoP

Traits are sometimes described as "compiler-assisted copy-and-paste"; the result of using a Trait can always be written out as a valid class in its own right. There is therefore no notion of parentin a Trait, because once the Trait has been applied, its methods are indistinguishable from those defined in the class itself, or imported from other Traits at the same time.

特性有时被描述为“编译器辅助的复制和粘贴”;使用 Trait 的结果总是可以自己写成一个有效的类。因此parent在 Trait 中没有概念,因为一旦应用了 Trait,它的方法与类本身定义的方法没有区别,或者同时从其他 Trait 导入。

Similarly, as the PHP docs say:

同样,正如PHP 文档所说

If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.

如果两个 Traits 插入一个同名的方法,并且没有明确解决冲突,则会产生致命错误。

As such, they are not very suitable for situations where you want to mix in multiple variants of the same piece of behaviour, because there is no way for base functionality and mixed in functionality to talk to each other in a generic way.

因此,它们不太适合您想要混合同一行为的多个变体的情况,因为基本功能和混合功能无法以通用方式相互交流。

In my understanding the problem you're actually trying to solve is this:

根据我的理解,您实际上要解决的问题是:

  • add custom Accessors and Mutators to an Eloquent model class
  • add additional items to the protected $appendsarray matching these methods
  • 将自定义访问器和修改器添加到 Eloquent 模型类
  • $appends与这些方法匹配的受保护数组添加其他项目


One approach would be to continue to use Traits, and use Reflectionto dynamically discover which methods have been added. However, beware that Reflection has a reputation for being rather slow.

一种方法是继续使用 Traits,并使用Reflection动态发现添加了哪些方法。但是,请注意 Reflection 以相当缓慢而著称。

To do this, we first implement a constructor with a loop which we can hook into just by naming a method in a particular way. This can be placed into a Trait of its own (alternatively, you could sub-class the Eloquent Modelclass with your own enhanced version):

要做到这一点,我们首先实现一个带有循环的构造函数,我们可以通过以特定方式命名方法来钩入它。这可以放在它自己的 Trait 中(或者,您可以Model使用自己的增强版本对 Eloquent类进行子类化):

trait AppendingGlue {
  public function __construct() {
    // parent refers not to the class being mixed into, but its parent
    parent::__construct();

    // Find and execute all methods beginning 'extraConstruct'
    $mirror = new ReflectionClass($this);
    foreach ( $mirror->getMethods() as $method ) {
      if ( strpos($method->getName(), 'extraConstruct') === 0 ) {
        $method->invoke($this);
      }
    }
  }
}

Then any number of Traits implementing differently named extraConstructmethods:

然后任意数量的 Traits 实现不同命名的extraConstruct方法:

trait AwesomeSauce {
  public function extraConstructAwesomeSauce() {
    $this->appends[] = 'awesome_sauce';
  }

  public function doAwesomeSauceStuff() {
  }
}

trait ChocolateSprinkles {
  public function extraConstructChocolateSprinkles() {
    $this->appends[] = 'chocolate_sprinkles';
  }

  public function doChocolateSprinklesStuff() {
  }
}

Finally, we mix in all the traits into a plain model, and check the result:

最后,我们将所有特征混合到一个普通模型中,并检查结果:

class BaseModel {
  protected $appends = array('base');

  public function __construct() {
    echo "Base constructor run OK.\n";
  }

  public function getAppends() {
    return $this->appends;
  }
}

class DecoratedModel extends BaseModel {
  use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
}

$dm = new DecoratedModel;
print_r($dm->getAppends());

We can set the initial content of $appendsinside the decorated model itself, and it will replace the BaseModeldefinition, but not interrupt the other Traits:

我们可以$appends在装饰模型内部设置初始内容,它会替换BaseModel定义,但不会中断其他 Traits:

class ReDecoratedModel extends BaseModel {
  use AppendingGlue, AwesomeSauce, ChocolateSprinkles;

  protected $appends = ['switched_base'];
}

However, if you over-ride the constructor at the same time as mixing in the AppendingGlue, you do need to do a bit of extra work, as discussed in this previous answer. It's similar to calling parent::__constructin an inheritance situation, but you have to alias the trait's constructor in order to access it:

但是,如果在混入 的同时覆盖构造函数,则AppendingGlue确实需要做一些额外的工作,如上一个答案中所述。它类似于parent::__construct在继承情况下调用,但您必须为 trait 的构造函数取别名才能访问它:

class ReConstructedModel extends BaseModel {
  use AppendingGlue { __construct as private appendingGlueConstructor; }
  use AwesomeSauce, ChocolateSprinkles;

  public function __construct() {
    // Call the mixed-in constructor explicitly, like you would the parent
    // Note that it will call the real parent as well, as though it was a grand-parent
    $this->appendingGlueConstructor();

    echo "New constructor executed!\n";
  }
}

This can be avoided by inheriting from a class which either exists instead of the AppendingGluetrait, or already uses it:

这可以通过从存在而不是AppendingGlue特征或已经使用它的类继承来避免:

class GluedModel extends BaseModel {
  use AppendingGlue;
}
class ReConstructedGluedModel extends GluedModel {
  use AwesomeSauce, ChocolateSprinkles;

  public function __construct() {
    // Standard call to the parent constructor
    parent::__construct();
    echo "New constructor executed!\n";
  }
}

Here's a live demo of all of that put together.

是所有这些组合在一起现场演示

回答by Kebian

I thought I'd add an update for 2019 since this was one of the first discussions that popped up when trying to do a similar thing. I'm using Laravel 5.7 and nowadays Laravel will do the reflection that IMSoP mentioned.

我想我会为 2019 年添加一个更新,因为这是尝试做类似事情时出现的第一批讨论之一。我正在使用 Laravel 5.7,现在 Laravel 将进行 IMSoP 提到的反射。

After the trait has been booted, Laravel will then call initializeTraitName() on the constructed object (where TraitName is the full name of the trait).

在 trait 启动后,Laravel 将在构造的对象上调用 initializeTraitName()(其中 TraitName 是 trait 的全名)。

To add extra items to $appendsfrom a trait, you could simply do this...

要从特征中添加额外的项目$appends,您可以简单地执行此操作...

trait AwesomeSauceTrait {

  public function initializeAwesomeSauceTrait()
  {
    $this->appends[] = 'sauced_by_current_user';
  }

  public function getSaucedByCurrentUserAttribute()
  {
    return 'whatever';
  }
}

回答by Emeka Mbah

KISS:

吻:

I don't see any reason why you should use trait when your are simply appending attributes.

当您只是附加属性时,我看不出您应该使用 trait 的任何理由。

I would only recommend using trait without a constructor like you were doing, only if you model is getting pretty bulky and you wish to slim down things.

我只建议使用没有构造函数的 trait,就像你在做的那样,只有当你的模型变得非常庞大并且你希望减少东西时。

Please also note this not the correct way of appending attribute

另请注意,这不是附加属性的正确方法

    protected $appends = array('age','saucedByCurrentUser');

You could do this:

你可以这样做:

    protected $appends = array('age','sauced_by_current_user');

Appends attribute names should the snake_case of its method Name

附加属性名称应该是它的方法名称的蛇形

Edited:

编辑:

The idea behind appends is to dynamically add fields that doesn't exist in your database table to your model so after you can do like:

appends 背后的想法是将数据库表中不存在的字段动态添加到模型中,以便在您可以执行以下操作之后:

  $model = FairlyBlandModel ::find(1); 
  dd($model->sauced_by_current_user);