call_user_func() & __construct()

crocodile2u

http://vbolshov.org.ru
call_user_func() & __construct()

Имеем:
PHP:
class test {
    function __construct($param)
    {
    }
}
$object = call_user_func_array(array('test', '__construct'), array('argument'));
В PHP5 это код (вполне ожидаемо) приводит к ошибке (Fatal error: Non-static method test::__construct() cannot be called statically).

В результате, если мы хотим динамически создавать объекты (и не ограничивать себя при передаче параметров конструктору), приходится прибегать к eval() :

PHP:
/**
 * Взято с http://ru2.php.net/call_user_func_array;
 * функция нуждается в шлифовке, но подход ясен.
 */
function createObjArray($type,$args=array()) {
     $paramstr = '';
     for ($i = 0; $i < count($args); $i++) {
           $paramstr .= '$args['.$i.'],';
     }
     $paramstr = rtrim($paramstr,',');

     return eval("return new $type($paramstr);");
}
Собсно, вопрос: верно ли я понимаю, что для решения такой задачи в сегодняшнем PHP можно воспользоваться только eval().

ЗЫ: вопрос скорее из области теории. На практике, имхо, лУчшим вариантом будет использовать factory method | abstract factory - созданные специально для тех классов, объекты которых мы хотим получить от данной фабрики.
 

crocodile2u

http://vbolshov.org.ru
new $test работает. осталось передать этому $test аргументы - произвольные.
То есть: допустим, есть классы:
PHP:
class test1 {
   function __construct($param1){}
}
class test2 {
   function __construct($param1, $param2){}
}
Так вот вопрос в том, чтобы написать функцию, которая создавала бы объекты - как первого, так второго класса - не используя eval(), получая на входе в качестве параметров имя класса и аргументы, которые нужно передать в конструктор.

Panchous
Спасибо за ссылку. Освежил в памяти. Сейчас пытаюсь применить все это к данному конкретному случаю.

-~{}~ 20.12.05 15:26:

PHP:
class test {
	function __construct($param)
	{
		echo $param."\n";
	}
}
$shapeClass = new ReflectionClass( 'test' );
$shapeMethod = $shapeClass->getMethod( '__construct' );

try {
	$test = $shapeMethod->invokeArgs(null, array("Hi"));
} catch (Exception $e) {
	echo $e->getMessage()."\n";
}
На выходе получаю:
Trying to invoke non static method test::__construct without an object
:(
 

Panchous

Павел
Читай внимательнее - там подробно все расписано...

ReflectionClass::getConstructor()
ReflectionMethod::getParameters()
ReflectionClass::newInstance
 

crocodile2u

http://vbolshov.org.ru
Panchous
Спасибо еще раз. К сожалению, меня не хватило на столь подробное прочтение (в основном остановился на примерах). В следующий раз надо быть внимательнее.

Итак, наконец:

PHP:
class test {
	function __construct($param1, $param2)
	{
		echo $param1.", ".$param2."!\n";
	}
}
$shapeClass = new ReflectionClass( 'test' );
try {
	$test = call_user_func_array(array(&$shapeClass, 'newInstance'), array("Hello", "world"));
} catch (Exception $e) {
	echo $e->getMessage()."\n";
}
Это рабочий вариант и его можно использовать для решения задачи, поставленной в начальном посте.
 

Panchous

Павел
ага - ты бы еще евал туда до кучи запихнул!
вот лень губит человека - ему даже все нужные функции указали...
 

crocodile2u

http://vbolshov.org.ru
Panchous
Не понял. Что тебе конкретно не понравилось? call_user_func_array() ?

Я собирался использовать это для следующей функции:
PHP:
function & createObjArray($type, $args = array()) {
	$reflection = new ReflectionClass($type);
	$output     = call_user_func_array(array(&$reflection, 'newInstance'), $args);
	return $output;
}
Это, собсно, есть вариант функции, которую я привел в своем первом посте - только уже без eval().
 

crocodile2u

http://vbolshov.org.ru
Panchous
Все же не понимаю. Каким образом продублировать действие той функции, которую я привел в предыдущем своем посте, с помощью упомянутых тобой методов и не используя call_user_func_array() ??

Еще раз: функция должна получать на вход имя класса и аргументы для конструктора в виде массива. Может быть, приведешь код?
 

Panchous

Павел
как я понял, необходимо создать объект заданного класса
и вернуть его.
Все это можно красиво решить через рефлекшн (для пхп5 естественно)
 

crocodile2u

http://vbolshov.org.ru
необходимо создать объект заданного класса, передав в конструктор произвольное число аргументов
 

Panchous

Павел
именно. только не произвольное - а необходимое.
см. мой пред. пост.
 

crocodile2u

http://vbolshov.org.ru
Я понимаю, что можно с пом. Рефлекшн АПИ узнать, какие аргументы должен принимать конструктор и какие из них являются необходимыми. Ну а если конструктор такой:
PHP:
function __construct()
{
   var_dump(func_get_args());
}
Рефлекшн АПИ в этом случае не покажет параметров в конструкторе, тем не менее, приложение (и программист) ожидает, что в конструктор _можно_ передавать аргументы и они будут адекватно обработаны.
 

Panchous

Павел
не вижу никакой проблемы:

1. получить ReflectionClass для искомого класса
2. получить его конструктор (ReflectionMethod) и узнать кол-во обязательных параметров
3. создать объект заданного класа с передачей параметров в конструктор (для корректности можно проверять кол-во передаваемых параметров)
 

crocodile2u

http://vbolshov.org.ru
Получить кол-во обязательных параметров нужно лишь для того, чтобы избежать ошибок PHP при создании экземпляра. Допустим, в пилотном варианте мы полагаемся на пользователя в этой части (считаем, что пользователь передает обязательные параметры и если используется Type Hinting - передает экземпляры соответствующих классов).

Объясни лучше, как в таком случае " создать объект заданного класа с передачей параметров в конструктор". Насколько я понимаю, это одна строка кода. Как ты это сделаешь, не используя call_user_func_array() ?

Проще говоря, не мог бы ты привести свой вариант функции createObjArray() ? Без проверки параметров.
 

crocodile2u

http://vbolshov.org.ru
Panchous
Не томи. Я, чесслово, не могу додуматься.

Имеется конструктор function __construct($param1, $param2);
имеется массив $arguments = array('argument1', 'argument2');
имеется экземпляр ReflectionClass для нужного класса $reflection = new ReflectionClass('MyClass');

Как построить экземпляр MyClass ?

Понятно, что можно, допустим, так:
PHP:
$instance = $reflection->newInstance($arguments[0], $arguments[1]);
Но по условиям задачи нужно передавать массив аргументов. Я додумался только до call_user_func_array(); Panchous, если ты знаешь более красивое решение, ну напиши ты в конце концов эту строчку кода.. а?
 
Сверху