Search code examples
phparraysmultidimensional-arrayarray-merge

Merge array recursively in extended class


I have the following code in php:

<?php
interface someInterface {
    public function foo();
}

class A implements someInterface {
    public function foo(){
        return [
            'a' => 'some value',
            'b' => 'some value',
            'c' => [
                'ca' => 'some value',
                'cb' => 'some value',
                'cc' => 'some value'
            ]
        ];
    }
}

class B extends A {
    public function foo(){
        return [
            'a' => 'some value 2',
            'c' => [
                'ca' => 'some value 3',
                'cb' => 'some value 4'
            ]
        ];
    }
}

Doing the following

$b = new B();
var_dump($b->foo());

must give me the following output

[
    'a' => 'some value 2',
    'b' => 'some value',
    'c' => [
        'ca' => 'some value 3',
        'cb' => 'some value 4',
        'cc' => 'some value'
    ]
]

Does anyone know how to achieve this?

Anyone can extend class A and I don't want other users to manually use array_merge_recursive for achieving this.

Thanks in advance


Solution

  • You can have a class in the middle that does the work. It should have another method that will be inherited and later used. The only condition here is to not use foo() in public, but getFoo() instead

    interface someInterface {
        public function foo();
    }
    
    
    class A implements someInterface {
        public function foo(){
            return [
                'a' => 'some value',
                'b' => 'some value',
                'c' => [
                    'ca' => 'some value',
                    'cb' => 'some value',
                    'cc' => 'some value'
                ]
            ];
        }
    }
    
    class Middle extends A {
        public function getFoo() {
            return array_merge_recursive(parent::foo(), $this->foo());
        }
    }
    
    class B extends Middle {
        public function foo(){
            return [
                'a' => 'some value 2',
                'c' => [
                    'ca' => 'some value 3',
                    'cb' => 'some value 4'
                ]
            ];
        }
    }
    
    $b = new B();
    var_dump($b->getFoo());
    

    This could also be achieved making foo() protected and a magic __call() method which does the work of getFoo(). Then calling $b->foo() will trigger __call() thus resulting in merging that parent and this contexts of foo() and spitting them out.

    By the way, array merge recursive will not result into the exactly the same output you do want, anyway, you can do whatever logic you need in getFoo() (or in __call())

    A problem here could be that if foo() has lower access modifier than public you cannot have it in an interface. Luckily, abstract classes could have abstract protected methods which will obligate the childred to implement this method.

    A sample scenario of the __call() way and obligating members to have protected foo() could be:

    abstract class Base {
        abstract protected function foo();
    }
    
    abstract class A {
        protected function foo(){
            return [
                'a' => 'some value',
                'b' => 'some value',
                'c' => [
                    'ca' => 'some value',
                    'cb' => 'some value',
                    'cc' => 'some value'
                ]
            ];
        }
    }
    
    class Middle extends A {
        public function __call($name, $args) {
            if ($name == 'foo')
                return array_merge_recursive(parent::foo(), $this->foo());
        }
    }
    
    class B extends Middle {
        protected function foo(){
            return [
                'a' => 'some value 2',
                'c' => [
                    'ca' => 'some value 3',
                    'cb' => 'some value 4'
                ]
            ];
        }
    }
    
    $b = new B();
    var_dump($b->foo());
    

    This way B will have some problems with autocompletion of foo. If One does not know the "hack" when writing $b-> will not recieve suggestion for foo(). A little more hacks can resolve this in at least Netbeans and PHPStorm

    Annotating that Middle has a public foo() method before definition of Middle class:

    /**
     * @method foo
     */
    class Middle extends A {
    

    Later annotating that the instance of B is actually an instance of Middle:

    $b = new B();
    /* @var $b Middle */
    

    Afterwards writing $b-> will result into suggesting the foo() method.

    It might be better if there is another class that inherits B and has no methods, but only annotations, but as I understood, classes like B will be written from other people and they might not be familiar with this convention.