views:

2216

answers:

5

I'm currently considering the use of Reflection classes (ReflectionClass and ReflectionMethod mainly) in my own MVC web framework, because I need to automatically instanciate controller classes and invoke their methods without any required configuration ("convention over configuration" approach).

I'm concerned about performance, even though I think that database requests are likely to be bigger bottlenecks than the actual PHP code.

So, I'm wondering if anyone has any good or bad experience with PHP 5 Reflection from a performance point of view.

Besides, I'd be curious to know if any one of the popular PHP frameworks (CI, Cake, Symfony, etc.) actually use Reflection.

+9  A: 

Don't be concerned. Install Xdebug and be sure where the bottleneck is.

There is cost to using reflection, but whether that matters depends on what you're doing. If you implement controller/request dispatcher using Reflection, then it's just one use per request. Absolutely negligible.

If you implement your ORM layer using reflection, use it for every object or even every access to a property, and create hundreds or thousands objects, then it might be costly.

porneL
Thanks, I didn't know about Xdebug. It looks like a great tool.My ORM layer shouldn't use reflection at all, I'll be using it only once for my request dispatcher. I think you're right about it being negligible!
Franck
+1  A: 

The overhead is small so there is no big performance penalty other stuff like db, template processing etc are performance problems, test your framework with a simple action to see how fast it is.

For example the code bellow (frontcontroller) which uses reflection does it jobs in a few miliseconds

<?php

require_once('sanitize.inc');

/**

 * MVC Controller

 *

 * This Class implements  MVC Controller part

 *

 * @package MVC

 * @subpackage Controller

 *

 */

class Controller

{

    /**

     * Standard Controller constructor

     */

    static private $moduleName;

    static private $actionName;

    static private $params;



    /**

     * Don't allow construction of the controller (this is a singleton)

     *

     */

    private function __construct()

    {

    }



    /**

     * Don't allow cloning of the controller (this is a singleton)

     *

     */

    private function __clone()

    {

    }



    /**

     * Returns current module name

     *

     * @return string

     */

    function getModuleName()

    {

     return self :: $moduleName;

    }



    /**

     * Returns current module name

     *

     * @return string

     */

    function getActionName()

    {

     return self :: $actionName;

    }

    /**

    * Returns the subdomain of the request

    *

    * @return string

    */ 

    function getSubdomain()

    {

     return substr($_SERVER['HTTP_HOST'],0,strpos($_SERVER['HTTP_HOST'],'.'));

    }



    function getParameters( $moduleName = false, $actionName = false )

    {

     if ( $moduleName === false or ( $moduleName === self :: $moduleName and $actionName === self :: $actionName ) )

     {

      return self :: $params;

     } else

     {

      if ( $actionName === false )

      {

       return false;

      } else

      {

       @include_once ( FRAMEWORK_PATH . '/modules/' . $moduleName. '.php' ) ;



       $method = new ReflectionMethod( 'mod_' . $moduleName , $actionName );



       foreach ($method ->getParameters() as $parameter)

       {

        $parameters[$parameter -> getName() ] = null;

       }



       return $parameters;

      }

     }

    }





    /**

     * Redirect or direct to a action or default module action and parameters

     * it has the ability to http redirect to the specified action

     * internally used to direct to action

     *

     * @param string $moduleName

     * @param string $actionName

     * @param array $parameters

     * @param bool $http_redirect

     * @return bool

     */

    function redirect( $moduleName , $actionName , $parameters = null, $http_redirect = false )

