I do a custom data provider with the support of the tree.

The ActiveDataProvider class functionality is quite enough for me, to which the $_tree attribute and the getter / setter are added:

 private $_tree = null; public function getTree() { $this->prepare(); return $this->_tree; } public function setTree($tree) { $this->_tree = $tree; } 

plus the advanced prepare() method:

 public function prepare($forcePrepare = false) { parent::prepare($forcePrepare); if ($forcePrepare || $this->_tree === null) $this->_tree = $this->prepareTree($this->_models); } 

The catch is that the $_models attribute is defined as private in the BaseDataProvider . Theoretically, I can't get to it in any way, except using $this->getModels() , in fact, even so I cannot get the model (due to the recursion that occurs).

There is a solution to the forehead: to copy BaseDataProvider and ActiveDataProvider , in order to first change the properties of the $_models attribute from private to protected . I don't like it.

How can you get the opportunity to reach $_models in your data provider with minimal effort, without creating extra classes?

    1 answer 1

    Before giving the code that can help you solve these problems, I want to note that the question you faced is the right signal that somewhere at the previous stage you made a number of incorrect architectural decisions.

    You need to break the ancestor's encapsulation, while you are heavily dependent on its implementation. So much so that, in principle, they are ready to duplicate as many as two classes in order to correct a small part of this very implementation.

    Inheritance is always a very strong connection between classes . In your case, I would advise using composition instead of inheritance and implementing the yii\data\DataProviderInterface .

    But, if you think that this does not fit your case, here is the solution:

    You can reach the $_models private property using closures, specifying a new scope ( http://php.net/manual/ru/closure.bindto.php ).

    Consider a test case:

     abstract class TestGrandPa { private $_models; public function __construct($m) { $this->_models = $m; } /** * @return mixed */ public function getModels() { return $this->_models; } /** * @param mixed $models */ public function setModels($models) { $this->_models = $models; } } class TestPa extends TestGrandPa { public function greet() { echo 'Hello ' . $this->getModels(); } protected function prepareGreet($m) { return $m . '!'; } } class TestYouth extends TestPa { public function prepare() { $closure = function() { return $this->_models; }; $binded = $closure->bindTo($this, 'TestGrandPa'); $this->setModels($this->prepareGreet($binded())); } } $y = new TestYouth('Denis'); $y->prepare(); echo $y->greet(); die(); 

    This code will output Hello Denis! .

    Here I create a closure that should return the value of an object property:

     $closure = function() { return $this->_models; }; 

    Here I tie the closure to the current instance and specify a new scope - the TestGrandPa class:

     $binded = $closure->bindTo($this, 'TestGrandPa'); 

    If I described the method simply:

     public function prepare() { // $closure = function() { // return $this->_models; // }; // $binded = $closure->bindTo($this, 'TestGrandPa'); $this->setModels($this->prepareGreet($this->_models)); } 

    I would get Hello ! .

    Those. for your case, to reach BaseDataProvider::$_models , you need to rewrite the method like this:

     public function prepare($forcePrepare = false) { parent::prepare($forcePrepare); if ($forcePrepare || $this->_tree === null) { $closure = function() { return $this->_models; }; $binded = $closure->bindTo($this, 'yii\data\BaseDataProvider'); $this->_tree = $this->prepareTree($binded()); } } 

    I note once again that it is preferable (it will be easier to support later) to review a number of architectural solutions that led to the emergence of this problem.