Chaining Static Methods in PHP?

PhpOopMethod Chaining

Php Problem Overview


Is it possible to chain static methods together using a static class? Say I wanted to do something like this:

$value = TestClass::toValue(5)::add(3)::subtract(2)::add(8)::result();

. . . and obviously I would want $value to be assigned the number 14. Is this possible?

Update: It doesn't work (you can't return "self" - it's not an instance!), but this is where my thoughts have taken me:

class TestClass {
	public static $currentValue;
	
	public static function toValue($value) {
		self::$currentValue = $value;
	}
	
	public static function add($value) {
		self::$currentValue = self::$currentValue + $value;
		return self;
	}
	
	public static function subtract($value) {
		self::$currentValue = self::$currentValue - $value;
		return self;
	}
	
	public static function result() {
		return self::$value;
	}
}

After working that out, I think it would just make more sense to simply work with a class instance rather than trying to chain static function calls (which doesn't look possible, unless the above example could be tweaked somehow).

Php Solutions


Solution 1 - Php

I like the solution provided by Camilo above, essentially since all you're doing is altering the value of a static member, and since you do want chaining (even though it's only syntatic sugar), then instantiating TestClass is probably the best way to go.

I'd suggest a Singleton pattern if you want to restrict instantiation of the class:

class TestClass
{	
	public static $currentValue;
	
	private static $_instance = null;
	
	private function __construct () { }
	
	public static function getInstance ()
	{
		if (self::$_instance === null) {
			self::$_instance = new self;
		}
		
		return self::$_instance;
	}
	
	public function toValue($value) {
		self::$currentValue = $value;
		return $this;
	}
	
	public function add($value) {
		self::$currentValue = self::$currentValue + $value;
		return $this;
	}
	
	public function subtract($value) {
		self::$currentValue = self::$currentValue - $value;
		return $this;
	}
	
	public function result() {
		return self::$currentValue;
	}
}

// Example Usage:
$result = TestClass::getInstance ()
	->toValue(5)
	->add(3)
	->subtract(2)
	->add(8)
	->result();

Solution 2 - Php

class oop{
	public static $val;

    public static function add($var){
	    static::$val+=$var;
	    return new static;
    }

    public static function sub($var){
	    static::$val-=$var;
	    return new static;
    }

    public static function out(){
	    return static::$val;
    }

    public static function init($var){
	    static::$val=$var;
	    return new static;		
    }
}

echo oop::init(5)->add(2)->out();

Solution 3 - Php

Little crazy code on php5.3... just for fun.

namespace chaining;
class chain
    {
    static public function one()
        {return get_called_class();}

    static public function two()
        {return get_called_class();}
    }

${${${${chain::one()} = chain::two()}::one()}::two()}::one();

Solution 4 - Php

With php7 you will be able to use desired syntax because of new Uniform Variable Syntax

<?php

abstract class TestClass {

    public static $currentValue;

    public static function toValue($value) {
        self::$currentValue = $value;
        return __CLASS__;
    }

    public static function add($value) {
        self::$currentValue = self::$currentValue + $value;
        return __CLASS__;
    }

    public static function subtract($value) {
        self::$currentValue = self::$currentValue - $value;
        return __CLASS__;
    }

    public static function result() {
        return self::$currentValue;
    }

}

$value = TestClass::toValue(5)::add(3)::subtract(2)::add(8)::result();
echo $value;

Demo

Solution 5 - Php

If toValue(x) returns an object, you could do like this:

$value = TestClass::toValue(5)->add(3)->substract(2)->add(8);

Providing that toValue returns a new instance of the object, and each next method mutates it, returning an instance of $this.

Solution 6 - Php

This is more accurate, easier, and read-friendly (allows code-completion)

class Calculator
{   
    public static $value = 0;

    protected static $onlyInstance;

    protected function __construct () 
    {
        // disable creation of public instances 
    }

    protected static function getself()
    {
        if (static::$onlyInstance === null) 
        {
            static::$onlyInstance = new Calculator;
        }

        return static::$onlyInstance;
    }

    /**
     * add to value
     * @param numeric $num 
     * @return \Calculator
     */
    public static function add($num) 
    {
        static::$value += $num;
        return static::getself();
    }

