【问题标题】:Traits with PHP and LaravelPHP 和 Laravel 的特征
【发布时间】:2015-06-18 17:42:43
【问题描述】:

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

如果我的特征存在,我想将某些项目添加到 appends 数组中。我不想编辑模型来实现这一点。在这种情况下,特征实际上是否可用,或者我应该使用继承吗?

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

这是我当前设置的工作原理。

特质

<?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');

}

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

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

我有seen some workarounds for this,但它们似乎都比手动将项目添加到数组中更好/更干净。任何想法都表示赞赏。

更新


我发现了这种方式来完成我需要的一个 trait,但它只适用于一个 trait,我看不出使用这种方式优于继承。

特质

protected $awesomeSauceAppends = ['sauced_by_current_user'];

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

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

型号

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

【问题讨论】:

  • @ceejayoz 我在我的问题中引用了这个问题和答案。我正在寻找另一种方法。如果您在答案中看到其他方式,请指出。
  • @ceejayoz 我投票决定重新打开它。 OP 在类中重载构造函数时没有问题。 OP 正在寻求一种不必这样做的方法,并且有一种特定于 Laravel 的方法来实现用户想要的。

标签: php laravel laravel-5 traits


【解决方案1】:

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

类似the PHP docs say:

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

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

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

  • 将自定义访问器和修改器添加到 Eloquent 模型类
  • 向受保护的$appends 数组添加与这些方法匹配的其他项目

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

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

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 方法:

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

  public function doAwesomeSauceStuff() {
  }
}

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

  public function doChocolateSprinklesStuff() {
  }
}

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

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的定义,但不会中断其他Traits:

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

  protected $appends = ['switched_base'];
}

但是,如果您在混入AppendingGlue 的同时覆盖构造函数,您确实需要做一些额外的工作,如discussed in this previous answer。这类似于在继承情况下调用 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";
  }
}

这可以通过继承一个存在而不是 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";
  }
}

这是live demo of all of that put together

【讨论】:

  • 我还打算提出一种不同的技术,通过覆盖getAttributeValue 的定义使其更“可挂钩”,并单独留下$appends,但我花了太长时间准备反射版本,现在就弄清楚。
  • 感谢您的周到回答。这种情况下如果ReDecoratedModel__construct方法,会不会和traits__construct方法冲突?
  • @whoacowboy 是的,因为AppendingGlue trait 会直接粘贴到该类中,并且您不能同时拥有两个构造函数,因此 PHP 必须选择一个(或选择出错)。但是,根据您链接到的答案,很容易解决。我再补充一个例子。 :)
  • 感谢您的更新!让我试试这些家伙。
【解决方案2】:

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

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

要从特征向$appends 添加额外的项目,您可以简单地这样做...

trait AwesomeSauceTrait {

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

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

【讨论】:

    【解决方案3】:

    亲吻:

    当您只是附加属性时,我看不出有什么理由应该使用 trait。

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

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

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

    你可以这样做:

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

    在其方法名称的蛇形大小写时附加属性名称

    编辑:

    追加背后的想法是动态地将数据库表中不存在的字段添加到模型中,以便之后您可以这样做:

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

    【讨论】:

    • 感谢您的回答。我目前正在走轻松的道路。我只是在寻找一种完全解耦特征以使其更清洁的方法。顺便说一句,我在示例中的代码目前没有问题。我认为这是一种风格,但我可能会弄错。
    猜你喜欢
    • 2016-08-20
    • 2013-12-21
    • 2014-12-22
    • 2020-11-28
    • 2017-10-11
    • 1970-01-01
    • 1970-01-01
    • 2013-03-04
    • 2018-12-12
    相关资源
    最近更新 更多