0

In an existing code base, I have a static builder method that returns an instance. Here's a simplified example:

class Grandparent{
}

class Parent extends Grandparent{
}

class Child extends Parent{
    public static fetchChildById($id){
        // ...
        return new Child;
    }
}

In real code I have a single Grandparent class and several subclasses similar to Parent and Child (not just Parent and Child).

I now need to implement a new method at Grandparent to be used at fetchChildById(). Such method needs to make use of certain piece of data that's common to all children from the same parent. Since I don't have a class instance yet I'm forced to make everything static but, of course, that won't work properly because it isn't possible to overrite static properties and methods:

class Grandparent{
    protected static $data = array(
        'default',
    );

    protected static function filter(){
        foreach(self::$data as $i){ // <!-- Will always be `default'
            // ...
        }
    }
}

class Parent extends Grandparent{
    protected static $data = array(
        'one',
        'two',
    );
}

class Child extends Parent{
    public static fetchChildById($id){
        self::filter();
        // ...
        return new Child;
    }
}

I believe it's a use case for late static binding but code needs to run on PHP/5.2.0 :(

I'm not very fond of the obvious workarounds I've thought about:

  • Creating a separate builder class suggest more refacting than I can afford at this time:

    $builder = new ChildBuilder;
    $bart = $builder->fetchChildById(1);
    
  • Creating additional instances looks ugly (and implies many changes as well):

    $builder = new Child;
    $bart = $builder->fetchChildById(1);
    
  • Global variables... Oh well, I'm not so desperate yet.

Am I overlooking some obvious mechanism to customize $data?

4
  • Tight spot. Did you consider variations of workaround #2? At first sight it seems fetchChildById will need to create an instance of the called class anyway; why not use that instance to buy virtual method calls inside fetchChildById itself? Commented Jul 18, 2014 at 10:31
  • What about some debug_backtrace? Commented Jul 18, 2014 at 10:34
  • @Jon - Egg and chicken. Data is needed to create the instance. And I can't easily create a dummy instance because of mandatory arguments in constructor. Commented Jul 18, 2014 at 10:38
  • @sectus - Yeah... Could work if I figure out the details... I'm working on a variation of that, I'll report back if I go somewhere. Commented Jul 18, 2014 at 10:38

1 Answer 1

1

Here's an alternative using reflection. It will require modification of all fetchChildById implementations, but it's trivial enough to be done with global find/replace:

self::filter(__CLASS__); // this is the modification

Then filter would become:

protected static function filter($className){
    $reflect = new ReflectionClass($className);
    $data = $reflect->getStaticPropertyValue('data');
    foreach($data as $i){
        // ...
    }
}

Update: The property $data needs to be public for the above to work (apologies -- I wrote public during exploration). But there's an equivalent version that doesn't have this requirement:

$reflect = new ReflectionProperty($className, 'data');
$reflect->setAccessible(true);
$data = $reflect->getValue();
Sign up to request clarification or add additional context in comments.

4 Comments

Absolutely feasible. Thanks a lot for the tip!
@ÁlvaroG.Vicario: After a little more tinkering it seems you can use get_class_vars and fish $data out of that as an alternative to reflection. Might be faster, no idea. Glad to help.
Confirmed: getStaticPropertyValue() does work. In my case I need to use get_parent_class() instead of __CLASS__ but property needs to be public.
@ÁlvaroG.Vicario: You can work around the public requirement easily -- updated the answer with reflection code, get_class_vars should work out of the box as long as you are calling it inside Grandparent because it takes the current scope into account. But why do you need to use get_parent_class?

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.