    /**
     * substruct
     * @param string $num
     * @return \Calculator
     */
    public static function subtract($num) 
    {
        static::$value -= $num;
        return static::getself();
    }

    /**
     * multiple by
     * @param string $num
     * @return \Calculator
     */
    public static function multiple($num) 
    {
        static::$value *= $num;
        return static::getself();
    }

    /**
     * devide by
     * @param string $num
     * @return \Calculator
     */
    public static function devide($num) 
    {
        static::$value /= $num;
        return static::getself();
    }

    public static function result()
    {
        return static::$value;
    }
}

Example:

echo Calculator::add(5)
        ->subtract(2)
        ->multiple(2.1)
        ->devide(10)
    ->result();

result: 0.63

Solution 7 - Php

People are overcomplicating this like crazy.

Check this out:

class OopClass
{
    public $first;
    public $second;
    public $third;

    public static function make($first)
    {
        return new OopClass($first);
    }

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

    public function second($second)
    {
        $this->second = $second;
        return $this;
    }

    public function third($third)
    {
        $this->third = $third;
        return $this;
    }
}

Usage:

OopClass::make('Hello')->second('To')->third('World');

Solution 8 - Php

You could always use the First method as a static and the remaining as instance methods:

$value = Math::toValue(5)->add(3)->subtract(2)->add(8)->result();

Or better yet:

 $value = Math::eval(Math::value(5)->add(3)->subtract(2)->add(8));

class Math {
     public $operation;
     public $operationValue;
     public $args;
     public $allOperations = array();

     public function __construct($aOperation, $aValue, $theArgs)
     {
       $this->operation = $aOperation;
       $this->operationValue = $aValue;
       $this->args = $theArgs;
     }

     public static function eval($math) {
       if(strcasecmp(get_class($math), "Math") == 0){
            $newValue = $math->operationValue;
            foreach ($math->allOperations as $operationKey=>$currentOperation) {
                switch($currentOperation->operation){
                    case "add":
                         $newvalue = $currentOperation->operationValue + $currentOperation->args;
                         break;
                    case "subtract":
                         $newvalue = $currentOperation->operationValue - $currentOperation->args;
                         break;
                }
            }
            return $newValue;
       }
       return null;
     }

     public function add($number){
         $math = new Math("add", null, $number);
         $this->allOperations[count($this->allOperations)] &= $math;
         return $this;
     }

     public function subtract($number){
         $math = new Math("subtract", null, $number);
         $this->allOperations[count($this->allOperations)] &= $math;
         return $this;
     }

     public static function value($number){
         return new Math("value", $number, null);
     }
 }

Just an FYI.. I wrote this off the top of my head (right here on the site). So, it may not run, but that is the idea. I could have also did a recursive method call to eval, but I thought this may be simpler. Please let me know if you would like me to elaborate or provide any other help.

Solution 9 - Php

Technically you can call a static method on an instance like $object::method() in PHP 7+, so returning a new instance should work as a replacement for return self. And indeed it works.

final class TestClass {
    public static $currentValue;

    public static function toValue($value) {
        self::$currentValue = $value;
        return new static();
    }

    public static function add($value) {
        self::$currentValue = self::$currentValue + $value;
        return new static();
    }

    public static function subtract($value) {
        self::$currentValue = self::$currentValue - $value;
        return new static();
    }

    public static function result() {
        return self::$currentValue;
    }
}

$value = TestClass::toValue(5)::add(3)::subtract(2)::add(8)::result();

var_dump($value);

Outputs int(14).

This about same as returning __CLASS__ as used in other answer. I rather hope no-one ever decides to actually use these forms of API, but you asked for it.

Solution 10 - Php

In a nutshell... no. :) The resolution operator (::) would work for the TetsClass::toValue(5) part, but everything after that will just give a syntax error.

Once namespaces are implemented in 5.3, you can have "chained" :: operators, but all that'll do is drill down through the namespace tree; it won't be possible to have methods in the middle of things like this.

Solution 11 - Php

The best that can be done

class S
{
	public static function  __callStatic($name,$args)
	{
		echo 'called S::'.$name . '( )<p>';
		return '_t';
	}
}

$_t='S';
${${S::X()}::F()}::C();

Solution 12 - Php

