3

It is kind of hard for me to explain what I am trying to do so I will just provide an example

class A {
    static $data = ['a'];

    static function getData() { return static::$data; }
}

class B extends A {
    static $data = ['b'];
}

class C extends B {
    static $data = ['c'];
}

class D extends B {
    static $data = ['d'];
}

$a = new A;
$b = new B;
$c = new C;
$d = new D;

$a::getData(); // Output: Array('a'), Expected: Array('a');
$b::getData(); // Output: Array('b'), Expected: Array('a', 'b');
$c::getData(); // Output: Array('c'), Expected: Array('a', 'b', 'c');
$c::getData(); // Output: Array('d'), Expected: Array('a', 'b', 'd');

Is this possible at all?

EDIT: I have my data objects and each object has a set of rules for its properties. For example a User object has a property name which can be maximum 10 symbols, I define this in the user class' rules and then all user objects will obey the rule when their property name is about to be changed. The rules array is static because they apply to all objects of this class. However when I inherit it for example in a VIP user then the VIP will need to have additional rules for properties that the basic user doesn't have. I need to be able to extend the rules array but if I define such an array in the child class it just overwrites the parent rules which I also need.

6
  • Have you actually tried what you want to try? Commented Jul 29, 2015 at 9:13
  • @D4V1D Yes, but the results are only the elements the classes are declared with. Commented Jul 29, 2015 at 9:15
  • So please be more precise on what is expected and what is the actual output. Commented Jul 29, 2015 at 9:16
  • @D4V1D edited, I hope it is clearer now. Commented Jul 29, 2015 at 9:17
  • Is is possible? Yes. Is that indeed needed? I doubt. Please, explain the circumstances around your case. Commented Jul 29, 2015 at 9:17

3 Answers 3

2

So yes, there is a way to do it with only parent class method:

class A
{
    public static $data = ['a'];

    public static function getData()
    {
        $result = static::$data;
        $class  = get_called_class();
        while ($class = get_parent_class($class)) {
            $result = array_merge($result, $class::$data);
        }

        return $result;  
    }
}

class B extends A 
{
    public static $data = ['b']; 
}

class C extends B 
{
    public static $data = ['c']; 
}

class D extends C 
{
    public static $data = ['d']; 
}

Demo here.

If order matters, then change merge arguments order (right now it will be like it is in class hierarchy chain - from child to parent)

Or, utilize class_parents()

class A
{
    public static $data = ['a'];

    public static function getData()
    {
        $classes  = [get_called_class()]; //no class with name "0"
        $classes += class_parents($classes[0]);
        return call_user_func_array('array_merge', 
            array_map(
                function($class) {
                    return $class::$data;
                }, 
                $classes
            )
        );
    }
}

Demo here. This is even shorter way. So it can be done with plain array mapping. Unfortunately, current class has to be added manually to the iterated hierarchy array.

Sign up to request clarification or add additional context in comments.

3 Comments

I guess this is the cleanest solution of the ones suggested. The only thing troubling me is that this will run every time a property is changed, which means 10s of times per request. Maybe I could come up with a way to save the merged array into the child class the first time getData() is called.
array_merge() is slow by definition. By there's a trick around it: instead of doing merge in-place, gather your $result as elements and do return call_user_func_array('array_merge', $result) - same result, only one merge.
@AlmaDo I've taken the liberty of simplifying your 2nd suggestion in terms of execution, removed 2 function calls and array declaration, as OP seems to be concerned about performance. Maybe narrowing it down to a simple loop would improve it even further but I'm not quite an internals guy so I can't definitely tell.
2

This will achieve what you want

class A {
    static $data = ['a'];

    static function getData() { return self::$data; }
}

class B extends A {
    static $data = ['b'];

    static function getData() { return array_merge(parent::getData(), self::$data); }
}

class C extends B {
    static $data = ['c'];

    static function getData() { return array_merge(parent::getData(), self::$data); }
}

class D extends B {
    static $data = ['d'];

    static function getData() { return array_merge(parent::getData(), self::$data); }
}

$a = new A;
$b = new B;
$c = new C;
$d = new D;

var_dump($a::getData()); // Array('a');
var_dump($b::getData()); // Array('a', 'b');
var_dump($c::getData()); // Array('a', 'b', 'c');
var_dump($d::getData()); // Array('a', 'b', 'd');

Demo

though there's probably a slightly cleaner way of doing it defining getData() only in class A

1 Comment

Thank you for the suggestion. I suppose I could use a trait for this but I'm still hoping there might be a simpler solution :c
2

You want to merge the arrays, but in your example code you never specify this. The call to get_parent_class(get_called_class()) gets the parent class, or false if it doesn't exist.

Also, you cannot use self, but you must use static if you want to use the value of the actual class that was called. self will always use the same value.

class A {
    static $data = ['a'];

    static function getData() {
        $parent = get_parent_class(get_called_class());
        if(method_exists($parent, 'getData')) {
            return array_merge($parent::getData(), static::$data);
        } else {
            return static::$data;
        }
    }
}
// other classes unchanged

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.