How to inherit parent class array properties by merging array?
Asked Answered
G

3

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?
Grapnel answered 19/1, 2013 at 18:26 Comment(0)
C
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
)
Cystoscope answered 19/1, 2013 at 18:56 Comment(5)
That's also a good idea. It would help avoid some strict warnings in PHP 5.3Grapnel
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 =)?Godspeed
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.Grapnel
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.Fechner
I prefer the answer of @synexis. This one needs too much manual coding.Ketch
T
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
Thistle answered 22/4, 2016 at 5:44 Comment(0)
C
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
)
Cystoscope answered 19/1, 2013 at 18:56 Comment(5)
That's also a good idea. It would help avoid some strict warnings in PHP 5.3Grapnel
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 =)?Godspeed
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.Grapnel
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.Fechner
I prefer the answer of @synexis. This one needs too much manual coding.Ketch
G
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.

Godspeed answered 19/1, 2013 at 18:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.