Последствия создания объектов с динамическими переменными в PHP

Что такое производительность, безопасность или "другие" последствия использования следующей формы для объявления нового экземпляра класса в PHP

<?php
  $class_name = 'SomeClassName';
  $object = new $class_name;
?>

Это надуманный пример, но я видел эту форму, используемую на фабриках (ООП), чтобы избежать большого оператора if/switch.

Проблемы, которые возникают сразу, - это

  • Вы теряете возможность передавать аргументы в конструктор (LIES. Спасибо Джереми)
  • Пахнет как eval(), со всеми проблемами безопасности, которые он вызывает в таблице (но не обязательно для производительности)?

Какие еще существуют последствия, или какие термины поисковой системы, отличные от "Rank PHP Hackery", могут кто-то использовать для исследования этого?

Ответ 1

Одна из проблем, возникающих при разрешении во время выполнения, заключается в том, что вы делаете это действительно трудным для кэшей операций операций (например, APC). Тем не менее, пока что-то, как вы описываете в своем вопросе, является допустимым способом, если вам нужно определенное количество косвенных действий, когда создаются вещи.

Пока вы не делаете что-то вроде

$classname = 'SomeClassName';
for ($x = 0; $x < 100000; $x++){
  $object = new $classname;
}

вы, вероятно, хорошо: -)

(моя точка зрения: динамический поиск класса здесь и тогда не повредит. Если вы это сделаете часто, это будет).

Кроме того, убедитесь, что $classname никогда не может быть установлено извне - вы должны иметь некоторый контроль над тем, какой именно класс вы создадите.

Ответ 2

Похоже, вы все равно можете передать аргументы конструктору, вот мой тестовый код:

<?php

class Test {
    function __construct($x) {
        echo $x;
    }
}

$class = 'Test';
$object = new $class('test'); // echoes "test"

?>

Это то, что вы имели в виду, правильно?

Таким образом, единственная проблема, о которой вы говорили, и о которой я могу думать, - это ее безопасность, но ее не должно быть слишком сложно сделать, и она явно намного более безопасна, чем использование eval().

Ответ 3

Я бы добавил, что вы также можете инициировать его с помощью динамического числа параметров, используя:

<?php

$class = "Test";
$args = array('a', 'b');
$ref = new ReflectionClass($class);
$instance = $ref->newInstanceArgs($args);

?>

Но, конечно, вы добавляете дополнительные накладные расходы, делая это.

О проблеме безопасности Я не думаю, что это имеет большое значение, по крайней мере, это ничто по сравнению с eval(). В худшем случае неправильный класс получает instanciated, конечно, это потенциальное нарушение безопасности, но гораздо труднее использовать, и его легко фильтровать с использованием массива разрешенных классов, если вам действительно нужен пользовательский ввод для определения имени класса.

Ответ 4

Действительно, может возникнуть поражение производительности для того, чтобы разрешить имя переменной перед поиском определения класса. Но, не объявляя классы динамически, у вас нет реального способа программирования "dyanmic" или "meta". Вы не сможете писать программы генерации кода или что-то вроде языковой конструкции, специфичной для домена.

Мы используем это соглашение повсеместно в некоторых основных классах нашей внутренней структуры, чтобы сделать URL-адрес для сопоставлений контроллеров. Я также видел это во многих коммерческих приложениях с открытым исходным кодом (я попробую и выкопаю пример и опубликую его). Во всяком случае, мой ответ заключается в том, что, похоже, это стоит того, что, вероятно, небольшое снижение производительности, если он делает более гибкий динамический код.

Другим компромиссом, который я должен упомянуть, является то, что производительность в стороне, это делает код немного менее очевидным и читаемым, если вы не очень осторожны с именами переменных. Большая часть кода записывается один раз и многократно перечитывается и изменяется, поэтому читаемость важна.

Ответ 5

Алан, нет ничего плохого в инициализации динамического класса. Этот метод присутствует также на языке Java, где можно преобразовать строку в класс с помощью метода Class.forClass('classname'). Также весьма удобно отложить сложность алгоритма до нескольких классов вместо списка if-условий. Динамические имена классов особенно хорошо подходят в ситуациях, когда вы хотите, чтобы ваш код оставался открытым для расширения без необходимости внесения изменений.

