Search code examples
phpclassnamespacesextend

Extend PHP Class to allow new methods found via __callStatic


Looking for a flexible way to allow other developers to extend render methods for a templating system, basically allowing them to generate their own render::whatever([ 'params' ]) methods.

Current set-up work well from a single developer point of view, I have a number of classes set-up based on context ( post, media, taxonomy etc ), with a __callStatic method collecting the calling function which checks if the method_exists within the class, and if so, extracts any passed arguments and renders the output.

quick example ( pseudo-code ):

-- view/page.php

render::title('<div>{{ title }}</div>');

-- app/render.php

class render {

    public static function __callStatic( $function, $args ) {
         
        // check if method exists 
        if ( method_exists( __CLASS__, $function ){

            self::{ $function }( $args );

        }

    }

    public static function title( $args ) {
         
        // do something with the passed args...

    }

}

I want to allow developers to extend the available methods from their own included class - so they could create for example render::date( $args ); and pass this to their logic to gather data, before rendering the results to the template.

The questions is, what approach would work best and be performant - errors are safety are not a big concern at this point, that can come later.

EDIT --

I am already making this work by doing the following ( pseudo-code again.. ):

-- app/render.php

class render {

    public static function __callStatic( $function, $args ) {
         
        // check if method exists 
        if ( 
            method_exists( __CLASS__, $function
        ){

            self::{ $function }( $args );

        }

        // check if method exists in extended class
        if ( 
            method_exists( __CLASS__.'_extend', $function 
        ){

            __CLASS__.'_extend'::{ $function }( $args );

        }

    }

    public static function title( $args ) {
         
        // do something with the passed args...

    }

}

-- child_app/render_extend.php

class render_extend {

    public static function date( $args = null ) {

        // do some dating..

    }

}

The issue here is that this is limited to one extension of the base render() class.


Solution

  • A common way (used by Twig and Smarty, for a couple of examples), is to require developers to manually register their extensions as callables. The render class keeps a record of them, and then as well as checking its own internal methods, also checks this list from _callStatic.

    Based on what you have already, this might look like this:

    class render
    {
        /** @var array */
        private static $extensions;
        
        public static function __callStatic($function, $args)
        {
            // check if method exists in class methods...
            if ( method_exists( __CLASS__, $function )) {
                self::{$function}(self::$args);
            }
            // and also in registry
            elseif (isset(self::$extensions[$function])) {
                (self::$extensions[$function])($args);
            }
        }
        
        public static function title($args)
        {
            // do something with the passed args...
        }
        
        public static function register(string $name, callable $callback)
        {
            self::$extensions[$name] = $callback;
        }
    }
    

    A developer would make use of this like so:

    render::register('date', function($args) {
        // Do something to do with dates
    });
    

    Full demo here: https://3v4l.org/oOiN6