No, this won't work. The :: operator needs to evaluate back to a class, so after the TestClass::toValue(5) evaluates, the ::add(3) method would only be able to evaluate on the answer of the last one.

So if toValue(5) returned the integer 5, you would basically be calling int(5)::add(3) which obviously is an error.

Solution 13 - Php

The most easiest way i have ever found for method chaining from new Instance or Static method of class is as below. I have used Late Static Binding here and i really loved this solution.

I have created a utility to send multiple User Notification on next page using tostr in Laravel.

<?php

namespace App\Utils;

use Session;

use Illuminate\Support\HtmlString;

class Toaster
{
	private static $options = [

		"closeButton" => false,

		"debug" => false,

		"newestOnTop" => false,

		"progressBar" => false,

		"positionClass" => "toast-top-right",

		"preventDuplicates" => false,

		"onclick" => null,

		"showDuration" => "3000",

		"hideDuration" => "1000",

		"timeOut" => "5000",

		"extendedTimeOut" => "1000",

		"showEasing" => "swing",

		"hideEasing" => "linear",

		"showMethod" => "fadeIn",

		"hideMethod" => "fadeOut"
	];

	private static $toastType = "success";

	private static $instance;
	
	private static $title;

	private static $message;

	private static $toastTypes = ["success", "info", "warning", "error"];

	public function __construct($options = [])
	{
		self::$options = array_merge(self::$options, $options);
	}

	public static function setOptions(array $options = [])
	{
		self::$options = array_merge(self::$options, $options);

		return self::getInstance();
	}

	public static function setOption($option, $value)
	{
		self::$options[$option] = $value;

		return self::getInstance();
	}

	private static function getInstance()
	{
		if(empty(self::$instance) || self::$instance === null)
		{
			self::setInstance();
		}

		return self::$instance;
	}

	private static function setInstance()
	{
		self::$instance = new static();
	}

	public static function __callStatic($method, $args)
	{
		if(in_array($method, self::$toastTypes))
		{
			self::$toastType = $method;

			return self::getInstance()->initToast($method, $args);
		}

		throw new \Exception("Ohh my god. That toast doesn't exists.");
	}

	public function __call($method, $args)
	{
		return self::__callStatic($method, $args);
	}

	private function initToast($method, $params=[])
	{
		if(count($params)==2)
		{
			self::$title = $params[0];

			self::$message = $params[1];
		}
		elseif(count($params)==1)
		{
			self::$title = ucfirst($method);

			self::$message = $params[0];
		}

		$toasters = [];

		if(Session::has('toasters'))
		{
			$toasters = Session::get('toasters');
		}

		$toast = [

			"options" => self::$options,

			"type" => self::$toastType,

			"title" => self::$title,

			"message" => self::$message
		];

		$toasters[] = $toast;

		Session::forget('toasters');

		Session::put('toasters', $toasters);

		return $this;
	}

	public static function renderToasters()
	{
		$toasters = Session::get('toasters');

		$string = '';

		if(!empty($toasters))
		{
			$string .= '<script type="application/javascript">';

			$string .= "$(function() {\n";

			foreach ($toasters as $toast)
			{
				$string .= "\n toastr.options = " . json_encode($toast['options'], JSON_PRETTY_PRINT) . ";";

				$string .= "\n toastr['{$toast['type']}']('{$toast['message']}', '{$toast['title']}');";
			}

			$string .= "\n});";

			$string .= '</script>';
		}

		Session::forget('toasters');

		return new HtmlString($string);
	}
}

This will work as below.

Toaster::success("Success Message", "Success Title")

    ->setOption('showDuration', 5000)

    ->warning("Warning Message", "Warning Title")

    ->error("Error Message");

Solution 14 - Php

Fully functional example of method chaining with static attributes:

<?php


class Response
{
    static protected $headers = [];
    static protected $http_code = 200;
    static protected $http_code_msg = '';
    static protected $instance = NULL;


    protected function __construct() { }

    static function getInstance(){
        if(static::$instance == NULL){
            static::$instance = new static();
        }
        return static::$instance;
    }

    public function addHeaders(array $headers)
    {
        static::$headers = $headers;
        return static::getInstance();
    }
  
