Search code examples
phpstatic-methodsinstantiation

Dynamically generate PHP static methods to instantiate new object


I am trying to build a database-class that allows method chaining to query it. I want to instantiate it like Laravel's Eloquent by making the first call static.

See this example:

class Database
{
    public function construct($data) {
        $this->data = $data;
    }
    public function filter($criteria) {
        // ...
        return $this;
    }
    public static function filter($data, $criteria) {
        $obj = new Database($data);
        $obj->filter($criteria);
        return $obj;
    }
    public function add($value) {
        // ...
        return $this;
    }
    public static function add($data, $value,) {
        $obj = new Database($data);
        $obj->add($value);
        return $obj;
    }
}

This would allow me for example:

Database::add($myData, $newValue)->add($anotherValue)->filter('isString');
Database::filter($myData, 'isNumber')->add($thirdValue);

This is not supposed to be the real thing, but I am curious if there was a way to reduce the duplicated code throughout my static methods or to remove them entirely. I thought of the magic method __callStatic() but I am not sure if it is the best way to achieve it.

I would appreciate it if someone who knows could explain me, how big frameworks deal with this kind of task.


Solution

  • Yea it is indeed done through magic method __callStatic().

    Here you have Laravel Eloquent code snippets, these are taken from Model class, there is more things going on, but these are relevant to your case:

    public static function __callStatic($method, $parameters)
    {
        return (new static)->$method(...$parameters); // delegating to __call method
    }
    
    public function __call($method, $parameters)
    {
        ...
        // self explanatory
        return $this->forwardCallTo($this->newQuery(), $method, $parameters);
    }
    
    // newQuery method lead to this method:
    public function newModelQuery()
    {
        return $this->newEloquentBuilder(
            $this->newBaseQueryBuilder() // deals with getting database connection
        )->setModel($this);
    }
    
    // newEloquentBuilder return Builder class which have the "where" etc. methods defined.
    

    In your case simple example:

    class Database
    {
        protected static $connection;
        protected $query; // could be array or better object
    
        public function __construct() {
            if(!static::$connection){
                static::$connection = new mysqli(...); // for example
            }
        }
        
        public static function __callStatic($method, $parameters)
        {
            return (new static)->$method(...$parameters);
        }
        
        // in case $query is object have these two methods defined there
        // also delegate with __call method to the query object
        
        public function filter($criteria) {
            ... // filling $query variable
            return $this;
        }
      
        public function add($value) {
            ... // filling $query variable
            return $this;
        }
        
        public function fetch_assoc() {
            $stmt = static::$connection->prepare(...);
            ... // fill the prepared statements with $query data
            return $stmt->get_result()->fetch_assoc();
        }
    }