7

I often use properties in my classes that store an array of options. I'd like to be able to somehow merge those options from defaults declared in a parent class.

I demonstrated with some code.

class A
{
    public $options = array('display'=>false,'name'=>'John');
}

class B extends A
{
    public $options = array('name'=>'Mathew');
}

Now when I create B, then I'd like $options to contain a merged array from A::options

What happens now is this.

$b = new B();
print_r($b);
array('name'=>'Mathew');

I would like something like this using array_merge_recursive().

array('display'=>false,'name'=>'Mathew');
  • Maybe it's something I could do in the constructor?
  • Is it possible to make this a behavior of class A? So that I don't always have to implement the same code in all subclasses.
  • Could I use reflection to auto find array properties in both classes and merge them?
Reactgular
  • 52,335
  • 19
  • 158
  • 208

3 Answers3

6

In addition to the previous answers, another approach that may be suited for certain cases would be to use PHP Reflection or built-in class functions. Here is a basic example using the latter:

class Organism
{
    public $settings;
    public $defaults = [
        'living' => true,
        'neocortex' => false,
    ];
    public function __construct($options = [])
    {
        $class = get_called_class();
        while ($class = get_parent_class($class)) {
            $this->defaults += get_class_vars($class)['defaults'];
        }
        $this->settings = $options + $this->defaults;
    }
}
class Animal extends Organism
{
    public $defaults = [
        'motile' => true,
    ];
}
class Mammal extends Animal
{
    public $defaults = [
        'neocortex' => true,
    ];
}

$fish = new Animal();
print_r($fish->settings); // motile: true, living: true, neocortex: false
$human = new Mammal(['speech' => true]);
print_r($human->settings); // motile: true, living: true, neocortex: true, speech: true
Synexis
  • 1,255
  • 14
  • 14
5

I realize I changed your interface from a public variable to a method, but maybe it works for you. Beware, adding a naive setOps($ops) method may work unexpected if you allow the parent ops to continue to be merged in.

class A
{
    private $ops = array('display'=>false, 'name'=>'John');
    public function getops() { return $this->ops; }
}
class B extends A
{
    private $ops = array('name'=>'Mathew');
    public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
class c extends B
{
    private $ops = array('c'=>'c');
    public function getops() { return array_merge(parent::getOps(), $this->ops); }
}

$c = new C();
print_r($c->getops());

out:

Array
(
    [display] => 
    [name] => Mathew
    [c] => c
)
goat
  • 31,486
  • 7
  • 73
  • 96
  • That's also a good idea. It would help avoid some strict warnings in PHP 5.3 – Reactgular Jan 19 '13 at 18:59
  • 1
    making `$ops` private eliminates the possibility to access the parent's `$ops` property, or to _not_ override it. It also forces calling the parent getops function each time you want to read any property, rather than once on instanciation - I wouldn't say that's a good implementation. Matthew - strict warning =)? – AD7six Jan 19 '13 at 19:11
  • Sometimes I get warnings on $b->options if the parent is using __get but the parent didn't declare $options as a property. I like your approach. It's a more classical OOP design, and I'll put getOps into an interface to make this a little more formal. – Reactgular Jan 19 '13 at 19:29
  • 1
    This doesn't directly answer the question, as he wants to merge the child into the parent from the parent, which avoids putting the responsibility of merging in every child class. – Mike Purcell Jan 22 '15 at 01:38
  • I prefer the answer of @synexis. This one needs too much manual coding. – Márton Tamás Jun 01 '18 at 18:38
2

You can use a simple pattern like so:

abstract class Parent {

    protected $_settings = array();

    protected $_defaultSettings = array(
        'foo' => 'bar'
    );

    public __construct($settings = array()) {
        $this->_settings = $settings + $this->_defaultSettings;
    }

}

In this way it's easily possible to modify the defaults applied in child classes:

class Child extends Parent {

    protected $_defaultSettings = array(
        'something' => 'different';
    );

}

Or to apply something more complex:

class OtherChild extends Parent {

    function __construct($settings = array()) {
        $this->_defaultSettings = Configure::read('OtherChild');
        return parent::__construct($settings);
    }

}

Merging variables

Cake does come with a function for merging variables. It's used for controller properties such as components, helpers etc. But be careful applying this function to none trivial arrays - you may find that it doesn't quite do what you want/expect.

AD7six
  • 63,116
  • 12
  • 91
  • 123