Как использовать аргументы PHP-функции или метода в любом порядке?
допустим, я определяю класс с помощью такого метода:
class Test {
public function doStuff($a, $b, $c) {
// -- Do stuff --
}
}
можно ли использовать этот метод, но с аргументами, в другом порядке, вот так:
$test = new Test();
$test->doStuff($b, $c, $a);
у них были бы те же имена, но только в другом порядке.
Я вижу, что Symfony2 может сделать это со своим диспетчером, вы можете использовать аргументы в любом порядке. Ссылка:контроллер Symfony2 может это сделать
вопрос в том, как сделать эту работу? Как можно ссылаться на Symfony2 соответствующий контроллер действий, который может принимать аргументы в любом порядке?
изменить: Я не могу использовать массивы, и я знаю, что php не использует именованные аргументы. Но как-то Symfony2 удается это сделать.
5 ответов
Я думаю, вы неправильно поняли, что говорит Symfony. Вы не можете передать аргументы действию контроллера в любом порядке, он должен быть в определенном порядке. Однако они делают это динамично, выясняя, в каком порядке ваши параметры маршрутизации находятся внутри определения функции.
например, мы определяем маршрут:
pattern: /hello/{first_name}/{last_name}
defaults: { _controller: AcmeHelloBundle:Hello:index, color: green }
в маршруте параметры называются first_name
, last_name
и color
.
то, что они говорят, это что не имеет значения, какой порядок вы используете для параметров в действии.
каждый из следующих эквивалентны:
public function indexAction($first_name, $last_name, $color) {...}
public function indexAction($color, $last_name, $first_name) {...}
public function indexAction($last_name, $color, $first_name) {...}
поскольку ваши аргументы названы так же, как параметры в маршруте, Symfony выясняет, что правильный порядок аргументов на основе вашего определения.
если вы должны были вызвать следующее действие вручную:
public function indexAction($first_name, $last_name, $color)
тогда аргументы еще должны быть передано как $first_name, $last_name, $color
и не в каком-либо другом порядке. Использование другого порядка просто свяжет неправильные значения с аргументами. Symfony просто не волнует, в каком порядке вы определяете свою функцию, поскольку он определяет порядок, потому что ваши параметры маршрутизации должны быть названы так же, как и ваши аргументы метода.
надеюсь, это прояснит путаницу.
вы можете использовать другой параметр для указания порядка, а затем использовать переменные:
function test($order, $_a, $_b, $_c){
$order = explode(';', $order);
${$order[0]} = $_a;
${$order[1]} = $_b;
${$order[2]} = $_c;
echo "a = $a; b = $b; c = $c<br>";
}
test('a;b;c', 'a', 'b', 'c');
test('b;a;c', 'b', 'a', 'c');
но серьезно, почему вы не можете использовать массив? Это лучший способ.
обновление: я написал это. Я, должно быть, очень скучно.
теперь я чувствую себя грязным.
class FuncCaller{
var $func;
var $order_wanted;
var $num_args_wanted;
function FuncCaller( $func, $order ){
$this->func = $func;
if( is_set($order) ){
// First version: receives string declaring parameters
// We flip the order_wanted array so it maps name => index
$this->order_wanted = array_flip( explode(';', $order) );
$this->num_args_wanted = count($this->order_wanted);
} else {
// Second version: we can do better, using reflection
$func_reflection = new ReflectionFunction($this->func);
$params = $func_reflection->getParameters();
$this->num_args_wanted = func_reflection->getNumberOfParameters();
$this->order_wanted = [];
foreach( $params as $idx => $param_reflection ){
$this->order_wanted[ $param_reflection->getName() ] = $idx;
}
}
}
function call(){
if( func_num_args() <= 1 ){
// Call without arguments
return $this->func();
}
else if( func_num_args() == $this->num_args_wanted ){
// order argument not present. Assume order is same as func signature
$args = func_get_args();
return call_user_func_array( $this->func, $args );
}
else {
// @TODO: verify correct arguments were given
$args_given = func_get_args();
$order_given = explode( ';', array_shift($args_given) );
$order_given = array_flip( $order_given ); // Map name to index
$args_for_call = array();
foreach( $this->order_wanted as $param_name => $idx ){
$idx_given = $order_given[$param_name];
$val = $args_given[ $idx_given ];
$args_for_call[$idx] = $val;
}
return call_user_func_array( $this->func, $args_for_call );
}
}
// This should allow calling the FuncCaller object as a function,
// but it wasn't working for me for some reason
function __invoke(){
$args = func_get_args();
return call_user_func( $this->call, $args );
}
}
// Let's create a function for testing:
function test( $first, $second, $third ){
$first = var_export($first , TRUE);
$second = var_export($second, TRUE);
$third = var_export($third , TRUE);
echo "Called test( $first, $second, $third );<br>";
}
// Now we test the first version: order of arguments is specified
$caller = new FuncCaller( test, '1st;2nd;3rd' );
$caller->call(1, 2, 3);
$caller->call('3rd;1st;2nd', 'c', 'a', 'b');
$caller->call('2nd;3rd;1st', 'Two', 'Three', 'One');
$caller->call('3rd;2nd;1st', 'Go!', 'Get Set...', 'Get Ready...');
echo "<br>";
// Now we test the second version: order of arguments is acquired by reflection
// Note we you won't be able to rename arguments this way, as we did above
$reflection_caller = new FuncCaller( test );
$reflection_caller->call(1, 2, 3);
$reflection_caller->call('third;first;second', 'c', 'a', 'b');
$reflection_caller->call('second;third;first', 'Two', 'Three', 'One');
$reflection_caller->call('third;second;first', 'Go!', 'Get Set...', 'Get Ready...');
нет, но вы могли бы перегружать методы с разными заказами. Или вы можете попробовать сделать это с помощью рефлексии или интеллектуального угадывания параметров. Я сомневаюсь, что вы могли придумать элегантное решение, которое будет работать для всех функций.
У меня такое чувство, что они используют отражение для проверки объявления функции действия contoller; затем они делают вызов функции с аргументами в правильном порядке.
взгляните на http://www.php.net/manual/en/class.reflectionparameter.php. Он имеет getName и getPosition.
почему бы вам не добавить четвертую переменную $d, которая определяет входящий порядок в функцию.
class Test {
public function doStuff($d, $a, $b, $c) {
$v=explode(',', $d);
foreach($v as $key => $value){
switch ($value){
case 'a':
$aa = $v[a];
break;
case 'b':
$bb = $v[b];
break;
case 'a':
$cc = $v[c];
break;
}
echo "a is $aa, b is $bb, c is $cc"; //output
}
// -- Do stuff --
}
}
$d= "b,c,a"; // the order of the variables
$test = new Test();
$test->doStuff($d, $b, $c, $a);