Xorifelse
Xorifelse

Reputation: 7911

Trait dependency loading in sequence

I'm currently working on a trait auto initializer and its all working fine and dandy however there are sadly some severe limitations on traits.

One issue I came across is the following:

trait constructor{}
trait cache{}
trait database{}
trait settings{} # requires database or cache.

Now for my auto loading system to work I must define the use in the correct sequence like so:

class t{
  use constructor, cache, settings;
}

If I would do the following:

class t{
  use constructor, settings, cache;
}

The settings trait initializer will be called before the cache initializer and thus the $cache variable inside the cache trait is null.

Now a simple solution would be to add use cache, database; to the settings trait however that can result in collisions of defined methods if used by another trait that must be included.

Another solution would be to check if the property $cache is defined and then check if it's set but this would create a lot of redundant code I'd rather not want to write for each and every trait.

Now the logic of the code is the following:

trait constructor{
  function init(){
    echo 'I am called first';
    print_r(class_uses(self::class))/*[
      'constructor' => 'constructor',
      'cache' => 'cache',
      'database' => 'database',
      'settings' => 'settings'

      // Sorted in sequence as defined in the class.
    ]*/

    # now call other trait initializers.
  }
}

trait settings{
  function settings(){
    echo 'I am called last, but before __construct()';
  }
}

class t{
  use constructor; // required by all traits
  use cache;       // requires constructor.
  use database;    // requires constructor.
  use settings;    // requires constructor, cache || database.

  function __construct(){
    echo 'I am called after init()';
    $this->db->prepare(...)->execute();
  }
}

It is in the constructor possible to sort the array to make sure the settings is indexed after cache or database. However here comes the tricky part, what is the best approach to do this?

Since most classes only use a small number of traits, defining a lengthy array to use as a base for sorting seems deficient. As this system is running at the core of the CMS I'd rather sort on a trait by trait basis.

The problem with traits is that I cannot define the same thing twice, noor in class as well so I'd like to keep the scope pollution to a minimum.

Upvotes: 0

Views: 673

Answers (1)

Xorifelse
Xorifelse

Reputation: 7911

I've found a viable solution to the issue without too much redundant code.

trait constructor{
  private $traits;

  function loaded(array $list){
    foreach($list as $trait){
      if(isset($this->_traits[$trait])){ // class_uses returns array of ['trait' => 'trait'] while I append numeric.
        $this->_traits[] = $this->_current; // append the current looping method (of init) to be loaded last.
        return false;
      }
    }
    return true;
  }

  function init(){
    $traits = class_uses(self::class);
    while(list(,$method) = each($this->traits)){
      unset($this->_traits[$method]); // avoid infinite loop.
      // This while loop will loop trough newly added values added by 'loaded' 
      call_user_func([$this, $method]);
    }
  }
}

trait settings{
  function settings(){
    if($this->loaded(['cache', 'database'])){
      # This method will queue the 'settings' method for later regardless if the traits are already loaded.
      # So it will first load the traits that don't have dependency's.
      # The order is not important because the __construct method is called last anyways.
    }
  }
}

If anyone has any other suggestion, just add an answer.

Upvotes: 0

Related Questions