PHP和Laravel的特征

who*_*boy 10 php traits laravel laravel-5

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

如果它存在于我的特征中,我想将某些项添加到appends数组中.我不想编辑模型来实现这一目标.特征是在这种情况下实际可用还是应该使用继承?

array_push($this->appends, 'saucedByCurrentUser');
Run Code Online (Sandbox Code Playgroud)

以下是我当前设置的工作原理.

特征

<?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;
  }
}
Run Code Online (Sandbox Code Playgroud)

模型

<?php namespace App;

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

class FairlyBlandModel extends Model {
    use AwesomeSauceTrait;

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

}
Run Code Online (Sandbox Code Playgroud)

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

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

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

更新


我发现这种方法可以完成我需要的一个特性,但它只适用于一个特征,我没有看到使用它超过继承的优势.

特征

protected $awesomeSauceAppends = ['sauced_by_current_user'];

protected function getArrayableAppends()
{
    array_merge($this->appends, $this->awesomeSauceAppends);
    parent::getArrayableAppends();
}
Run Code Online (Sandbox Code Playgroud)

我目前如何处理我的模型,以及它的价值.

模型

public function __construct()
{
    array_merge($this->appends, $this->awesomeSauceAppends);
}
Run Code Online (Sandbox Code Playgroud)

IMS*_*SoP 12

特征有时被描述为"编译器辅助的复制和粘贴"; 使用Trait的结果总是可以作为一个有效的类本身写出来.因此parent,特质中没有概念,因为一旦应用了特征,其方法与类本身定义的方法无法区分,或同时从其他特征导入.

同样,正如PHP文档所说:

如果两个Traits插入具有相同名称的方法,则在未明确解决冲突时会产生致命错误.

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

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

  • 将自定义Accessor和Mutators添加到Eloquent模型类中
  • 将其他项添加到$appends与这些方法匹配的受保护数组中

一种方法是继续使用Traits,并使用Reflection动态发现添加了哪些方法.但是,要注意Reflection有一个相当慢的声誉.

为此,我们首先实现一个带循环的构造函数,我们可以通过以特定方式命名方法来挂钩.这可以放入自己的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);
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后任意数量的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() {
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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());
Run Code Online (Sandbox Code Playgroud)

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

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

  protected $appends = ['switched_base'];
}
Run Code Online (Sandbox Code Playgroud)

但是,如果你在混合中同时覆盖构造函数,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";
  }
}
Run Code Online (Sandbox Code Playgroud)

这可以通过从存在而不是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";
  }
}
Run Code Online (Sandbox Code Playgroud)

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