views:

261

answers:

2

I'm working on a class which needs to be accessible via static function calls as well as object methods. One thing I have found is that I'm duplicating logic across multiple functions.

Simplified example:

class Configurable{

    protected $configurations = array();

    protected static $static_configurations = array();

    public function configure($name, $value){

        // ...lots of validation logic...

        $this->configurations[$name] = $value;

        }

     public static function static_configure($name, $value){

        // ...lots of validation logic (repeated)...

        self::$static_configurations[$name] = $value;

        }

    }

I've found a solution to this, but it feels really dirty:

class Configurable{

    protected $configurations = array();

    protected static $static_configurations = array();

    public function configure($name, $value){

        // ...lots of validation logic...

        if (isset($this)){
            $this->configurations[$name] = $value;
            }
        else{
            self::$static_configurations[$name] = $value;
            }

        }

    }

I need the static function as well so that I can set configurations throughout the application. Also, the nice thing with this technique is that I can use the same method names in both scopes.

Are there any problems with testing scope like this? Performance issues, forward compatibility issues, etc. It all works for me on PHP 5.2, and I don't need to support <5.

+1  A: 

The issue with the second method is that it will result in an error when error reporting is set to E_STRICT. For example:

Strict standards: Non-static method Foo::bar() should not be called statically in /home/yacoby/dev/php/test.php on line 10

A point with PHP6 is that the E_STRICT errors are moved to E_ALL. In other words E_ALL will cover all errors including not allowing you to call non static methods statically.

An alternative method may be to move the validation logic to a static function. That way the non static function and the static function can call the validation logic.

Yacoby
OK, apart from the strict error are there be any issues with the second method? I will probably change it on your advice anyway but I'd like to understand the real issues with using code like this. Is it a feature that's likely to be removed from future PHP versions or will you always be able to call non-static methods statically?
Rowan
`E_STRICT` errors tend to be "This seems like a really bad idea, but we're going to let you try to do it anyway." There are no guarantees that such a thing will always work, but they'll probably be converted to `E_NOTICE` or `E_WARN` before completely deprecated.
MightyE
Thanks for clarifying that @MightyE
Rowan
+1  A: 

Static methods would require a different number of arguments than their objective counterpart - the additional argument would be an execution context. If there's no execution context, then it only makes sense to call it statically.

My preferred approach given that I'm building a library with multiple interfaces like this, is to create a static class and a dynamic class. Have one proxy the calls to the other. For example:

class DynamicClass {
    protected $foo;
    protected $bar;
    public function baz($arg1) {
        return StaticClass::bar($this->foo, $arg1);
    }
    public function zop($arg1, $arg2) {
        return StaticClass::zop($this->foo, $this->bar, $arg1, $arg2);
    }
    // Context-less helper function
    public function womp($arg1) {
        return StaticClass::womp($arg1);
    }
}

class StaticClass {
    public static function baz(&$fooContext, $arg1) { ... }
    public static function zop(&$fooContext, &$barContext, $arg1, $arg2) { ... }
    public static function womp($arg1) { ... }
}

It's up to you exactly how you pass context to the static class - you'll have to do whatever makes sense for you. The work done in most functions should be pretty minor (if you're doing a lot, then you probably should be breaking the work up into smaller functions as a rule), and so should only require a handful of context arguments. Or you could create a full context array and pass that around everywhere (either populating it in DynamicClass just before each call, or else track all DynamicClass properties in that array so you can quickly & easily pass it around.


Though actually it looks like you might benefit from a Singleton design pattern. From what I can see, you're trying to create a global Configurable, and also have the option to create individual local Configurables. With the singleton design pattern, you create a globally accessible version of a class that you can guarantee you only have one of (without breaking OOP design principles and having to rely on $_GLOBALS etc). For example:

class DynamicClass {
    protected $foo;
    protected $bar;

    public function baz($arg1) { ... }
    public function zop($arg1, $arg2) { ... }

    public static function getSingleton() {
        static $instance = null;
        if ($instance === null) $instance = new DynamicClass();
        return $instance;
    }
}

No matter where in your code you are, you can get access to the same instance with DynamicClass::getSingleton(). You also have the option of creating one-off non-singleton versions. You essentially get the best of both worlds while only having to write all your methods with dynamic access in mind exclusively.

MightyE