Я сам часто использую разные классы в сочетании с таблицами базы данных. В одном столбце я сохраняю имя класса, которое будет использоваться для обработки записи. Это дает мне большую возможность добавлять новые типы записей и обрабатывать их уникальным образом без изменения одного байта в существующем коде.

Вы не должны беспокоиться о производительности. У него почти нет накладных расходов, и сами объекты очень быстры в PHP. Если вам нужно создать тысячи одинаковых объектов, используйте Flyweight шаблон дизайна, чтобы уменьшить объем памяти. В частности, вы не должны жертвовать своим временем как разработчиком, чтобы сохранить миллисекунды на сервере. Также оптимизаторы op-кода работают с этой техникой без проблем. Сценарии, скомпилированные с помощью Zend Optimizer, не ошибались.

Ответ 6

Итак, я недавно столкнулся с этим и хотел рассказать о "других" последствиях использования динамического экземпляра.

Во-первых, func_get_args() бросает немного ключа в вещи. Например, я хочу создать метод, который действует как конструктор для определенного класса (например, метод factory). Мне нужно было бы передать параметры, переданные моему методу factory, в конструктор класса, который я создаю.

Если вы выполните:

public function myFactoryMethod() 
{
  $class = 'SomeClass'; // e.g. you'd get this from a switch statement
  $obj = new $class( func_get_args() );
  return $obj;
}

а затем вызовите:

$factory → myFactoryMethod ( 'Foo', 'бар');

Фактически вы передаете массив как параметр first/only, который совпадает с new SomeClass( array( 'foo', 'bar' ) ) Это явно не то, что мы хотим.

Решение (как отмечено @Seldaek) требует от нас преобразования массива в params конструктора:

public function myFactoryMethod() 
{
  $class = 'SomeClass'; // e.g. you'd get this from a switch statement
  $ref = new ReflectionClass( $class );
  $obj = $ref->newInstanceArgs( func_get_args() );
  return $obj;
}

Примечание. Это невозможно выполнить с помощью call_user_func_array, потому что вы не можете использовать этот подход для создания новых объектов.

НТН!

Ответ 7

Я использую динамическое создание экземпляра в своей пользовательской структуре. Моему контроллеру приложений необходимо создать субконтроллер на основе запроса, и было бы просто смешно использовать гигантский, постоянно меняющийся оператор switch для управления загрузкой этих контроллеров. В результате я могу добавить контроллер после контроллера в свое приложение без необходимости изменять контроллер приложения для их вызова. Пока мои URI придерживаются конвенций моей фреймворк, контроллер приложения может использовать их без необходимости знать что-либо до выполнения.

Я использую эту фреймворк в приложении для покупок в магазине прямо сейчас, и производительность тоже весьма благоприятная. При этом я использую только выбор динамического класса в одном или двух местах в целом приложении. Интересно, в каких обстоятельствах вам нужно будет часто его использовать, и будут ли эти ситуации теми, которые страдают от желания программиста переоценить приложение (ранее я был виноват в этом).

Ответ 8

Одна из проблем заключается в том, что вы не можете обращаться к статическим членам вроде этого, например

<?php
$className = 'ClassName';

$className::someStaticMethod(); //doesn't work
?>

Ответ 9

@coldFlame: IIRC вы можете использовать call_user_func(array($className, 'someStaticMethod') и call_user_func_array() для прохождения параметров

Ответ 10

class Test {
    function testExt() {
    print 'hello from testExt :P';
    }
    function test2Ext()
    {
    print 'hi from test2Ext :)';
    }
}


$class = 'Test';
$method_1 = "testExt";
$method_2 = "test2Ext";
$object = new $class(); // echoes "test"
$object->{$method_2}(); // will print 'hi from test2Ext :)'
$object->{$method_1}(); // will print 'hello from testExt :P';

этот трюк работает как на php4, так и на php5: D нравится..