Черты с PHP и Laravel

Я использую Laravel 5.1 и хотел бы получить доступ к массиву модели из Trait, когда модель до модели использует массив 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;
  }
}

Model

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

Я видел некоторые обходные пути для этого, но ни один из них не выглядит лучше/чище, чем просто добавлять элемент в массив вручную. Любые идеи приветствуются.

Обновление


Я обнаружил этот способ выполнения того, что мне нужно для одного признака, но он работает только для одного признака, и я не вижу преимущества использования этого над наследованием.

черта

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

Ответ 1

Черты иногда описываются как "копирование и вставка с помощью компилятора"; результат использования Trait всегда может быть выписан как действительный класс в своем собственном праве. Поэтому в значении нет понятия parent, потому что, как только Признак был применен, его методы неотличимы от тех, которые определены в самом классе или импортированы из других признаков одновременно.

Аналогично, документы в формате PDF:

Если две черты вставляют метод с тем же именем, возникает фатальная ошибка, если конфликт явно не разрешен.

Как таковые, они не очень подходят для ситуаций, когда вы хотите смешивать несколько вариантов одного и того же поведения, потому что нет возможности для базовых функциональных возможностей и смешанных функциональных возможностей для общего общения друг с другом.

В моем понимании проблема, которую вы на самом деле пытаетесь решить, такова:

  • добавить пользовательские Accessors и Mutators в класс модели Eloquent
  • добавить дополнительные элементы в защищенный массив $appends, соответствующий этим методам.

Одним из подходов было бы продолжать использовать Черты и использовать 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";
  }
}

Здесь живая демонстрация всего того, что собрано.

Ответ 2

ПОЦЕЛУЙ:

Я не вижу причин, по которым вам следует использовать черту, когда вы просто добавляете атрибуты.

Я бы рекомендовал использовать черту без конструктора, как вы делали, только если ваша модель становится довольно громоздкой, и вы хотите сгладить вещи.

Также обратите внимание, что это не правильный способ добавления атрибута

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

Вы можете сделать это:

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

Добавляет имена атрибутов, если snake_case его метода Name

Отредактировано:

Идея добавления заключается в том, чтобы динамически добавлять поля, которые не существуют в вашей таблице базы данных, в вашу модель, поэтому после того, как вы можете сделать это:

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

Ответ 3

Я думал, что добавлю обновление для 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';
  }
}