PHP 和 Laravel 的特征
Traits with PHP and Laravel
我正在使用 Laravel 5.1 并希望在模型之前的模型使用 appends
数组时从特征访问模型上的数组。
我想将某些项目添加到 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');
}
我想做的是达到与扩展 class 相同的效果。我有一些相似的特征,所以使用继承有点难看。
trait AwesomeSauceTrait {
function __construct() {
parent::__construct();
array_push($this->appends, 'saucedByCurrentUser');
}
}
我有 seen some workarounds for this,但其中 none 看起来 better/cleaner 而不是手动将项目添加到数组。任何想法表示赞赏。
更新
我发现这种方法可以实现我对一个特征的需求,但它只适用于一个特征,而且我看不出使用它比继承有什么优势。
特质
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);
}
吻:
我看不出有任何理由在您只是附加属性时应该使用 trait。
我只建议像您一样在没有构造函数的情况下使用 trait,只有当您的模型变得非常庞大并且您希望精简时。
另请注意,这不是附加属性的正确方法
protected $appends = array('age','saucedByCurrentUser');
你可以这样做:
protected $appends = array('age','sauced_by_current_user');
附加属性名称应该是其方法名称
的snake_case
已编辑:
追加背后的想法是动态添加数据库中不存在的字段 table 到您的模型中,因此您可以这样做:
$model = FairlyBlandModel ::find(1);
dd($model->sauced_by_current_user);
特质有时被描述为"compiler-assisted copy-and-paste";使用 Trait 的结果总是可以自己写成有效的 class。因此在 Trait 中没有 parent
的概念,因为一旦应用了 Trait,它的方法就无法与 class 本身定义的方法区分开来,或者同时从其他 Traits 导入。
同样,如the PHP docs say:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.
因此,它们不太适合您想要混合同一行为的多个变体的情况,因为基本功能和混合功能无法在通用中相互通信方式。
据我了解,您实际要解决的问题是:
- 向 Eloquent 模型添加自定义访问器和修改器 class
- 将其他项目添加到受保护的
$appends
数组匹配这些方法
一种方法是继续使用 Traits,并使用 Reflection 动态发现添加了哪些方法。但是,请注意 Reflection 以相当慢着称。
为此,我们首先实现一个带有循环的构造函数,我们可以通过以特定方式命名一个方法来挂钩它。这可以放入它自己的特征中(或者,您可以使用您自己的增强版本子 class Eloquent Model
class):
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
定义,但不会中断其他特征:
class ReDecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
protected $appends = ['switched_base'];
}
但是,如果您在混入 AppendingGlue
的同时覆盖构造函数,您确实需要做一些额外的工作,如 discussed in this previous answer。它类似于在继承情况下调用 parent::__construct
,但您必须为特征的构造函数设置别名才能访问它:
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";
}
}
这可以通过继承 class 来避免,它要么存在而不是 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";
}
}
我想我会添加 2019 年的更新,因为这是在尝试做类似事情时出现的第一批讨论之一。我正在使用 Laravel 5.7,如今 Laravel 将执行 IMSoP 提到的反射。
特征启动后,Laravel 将在构造的对象上调用 initializeTraitName()(其中 TraitName 是特征的全名)。
要从特征向 $appends
添加额外的项目,您可以简单地这样做...
trait AwesomeSauceTrait {
public function initializeAwesomeSauceTrait()
{
$this->appends[] = 'sauced_by_current_user';
}
public function getSaucedByCurrentUserAttribute()
{
return 'whatever';
}
}
我正在使用 Laravel 5.1 并希望在模型之前的模型使用 appends
数组时从特征访问模型上的数组。
我想将某些项目添加到 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');
}
我想做的是达到与扩展 class 相同的效果。我有一些相似的特征,所以使用继承有点难看。
trait AwesomeSauceTrait {
function __construct() {
parent::__construct();
array_push($this->appends, 'saucedByCurrentUser');
}
}
我有 seen some workarounds for this,但其中 none 看起来 better/cleaner 而不是手动将项目添加到数组。任何想法表示赞赏。
更新
我发现这种方法可以实现我对一个特征的需求,但它只适用于一个特征,而且我看不出使用它比继承有什么优势。
特质
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);
}
吻:
我看不出有任何理由在您只是附加属性时应该使用 trait。
我只建议像您一样在没有构造函数的情况下使用 trait,只有当您的模型变得非常庞大并且您希望精简时。
另请注意,这不是附加属性的正确方法
protected $appends = array('age','saucedByCurrentUser');
你可以这样做:
protected $appends = array('age','sauced_by_current_user');
附加属性名称应该是其方法名称
的snake_case已编辑:
追加背后的想法是动态添加数据库中不存在的字段 table 到您的模型中,因此您可以这样做:
$model = FairlyBlandModel ::find(1);
dd($model->sauced_by_current_user);
特质有时被描述为"compiler-assisted copy-and-paste";使用 Trait 的结果总是可以自己写成有效的 class。因此在 Trait 中没有 parent
的概念,因为一旦应用了 Trait,它的方法就无法与 class 本身定义的方法区分开来,或者同时从其他 Traits 导入。
同样,如the PHP docs say:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.
因此,它们不太适合您想要混合同一行为的多个变体的情况,因为基本功能和混合功能无法在通用中相互通信方式。
据我了解,您实际要解决的问题是:
- 向 Eloquent 模型添加自定义访问器和修改器 class
- 将其他项目添加到受保护的
$appends
数组匹配这些方法
一种方法是继续使用 Traits,并使用 Reflection 动态发现添加了哪些方法。但是,请注意 Reflection 以相当慢着称。
为此,我们首先实现一个带有循环的构造函数,我们可以通过以特定方式命名一个方法来挂钩它。这可以放入它自己的特征中(或者,您可以使用您自己的增强版本子 class Eloquent Model
class):
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
定义,但不会中断其他特征:
class ReDecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
protected $appends = ['switched_base'];
}
但是,如果您在混入 AppendingGlue
的同时覆盖构造函数,您确实需要做一些额外的工作,如 discussed in this previous answer。它类似于在继承情况下调用 parent::__construct
,但您必须为特征的构造函数设置别名才能访问它:
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";
}
}
这可以通过继承 class 来避免,它要么存在而不是 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";
}
}
我想我会添加 2019 年的更新,因为这是在尝试做类似事情时出现的第一批讨论之一。我正在使用 Laravel 5.7,如今 Laravel 将执行 IMSoP 提到的反射。
特征启动后,Laravel 将在构造的对象上调用 initializeTraitName()(其中 TraitName 是特征的全名)。
要从特征向 $appends
添加额外的项目,您可以简单地这样做...
trait AwesomeSauceTrait {
public function initializeAwesomeSauceTrait()
{
$this->appends[] = 'sauced_by_current_user';
}
public function getSaucedByCurrentUserAttribute()
{
return 'whatever';
}
}