    public function addHeader(string $header)
    {
        static::$headers[] = $header;
        return static::getInstance();
    }

    public function code(int $http_code, string $msg = NULL)
    {
        static::$http_code_msg = $msg;
        static::$http_code = $http_code;
        return static::getInstance();
    }

    public function send($data, int $http_code = NULL){
        $http_code = $http_code != NULL ? $http_code : static::$http_code;
        
        if ($http_code != NULL)
            header(trim("HTTP/1.0 ".$http_code.' '.static::$http_code_msg));
        
        if (is_array($data) || is_object($data))
            $data = json_encode($data);

        echo $data; 
        exit();  	
    }

    function sendError(string $msg_error, int $http_code = null){
        $this->send(['error' => $msg_error], $http_code);
    }
}

Example of use:

Response::getInstance()->code(400)->sendError("Lacks id in request");

Solution 15 - Php

Here's another way without going through a getInstance method (tested on PHP 7.x):

class TestClass
{
    private $result = 0;

    public function __call($method, $args)
    {
        return $this->call($method, $args);
    }

    public static function __callStatic($method, $args)
    {
        return (new static())->call($method, $args);
    }

    private function call($method, $args)
    {
        if (! method_exists($this , '_' . $method)) {
            throw new Exception('Call undefined method ' . $method);
        }

        return $this->{'_' . $method}(...$args);
    }

    private function _add($num)
    {
        $this->result += $num;

        return $this;
    }

    private function _subtract($num)
    {
        $this->result -= $num;

        return $this;
    }

    public function result()
    {
        return $this->result;
    }
}

The class can be used as following:

$res1 = TestClass::add(5)
    ->add(3)
    ->subtract(2)
    ->add(8)
    ->result();

echo $res1 . PHP_EOL; // 14

$res2 = TestClass::subtract(1)->add(10)->result();
echo $res2 . PHP_EOL; // 9

Solution 16 - Php

Also works as:

ExampleClass::withBanners()->withoutTranslations()->collection($values)

Using new static(self::class);

public static function withoutTranslations(): self
{
    self::$withoutTranslations = true;
    
    return new static(self::class);
}

public static function withBanners(): self
{
    return new static(self::class);
}

public static function collection(values): self
{
    return $values;
}

Solution 17 - Php

Use PHP 7! If your web provider cannot --> change provider! Don't lock in past.

final class TestClass {
    public static $currentValue;

    public static function toValue($value) {
        self::$currentValue = $value;
        return __CLASS__;
    }

    public static function add($value) {
        self::$currentValue = self::$currentValue + $value;
        return __CLASS__;
    }

    public static function subtract($value) {
        self::$currentValue = self::$currentValue - $value;
        return __CLASS__;
    }

    public static function result() {
        return self::$currentValue;
    }
}

And very simple use:

$value = TestClass::toValue(5)::add(3)::subtract(2)::add(8)::result();

var_dump($value);

Return (or throw error):

int(14)

completed contract.

Rule one: most evolved and maintainable is always better.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionWilcoView Question on Stackoverflow
Solution 1 - PhpMathew ByrneView Answer on Stackoverflow
Solution 2 - PhpAriful IslamView Answer on Stackoverflow
Solution 3 - PhpsectusView Answer on Stackoverflow
Solution 4 - PhpsectusView Answer on Stackoverflow
Solution 5 - PhpCamilo Díaz RepkaView Answer on Stackoverflow
Solution 6 - PhpGeorge GView Answer on Stackoverflow
Solution 7 - Phpkdion4891View Answer on Stackoverflow
Solution 8 - PhpPhobisView Answer on Stackoverflow
Solution 9 - PhpsanmaiView Answer on Stackoverflow
Solution 10 - PhpdirtsideView Answer on Stackoverflow
Solution 11 - PhpjsdeveloperView Answer on Stackoverflow
Solution 12 - PhpjW.View Answer on Stackoverflow
Solution 13 - PhpPratik SoniView Answer on Stackoverflow
Solution 14 - PhpboctulusView Answer on Stackoverflow
Solution 15 - PhpDanView Answer on Stackoverflow
Solution 16 - PhpSamir MammadhasanovView Answer on Stackoverflow
Solution 17 - PhpEvehneView Answer on Stackoverflow