В настоящее время я рассматриваю возможность использования классов Reflection (в основном ReflectionClass и ReflectionMethod) в моей собственной веб-платформе MVC, потому что мне нужно автоматически создавать экземпляры классов контроллеров и вызывать их методы без какой-либо требуемой конфигурации (подход «соглашение по конфигурации»).
Меня беспокоит производительность, хотя я думаю, что запросы к базе данных, вероятно, будут более узкими местами, чем реальный код PHP.
Итак, мне интересно, есть ли у кого-нибудь хороший или плохой опыт работы с PHP 5 Reflection с точки зрения производительности.
Кроме того, мне было бы любопытно узнать, действительно ли какой-либо из популярных фреймворков PHP (CI, Cake, Symfony и т. д.) Использует Reflection.
Zend Framework 1.x использует отражение при загрузке. Поскольку ZF позволяет автоматически вызывать функции _init, для этого нужен какой-то механизм. И он использует Reflection.
Laravel довольно часто использует Reflection.






Не беспокойся. Установите Xdebug и убедитесь, где находится узкое место.
За использование отражения приходится платить, но будет ли это иметь значение, зависит от того, что вы делаете. Если вы реализуете диспетчер контроллеров / запросов с помощью Reflection, тогда это будет только одно использование на запрос. Совершенно ничтожно.
Если вы реализуете свой уровень ORM с помощью отражения, используете его для каждого объекта или даже для каждого доступа к свойству и создаете сотни или тысячи объектов, то это может оказаться дорогостоящим.
Спасибо, я не знал о Xdebug. Похоже, отличный инструмент. Мой уровень ORM вообще не должен использовать отражение, я буду использовать его только один раз для диспетчера запросов. Я думаю, вы правы, говоря, что это ничтожно мало!
Накладные расходы небольшие, поэтому нет большого снижения производительности, другие вещи, такие как db, обработка шаблонов и т. д., Являются проблемами производительности, протестируйте свою структуру с помощью простого действия, чтобы увидеть, насколько она быстро.
Например, приведенный ниже код (frontcontroller), который использует отражение, выполняет свою работу за несколько миллисекунд.
<?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);
}
}
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
«Обычно эта функция используется только разработчиками серверных классов для фреймворка».
Вызов статической функции 1 миллион раз на моей машине будет стоить ~ 0,31 секунды. При использовании ReflectionMethod это стоит ~ 1,82 секунды. Это означает, что использование Reflection API на ~ 500% дороже.
Кстати, это код, который я использовал:
<?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;
Очевидно, что реальный эффект зависит от количества звонков, которые вы ожидаете сделать.
Это может быть на 500% дороже, но в среднем составляет всего 1,82 микросекунды на звонок.
Этот тест неверен, потому что экземпляр метода отражения должен быть создан только один раз. Не в цикле.
CodeIgniter обязательно использует отражения. И я уверен, что другие тоже. Посмотрите на класс контроллера в папке system / controller в установке ci.
В моем случае отражение всего на 230% медленнее, чем прямой вызов метода класса, который так же быстр, как функция call_user_func.
В моем тесте (я его опубликовал) ReflectionMethod почти так же быстр, как call_user_func, хотя он всего на 200–220% медленнее, чем прямой вызов метода.
Иногда использование чего-то вроде call_user_func_array () может дать вам то, что вам нужно. Не знаю, чем отличается производительность.
Я протестировал эти 3 варианта (другой тест не разделял циклы процессора и был старше 4 лет):
class foo {
public static function bar() {
return __METHOD__;
}
}
function directCall() {
return foo::bar($_SERVER['REQUEST_TIME']);
}
function variableCall() {
return call_user_func(array('foo', 'bar'), $_SERVER['REQUEST_TIME']);
}
function reflectedCall() {
return (new ReflectionMethod('foo', 'bar'))->invoke(null, $_SERVER['REQUEST_TIME']);
}
Абсолютное время, затраченное на 1000000 итераций:
print_r(Benchmark(array('directCall', 'variableCall', 'reflectedCall'), 1000000));
Array
(
[directCall] => 4.13348770
[variableCall] => 6.82747173
[reflectedCall] => 8.67534351
)
И относительное время, также с 1000000 итераций (отдельный прогон):
ph()->Dump(Benchmark(array('directCall', 'variableCall', 'reflectedCall'), 1000000, true));
Array
(
[directCall] => 1.00000000
[variableCall] => 1.67164707
[reflectedCall] => 2.13174915
)
Похоже, что в 5.4.7 производительность отражения сильно увеличилась (с ~ 500% до ~ 213%).
Вот функция Benchmark(), которую я использовал, если кто-то хочет повторно запустить этот тест:
function Benchmark($callbacks, $iterations = 100, $relative = false)
{
set_time_limit(0);
if (count($callbacks = array_filter((array) $callbacks, 'is_callable')) > 0)
{
$result = array_fill_keys($callbacks, 0);
$arguments = array_slice(func_get_args(), 3);
for ($i = 0; $i < $iterations; ++$i)
{
foreach ($result as $key => $value)
{
$value = microtime(true);
call_user_func_array($key, $arguments);
$result[$key] += microtime(true) - $value;
}
}
asort($result, SORT_NUMERIC);
foreach (array_reverse($result) as $key => $value)
{
if ($relative === true)
{
$value /= reset($result);
}
$result[$key] = number_format($value, 8, '.', '');
}
return $result;
}
return false;
}
+1 за то, что он классный. Вы забыли указать версию PHP. Пожалуйста, отметьте это.
Протестировано на PHP 7.1.7: array(3) { ["directCall"]=> string(10) "1.00000000" ["variableCall"]=> string(10) "1.06057096" ["reflectedCall"]=> string(10) "2.59103844" }
на основе кода, предоставленного @Alix Axel
Поэтому для полноты картины я решил обернуть каждую опцию в класс и включить кеширование объектов, где это возможно. вот результаты и код Результаты на PHP 5.6 на i7-4710HQ
array (
'Direct' => '5.18932366',
'Variable' => '5.62969398',
'Reflective' => '6.59285069',
'User' => '7.40568614',
)
function Benchmark($callbacks, $iterations = 100, $relative = false)
{
set_time_limit(0);
if (count($callbacks = array_filter((array) $callbacks, 'is_callable')) > 0)
{
$result = array_fill_keys(array_keys($callbacks), 0);
$arguments = array_slice(func_get_args(), 3);
for ($i = 0; $i < $iterations; ++$i)
{
foreach ($result as $key => $value)
{
$value = microtime(true); call_user_func_array($callbacks[$key], $arguments); $result[$key] += microtime(true) - $value;
}
}
asort($result, SORT_NUMERIC);
foreach (array_reverse($result) as $key => $value)
{
if ($relative === true)
{
$value /= reset($result);
}
$result[$key] = number_format($value, 8, '.', '');
}
return $result;
}
return false;
}
class foo {
public static function bar() {
return __METHOD__;
}
}
class TesterDirect {
public function test() {
return foo::bar($_SERVER['REQUEST_TIME']);
}
}
class TesterVariable {
private $class = 'foo';
public function test() {
$class = $this->class;
return $class::bar($_SERVER['REQUEST_TIME']);
}
}
class TesterUser {
private $method = array('foo', 'bar');
public function test() {
return call_user_func($this->method, $_SERVER['REQUEST_TIME']);
}
}
class TesterReflective {
private $class = 'foo';
private $reflectionMethod;
public function __construct() {
$this->reflectionMethod = new ReflectionMethod($this->class, 'bar');
}
public function test() {
return $this->reflectionMethod->invoke(null, $_SERVER['REQUEST_TIME']);
}
}
$testerDirect = new TesterDirect();
$testerVariable = new TesterVariable();
$testerUser = new TesterUser();
$testerReflective = new TesterReflective();
fputs(STDOUT, var_export(Benchmark(array(
'Direct' => array($testerDirect, 'test'),
'Variable' => array($testerVariable, 'test'),
'User' => array($testerUser, 'test'),
'Reflective' => array($testerReflective, 'test')
), 10000000), true));
Я хотел что-то более новое, поэтому взгляните на это репо. Из резюме:
- PHP 7 is almost twice as fast as PHP 5 in case of reflections - This does not directly indicate that reflections are faster on PHP7, the PHP7 core have just received a great optimization and all code will benefit from this.
- Basic reflections are quite fast - Reading methods and doc comments for 1000 classes cost just a few milliseconds. Parsing/Autoloading the classfiles does take a lot more time than the actual reflection mechanics. On our testsystem it takes about 300ms to load 1000 class files into memory (require/include/autoload) - And than just 1-5ms to use reflection parsing (doc comments, getMethods, etc...) on the same amount of classes.
- Conclusion: Reflections are fast and in normal use cases you can ignore that performance impact. However, it is always recommended to only parse what is necessary. And, caching reflections doesn't give you any noticeable benefit on performance.
Также проверьте еще один ориентир.
Those results were obtained on a development OS X machine using PHP 5.5.5. [...]
Read a single property on one object: The closure is slightly faster.
Read a single property on many objects: Reflection is way faster.
Reading all the properties of an object: The closure is faster.
Writing a single property on one object: Reflection is slightly faster.
Writing a single property on many objects: Reflection is way faster.
Я использую отражение в Европе (europaphp.org, github.com/treshugart/EuropaPHP), и оно быстрее, чем все они.