web-dev-qa-db-ja.com

PHPおよび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');

}

私がやりたいのは、クラスを拡張するのと同じ効果を達成するための何かです。私には似たような特徴がいくつかあるので、継承の使用はやや醜いものになります。

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

私は これに対するいくつかの回避策を見ました ですが、それらのどれも、単にアイテムを配列に手動で追加するよりも優れている/きれいに見えません。どんなアイデアでも大歓迎です。

更新


私は1つの特性に必要なことを達成するこの方法を発見しましたが、それは1つの特性に対してのみ機能し、継承よりもこれを使用する利点がわかりません。

特性

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

特性は、「コンパイラー支援のコピーアンドペースト」と呼ばれることもあります。トレイトを使用した結果は、それ自体で常に有効なクラスとして書き出すことができます。したがって、トレイトにはparentの概念はありません。これは、トレイトが適用されると、そのメソッドはクラス自体で定義されたメソッドと区別がつかないか、同時に他のトレイトからインポートされるためです。

同様に、 PHP docs say

2つのトレイトが同じ名前のメソッドを挿入した場合、競合が明示的に解決されていないと、致命的なエラーが発生します。

そのため、基本機能と混合機能が一般的な方法で相互に通信する方法がないため、同じ動作の複数のバリアントを混合する状況にはあまり適していません。

私の理解では、あなたが実際に解決しようとしている問題はこれです:

  • eloquentモデルクラスにカスタムアクセサーとミューテーターを追加する
  • これらのメソッドに一致する保護された$appends配列にアイテムを追加します

1つのアプローチは、引き続き特性を使用し、 Reflection を使用して、追加されたメソッドを動的に検出することです。ただし、Reflectionはかなり遅いという評判があることに注意してください。

これを行うには、最初に、特定の方法でメソッドに名前を付けるだけでフックできるループを持つコンストラクターを実装します。これは、独自のトレイトに配置できます(または、独自の拡張バージョンを使用して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);
      }
    }
  }
}

次に、異なる名前の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のミキシングと同時にコンストラクターをオーバーライドする場合は、 この前の回答で説明 のように、少し余分な作業を行う必要があります。これは、継承状況で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";
  }
}

これは、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";
  }
}

これが すべてをまとめたライブデモ です。

12
IMSoP

2019年のアップデートを追加すると思いました。これは、同様のことをしようとしたときに最初に出てきたディスカッションの1つだったからです。私は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';
  }
}
3
Kebian

接吻:

単に属性を追加するだけの場合に、トレイトを使用する必要がある理由はわかりません。

モデルがかなりかさばり、物事をスリムにしたい場合にのみ、コンストラクターなしでトレイトを使用することをお勧めします。

これは属性を追加する正しい方法ではないことにも注意してください

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

あなたはこれを行うことができます:

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

メソッドNameのsnake_caseの場合、属性名を追加します

編集:

追加の背後にある考え方は、データベーステーブルに存在しないフィールドをモデルに動的に追加して、次のようにできるようにすることです。

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