3

I'm building a framework and I'm stuck with a small problem. Well, more than a problem I'd call it a challenge. While building the widget system, I found that I wanted some basic functionality for all of my widgets, namely a build() static method that would allow me to easily instantiate any widget and then chain the next method, like this:

$code = MySpecificWidget::build()->getHTML();

For that, I wrote this code:

abstract class StandardWidget {

  public static function build() {
    $className = get_called_class(); // the "late static binding" classname
    return new $className;
  }

}

class MySpecificWidget extends StandardWidget {

  public function getHTML() {
    return '<b>Some HTML</b>';
  }

}

So far so good. But what if the developers were to write widgets that need parameters when constructed?

What I tried to do was this:

abstract class StandardWidget {

  public static function build() {
    return call_user_func_array(
      array(
        get_called_class(),      // the "late static binding" classname
        '__construct'            // the method in the target class to invoke
      ),
      func_get_args()            // pass along all (if any) of the arguments received
    );
  }

}

So that you'd only have to write __construct() in the subclass if parameters were needed.

Unfortunately, the PHP parser informed me that __construct() cannot be called statically.

Since I'm writing a framework, I'm trying to be as lightweight as possible. So here's the challenge:

Is there any way of doing this without:

  • Using eval()
  • Using Reflection
  • Having to forcefully write constructors in every subclass

Update

Some more insights of why do I want it like this.

The idea behind it is that most of the time, a widget will be instantiated, then asked to return its HTML and finally destroy itself forever as it is won't be needed anymore. They will most likely be used as parameters inside associative arrays (parameters to be passed along to my templating system), so I think that having them instantiated "anonymously" and in one line would be really useful and code saving.

