PHP和Laravel的特质 [英] Traits with PHP and Laravel

查看:91
本文介绍了PHP和Laravel的特质的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Laravel 5.1,并且当模型之前的模型使用appends数组时,我想从Trait中访问模型上的数组.

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 appends array.

我想将某些项目添加到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?

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

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

Here is how my current setup works.

特质

<?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;
  }
}

模型

<?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.

更新

我发现这种方法可以完成我需要的一个特征,但它仅适用于一个特征,我看不出使用它优于继承是没有优势的.

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.

特征

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.

模型

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

推荐答案

特质有时被描述为编译器辅助的复制粘贴".使用特质的结果总是可以单独写成一个有效的类.因此,在Trait中没有parent的概念,因为一旦应用了Trait,其方法就无法与类中定义的方法区分开,或者同时从其他Traits中导入.

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 parent in 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.

类似地,如 PHP文档所说的:

如果两个特性插入了一个具有相同名称的方法,并且如果冲突没有得到明确解决,则会产生致命错误.

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

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

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:

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

一种方法是继续使用特征,并使用反射动态发现已添加了哪些方法.但是,请注意,Reflection以其速度较慢而闻名.

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

为此,我们首先实现一个带有循环的构造函数,只需以特定方式命名方法,就可以将其插入.可以将其放入自己的Trait中(或者,您可以使用自己的增强版对Eloquent Model类进行子类化):

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 Model class with your own enhanced version):

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);
      }
    }
  }
}

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

Then any number of Traits implementing differently named extraConstruct methods:

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

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

We can set the initial content of $appends inside the decorated model itself, and it will replace the BaseModel definition, but not interrupt the other Traits:

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

  protected $appends = ['switched_base'];
}

但是,如果在混入AppendingGlue的同时重写了构造函数,则确实需要做一些额外的工作,例如

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::__construct in an inheritance situation, but you have to alias the trait's constructor in order to access it:

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";
  }
}

可以通过从一个存在的类(而不是AppendingGlue特质)或已经使用它的类中进行继承来避免这种情况:

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

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";
  }
}

这是所有内容的实时演示.

这篇关于PHP和Laravel的特质的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