Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP: Design pattern for classes offering static and non-static methods

My objective is to create class that can be used both static and non-static way. Both ways have to use the same methods, but in different way

Non-static way:

$color = new Color("#fff");
$darkenColor = $color->darken(0.1);

Static way:

$darkenColor = Color::darken("#fff", 0.1);

So in this example method darken can be used both on existing object and as static method of Color class. But depending on how it's used, it uses different parameters.

How should such class be designed? What is good pattern for creating such types of classes?

Class will have a lot of different methods so it should avoid massive checking code in beginning of each method.

like image 945
Adam Pietrasiak Avatar asked Oct 19 '22 14:10

Adam Pietrasiak


2 Answers

PHP doesn't really support method overloading, so it's not all that simple to realize, but there are ways.

Why provide static and non-static?

What I would ask myself first though is if it is really needed to offer both static and non-static approaches. It seems overly complicated, possibly confusing to the users of your color class, and doesn't seem to add all that many benefits. I would just go with the non-static approach and be done with it.

Static Factory Class

What you basically want are static factory methods, so you could create an extra class that realizes this:

class Color {

    private $color;

    public function __construct($color)
    {
        $this->color = $color;
    }

    public function darken($by)
    {
        // $this->color = [darkened color];
        return $this;
    }
}

class ColorFactory {
    public static function darken($color, $by) 
    {
        $color = new Color($color);
        return $color->darken($by);
    }
}

An alternative would be to put the static method inside Color and give it a different name, eg createDarken (this should be the same each time, so all static factory methods would be called createX for user convenience).

callStatic

Another possibility is to use the magic methods __call and __callStatic. The code should look something like this:

class Color {

    private $color;

    public function __construct($color)
    {
        $this->color = $color;
    }

    // note the private modifier, and the changed function name. 
    // If we want to use __call and __callStatic, we can not have a function of the name we are calling in the class.
    private function darkenPrivate($by) 
    {
        // $this->color = [darkened color];
        return $this;
    }

    public function __call($name, $arguments)
    {
        $functionName = $name . 'Private';
        // TODO check if $functionName exists, otherwise we will get a loop
        return call_user_func_array(
            array($this, $functionName),
            $arguments
        );
    }

    public static function __callStatic($name, $arguments)
    {
        $functionName = $name . 'Private';
        $color = new Color($arguments[0]);
        $arguments = array_shift($arguments);
        // TODO check if $functionName exists, otherwise we will get a loop
        call_user_func_array(
            array($color, $functionName),
            $arguments
        );
        return $color;

    }
}

Note though that this is somewhat messy. Personally, I would not use this approach, because it's not that nice for the users of your class (you can't even have proper PHPDocs). It is the simplest for you as programmer though, because you do not need to add a lot of extra code when adding new functions.

like image 172
tim Avatar answered Nov 03 '22 02:11

tim


According to your code examples and your comments, I understand that your method ->darken() will modify a property in the $color object, based on the current value of that property. On the other hand, the static ::darken() method will return the value of the darkened color...

If I understood it correctly, you may do something like:

class Color {

    protected $value;

    public static function darkenColor($value, $coef) {
        $darkened = //Darken $value using $coef somehow...
        return $darkened;
    }

    public function darken($coef) {
        $darkened = Color::darkenColor($this->value, $coef);
        $this->value = $darkened;
        return $darkened;
    }
}

With this approach, you don't need to repeat the code of darkening the color, and you offer both the dynamic and static methods from the class.
The static method "does the job", and can be called independently, and the dynamic method just uses the static one to make the calculations and assign the result to the object property.
Note that you have to use different names for the methods as PHP does not support overloading methods...


That said, personally I'd move the static methods to a different class(es), e.g., ColorManager or ColorCalculator, or even better, if the code to darken a color is complex enough and you'll have more operations like that, I'd create a ColorDarkener class with just that method...

class Color {

    protected $value;

    public function darken($coef) {
        $darkened = ColorDarkener::darken($this->value, $coef);
        $this->value = $darkened;
        return $darkened;
    }
}

class ColorDarkener {

    public static function darken($value, $coef) {
        $darkened = //Darken $value using $coef somehow...
        return $darkened;
    }
}
like image 37
MikO Avatar answered Nov 03 '22 04:11

MikO