skip to Main Content

I have a class that extends EloquentModel.
I want to define a trait that adds a static property and a static method to the class.

trait Searchable
{
  protected static array $searchable;

  public static function formatSearchQuery($value)
  {
    $queryArray = array();
    $paramArray = array();

    foreach (self::$searchable as $field) {
      $queryArray[] = "{$field} LIKE ?";
      $paramArray[] = $value . '%';
    }

    return ['query' => join(" OR ", $queryArray), 'params' => $paramArray];
  }
}

the aim is to define the fields that can be used in a text search query.

The problem I have is when I define my class like this:

class Benefit extends NAFModel
{
  use Searchable;
  /**
   * Name for table without prefix
   *
   * @var string
   */
  protected $table = 'naf_agenda_benefits';

  protected static array $searchable =
  [
    'name',
    'description',
  ];
}

I get the following error :

PHP Fatal error: NafAgendaEloquentClient and NafAgendaTraitsSearchable define the same property ($searchable) in the composition of NafAgendaEloquentClient. However, the definition differs and is considered incompatible. Class was composed in /var/www/html/wp-content/plugins/naf-agenda-react/includes/Eloquent/Client.php on line 9

2

Answers


  1. If a trait defines a property then a class can not define a property with the same name unless it is compatible (same visibility, type, readonly modifier and initial value), otherwise a fatal error is issued. (Reference from PHP Manual)

    Methods defined in traits can access methods and properties of the class, therefore we don’t need to define searchable array in the Trait.

    <?php
    
    trait Searchable
    {
        public static function formatSearchQuery($value)
        {
            $queryArray = array();
            $paramArray = array();
    
            if (!is_array(self::$searchable ?? null)) {
              throw new Error(self::class . '::$searchable not iterable');
            }
    
            foreach (self::$searchable as $field) {
                $queryArray[] = "{$field} LIKE ?";
                $paramArray[] = $value . '%';
            }
    
            return ['query' => join(" OR ", $queryArray), 'params' => $paramArray];
        }
    
    }
    
    class Benefit extends NAFModel
    {
        use Searchable;
    
        protected static array $searchable = ['name', 'description'];
    
        /**
         * Name for table without prefix
         *
         * @var string
         */
        protected $table = 'naf_agenda_benefits';
    }
    
    print_r(Benefit::formatSearchQuery('Something'));
    
    Login or Signup to reply.
  2. If you’re looking for reference, this is straight from the PHP Manual related to your question:

    If a trait defines a property then a class can not define a property with the same name unless it is compatible (same visibility and type, readonly modifier, and initial value), otherwise a fatal error is issued.

    This is bad news for you, as it means that you have written non-working code by violating these rules, in your case specifically the initial value:

    trait Searchable
    {
      protected static array $searchable;
    

    vs.

    class Benefit extends NAFModel
    {
      protected static array $searchable =
      [
        'name',
        'description',
      ];
    

    Therefore, you need to work around such a limitation (there is no conflict resolution possible, e.g. as for methods, cf. "Collisions with other trait methods").


    I want to define a trait that adds a static property and a static method to the class.

    As outlined, the two properties need to be compatible, therefore the resolution is to make them so.

    Use the method aliased, define formatSearchQuery, then when called, initialize the properties value you’d like to override and then delegate to the alias.

    If you also want to have an initial value, add a new static property for that and use it in your defined formatSearchQuery to copy the value over.

    class Benefit extends NAFModel
    {
        use Searchable {formatSearchQuery as _formatSearchQuery;}
    
        /**
         * Name for table without prefix
         *
         * @var string
         */
        protected $table = 'naf_agenda_benefits';
    
        protected static array $_searchable =
            [
                'name',
                'description',
            ];
    
        public static function formatSearchQuery($value)
        {
            # lazy initialization pattern, only _one_ example.
            self::$searchable ??= self::$_searchable;
    
            return self::_formatSearchQuery($value);
        }
    }
    

    By the way, if you control the Trait, you can design the mixin already with extension in mind, e.g. the original implementation of formatSearchQuery() in the trait could handle that.


    However you have not shared what you’re trying to achieve therefore there is little suggestion to be given. Not making it static would not solve the class composition problem on the Traits level. You perhaps have an initialization problem and you try to solve it by static, perhaps you’re looking for a static factory method and then have your system handle the details per instance.

    Try to build your class hierarchy with abstract super-classes only first and then add traits only for additional mixins not crossing existing names.

    Commonly its known that composition should be favored over inheritance, and traits likely fall into the later category than the first. YMMV.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search