Widgets cannot be static classes because they may have an internal status (only in that case I won't need to chain its constructor), and in order to avoid it becoming cumbersome (as I don't want every widget to have a constructor if it doesn't need one), I'd like the build method to be in the abstract class from which all widgets inherit.

I think the best way would be the one I wrote above, if only I could invoke the magic constructor like that...

2

4 Answers 4

1

I don't know if there is any special need for you build method, but what you are trying to do is a kind of Factory pattern.

One easy solution for your problem is to say "Widget constructor have to take an array as single parameter". Thus you get the parameters using func_get_args and pass that array to the widget constructor.

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

1 Comment

That's not a bad idea, albeit a bit cumbersome as it's not transparent. I'm going to wait up and see if anyone else comes up with something else before accepting your answer. Thanks!
1

Since __construct initializes but doesn't allocate an object, you can't get around using new or ReflectionClass::newInstance (the only ways of allocating a new object, as far as I know). This suggests you could create an instance with new, then call __construct a second time to initialize the object.

<?php

class A {
    function __construct() {
        echo __CLASS__ , '/', get_called_class(), "\n";
    }

    static function build() {
        $class = get_called_class();
        $args = func_get_args();
        echo "Building $class:\n";
        $instance = new static;
        # don't bother calling constructor a second time if there are no arguments
        if ($args) {
            echo "Calling $class::__construct:\n";
            call_user_func_array(array($instance, '__construct'), $args);
        }
        echo "done\n";
        return $instance;
    }
}

class B extends A {
    function __construct($foo=Null, $bar=Null) {
        if (! is_null($foo)) {
            parent::__construct();
            echo __CLASS__ , '/', get_called_class(), "::__construct($foo, $bar)\n";
        }
    }
}
class C extends A {}

$b = B::build(1, 2);
$c = C::build();

This approach can't be transparent as it has two requirements for children: all constructor arguments must be optional, and they must make use of some mechanism to prevent double-initialization. The approached used here, testing whether the first argument is Null, won't work for constructors that truly have an optional first argument, as the call patterns for direct and indirect invocation of __construct are indistinguishable. In such cases, debug_backtrace could be used to distinguish between them, but this causes an additional problem when build is called without arguments: when the constructor is invoked indirectly in build, it can't determine whether or not it will later be invoked directly. It's also not so lightweight.

class ProblemWidget extends StandardWidget {
    function __construct($id=Null) {
        $trace = debug_backtrace();
        if ($trace[1]['function'] == 'call_user_func_array') {
            # direct invocation
        ...
        parent::__construct();
        if (is_null($id)) {
            $this->id = self::generateId();
        } else {
            $this->id = $id;
        }
        ...
    }
}

/* Depending on the implementations of ProblemWidget::__construct and 
 * StandardWidget::build, either $oops won't be initialized, or $oops2
 * will be double-initialized.
 */
$oops = ProblemWidget::build();
$oops2 = ProblemWidget::build(42);

You could go a level higher in the stack trace in an attempt to tell wether there will be a second invocation by examining the argument count to build, but it's not particularly elegant.

class HeavyWidget extends StandardWidget {
    function __construct($id=Null) {
        $trace = debug_backtrace();
        if (! is_null($id)                                     # there's an argument
            || count($trace) <= 2                              # not called within `build`
            || $trace[1]['function'] == 'call_user_func_array' # direct
            || ($trace[2]['function'] == 'build'               # indirect, no 2nd invocation
                && count($trace[2]['args']) == 0)
            || $trace[2]['function'] != 'build'                # indirect, not called within `build`
            )
        {
            /* direct invocation, or there aren't any arguments so there 
             * won't be a second (direct) invocation
             */ 
            parent::__construct();
            if (is_null($id)) {
                $this->id = self::generateId();
            } else {
                $this->id = $id;
            }
            ...
        }
    }
}

All in all, you're probably better off either requiring constructors to take an array containing the arguments or using reflection. With the former option, constructors could be dual-purposed by allowing the first argument to be an array (when called from StandardWidget::build) with the remaining arguments optional. When invoked indirectly, the arguments can be passed as separate arguments.

class MySpecificWidget extends StandardWidget {
    function __construct($args, $foo=Null) {
        if (is_null($foo) && is_array($args)) {
            /* called from StandardWidget::build(); $args contains all arguments.
             * Extract them.
             */
            $foo = $args[1];
            $args = $args[0];
        }
        ...
    }
}

$frob = new MySpecificWidget(42,23);

Note this has a similar problem to the earlier code: it might not be possible to distinguish direct and indirect invocation based on the argument pattern if the first argument might be a (non-argument) array and the second null in indirect invocation.

class NopDelegate {
    function __get($name) {}
function __call($name,$arguments) {}
    function __callStatic($name,$arguments) {}
}

class ProblemWidget extends StandardWidget {
    function __construct($values, $delegate=Null) {
        parent::__construct();
        $this->values = $values;
        if (is_null($delegate)) {
            $this->delegate = new NopDelegate;
        }
    }
}

$works = new ProblemWidget(array('a', 'b', 'c'));
$works2 = new ProblemWidget(array('a', 'b', 'c'), new SomeDelegate());

$oops = ProblemWidget::make(array('a', 'b', 'c'));
$oops2 = ProblemWidget::make(array('a', 'b', 'c'), new SomeDelegate());
$oops_also = ProblemWidget::make('a', 'b', 'c', new SomeDelegate());

Thus the only truly transparent option is to use reflection and accept the performance hit.

1 Comment

Yeah, I think I'm better off with Reflection. It's a shame it can't be pulled off with call_user_func_array(). Thanks!
0

Are you sure that you need this static build method? I don't see any advantage of favouring your build() method over new other than saving a LOC.

$widget = new MySpecificWidget($param1, $param2, $param500);
$code = $widget->getHTML();

Should work perfectly fine and allows to have different parameters in the constructor. I think your built method introduces unneccesary complexity and solves a non-problem.

5 Comments

The build method could allow some other stuff to be done, such as registering and so on
Wouldn't the parent constructor be a more appropriate place for this?
@middus: Thing is, the whole idea of it is to be able to chain the constructor and the following method in one line. For example, if I'm passing the widget's HTML as a parameter to another function, or putting it inside an array, creating one instance of one widget wouldn't be much of a problem... but let's say that I have 5 o 10 widgets, that would be 5 o 10 lines of code that are there only to clutter up. There's no point in saving them unless I want to reference them later on. Maybe I should emphasize this more in my question.
@Boro Okay, understood: this is just a glorified sprintf!? If you instantiate just to throw away, why instantiate objects in the first place? Just wrap it in a function and you're done. Alternatively, you'll probably have to introduce some kind of init method that takes parameters.
Not quite, since a widget may have some complex functionality within. For example, a pager widget has to resolve how many pages should it be, and only then return its html.
-1

What about allowing the getHtml function to accept an array as a parameter that can then be used by the developer as needed?

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.