    {

     self :: $moduleName = $moduleName;

     self :: $actionName = $actionName;



     // We assume all will be ok

     $ok = true ;



      @include_once ( PATH . '/modules/' . $moduleName. '.php' ) ;



      // We check if the module's class really exists

      if ( ! class_exists ( 'mod_' . $moduleName , false ) ) // if the module does not exist route to module main

      {

       @include_once ( PATH . '/modules/main.php' ) ;



       $modClassName = 'mod_main';

       $module = new $modClassName() ;



       if ( method_exists( $module , $moduleName ))

       {



        self :: $moduleName = 'main';

        self :: $actionName = $moduleName;



        //$_PARAMS = explode( '/' , $_SERVER['REQUEST_URI'] );

        //unset($parameters[0]);

        //$parameters = array_slice($_PARAMS, 1, -1);

        $parameters = array_merge(array($actionName),$parameters);//add first parameter

       } else

       {

        $parameters = array($moduleName,$actionName) + $parameters;

        $actionName = 'index';

        $moduleName = 'main';



        self :: $moduleName = $moduleName;

        self :: $actionName = $actionName;

       }

      } else //if the action does not exist route to action index

      {

       @include_once ( PATH . '/modules/'.$moduleName.'.php' ) ;



       $modClassName =  'mod_' . $moduleName ;

       $module = new $modClassName() ;



       if ( ! method_exists( $module , $actionName ) )

       {

        $parameters = array_merge(array($actionName),$parameters);//add first parameter

        $actionName = 'index';



       }

       self :: $moduleName = $moduleName;

       self :: $actionName = $actionName;

      }



      if ( empty($module) )

      {

       $modClassName = 'mod_' . self :: $moduleName;

       $module = new $modClassName();

      }





      $method = new ReflectionMethod( 'mod_' . self :: $moduleName , self :: $actionName );



      //sanitize and set method variables

      if ( is_array( $parameters ) )

      {

       foreach ( $method ->getParameters() as $parameter )

       {



        $param = current($parameters);

        next($parameters);



        if ( $parameter -> isDefaultValueAvailable() )

        {

         if ( $param !== false )

         {

          self :: $params[ $parameter -> getName() ] =  sanitizeOne( urldecode(trim($param)) , $parameter -> getDefaultValue() );

         } else

         {

          self :: $params[ $parameter -> getName() ] = null;

         }

        }

        else

        {

         if ( $param !== false )//check if variable is set, avoid notice

         {

          self :: $params[ $parameter -> getName() ] = sanitizeOne( urldecode( trim( $param ) ) , 'str' );

         } else

         {

          self :: $params[ $parameter -> getName() ] = null;

         }

        }

       }

      } else 

      {

       foreach ( $method ->getParameters() as $parameter )

       {

        self :: $params[ $parameter -> getName() ] = null;

       }

      }



      if ($http_redirect === false)//no redirecting just call the action

      {

        if ( is_array(self :: $params) )

        {

         $method -> invokeArgs( $module, self :: $params );

        } else 

        {

         $method -> invoke( $module ); 

        }

      }

      else

      {

       //generate the link to action

       if (is_array($parameters)) // pass parameters

       {

        $link =  '/' . $moduleName . '/' . $actionName . '/' . implode('/',self :: $params);

       } else

       {

        $link =  '/' . $moduleName . '/' . $actionName;

       }



       //redirect browser

       header( 'Location:'.$link );

       //if the browser does not support redirecting then provide a link to the action

       die( 'Your browser does not support redirect please click here <a href="' . $link . '">' . $link . '</a>' );

      }



     return $ok;

    }

    /**

    * Redirects to action contained within current module

    */ 

    function redirectAction($actionName,$parameters)

    {

     self :: $actionName = $actionName;

     call_user_func_array(array(&$this, $actionName),$parameters);

    }



    public function module($moduleName)

    {

     self :: redirect( $moduleName , $actionName , $parameters, $http_redirect = false );

    }

    /**

     * Processes the client's REQUEST_URI and handles module loading/unloading and action calling

     *

     * @return bool

     */

    public function dispatch()

    {

     if ( $_SERVER['REQUEST_URI'][strlen($_SERVER['REQUEST_URI']) - 1] !== '/' )

     {

      $_SERVER['REQUEST_URI'] .= '/';//add end slash for safety (if missing)

     }

     //$_SERVER['REQUEST_URI'] = @str_replace( BASE ,'', $_SERVER['REQUEST_URI']);

     // We divide the request into 'module' and 'action' and save paramaters into $_PARAMS



     if ($_SERVER['REQUEST_URI'] != '/')

     {

      $_PARAMS = explode( '/' , $_SERVER['REQUEST_URI'] );



      $moduleName = $_PARAMS[1];//get module name

      $actionName = $_PARAMS[2];//get action

      unset($_PARAMS[count($_PARAMS) - 1]);//delete last

      unset($_PARAMS[0]);

      unset($_PARAMS[1]);

      unset($_PARAMS[2]);

     } else 

     {

      $_PARAMS = null;

     }



     if ( empty($actionName) )

     {

      $actionName = 'index' ;//use default index action

     }



     if ( empty($moduleName) )

     {

      $moduleName = 'main' ;//use default main module

     }



     /*if (isset($_PARAMS))

     {

      $_PARAMS = array_slice($_PARAMS, 3, -1);//delete action and module from array and pass only parameters

     }*/



     return self :: redirect($moduleName, $actionName, $_PARAMS);

    }

}
codeassembly
+1  A: 

Besides, I'd be curious to know if any one of the popular PHP frameworks (CI, Cake, Symfony, etc.) actually use Reflection.

http://framework.zend.com/manual/en/zend.server.reflection.html

"Typically, this functionality will only be used by developers of server classes for the framework."

vartec
+3  A: 

Calling a static function 1 million times will cost ~ 0.31 seconds on my machine. When using a ReflectionMethod, it costs ~ 1.82 seconds. That means it is ~ 500% more expensive to use the Reflection API.

This is the code I used by the way:

<?PHP

class test
{
    static function f(){
            return;
    }
}

$s = microtime(true);
for ($i=0; $i<1000000; $i++)
{
    test::f('x');
}
echo ($a=microtime(true) - $s)."\n";

$s = microtime(true);
for ($i=0; $i<1000000; $i++)
{
    $rm = new ReflectionMethod('test', 'f');
    $rm->invokeArgs(null, array('f'));
}

echo ($b=microtime(true) - $s)."\n";

echo 100/$a*$b;

Obviously, the actual impact depends on the number of calls you expect to do

It may be 500% more expensive, but it still averages at just 1.82 microseconds per call.
Alex Barrett
A: 

CodeIgniter defenitly uses Reflections. And i bet the others also do. Look into Controller class in the system/controller folder in ci installation.

Dre