2012-10-19 13 views
6

Können sagen, ich Klasse mit Methode wie folgt definieren:Wie können PHP-Funktion oder Methodenargumente in beliebiger Reihenfolge verwendet werden?

class Test { 
    public function doStuff($a, $b, $c) { 
    // -- Do stuff -- 
    } 
} 

Ist es möglich, diese Methode zu verwenden, sondern mit Argumenten in einer anderen Reihenfolge, wie folgt aus:

$test = new Test(); 
$test->doStuff($b, $c, $a); 

Sie die gleichen Namen haben würde, aber nur andere Reihenfolge.

Ich sehe Symfony2 kann es mit seinem Dispatcher tun, können Sie Argumente in beliebiger Reihenfolge verwenden. Link: Symfony2 controller can do it

Die Frage ist, wie dies funktioniert? Wie kann Symfony2 einen geeigneten Action-Controller aufrufen, der dann Argumente in beliebiger Reihenfolge akzeptiert?

Edit: Ich kann Arrays nicht verwenden, und ich weiß, dass PHP nicht benannte Argumente verwendet. Aber Symfony2 schafft es irgendwie.

+0

Die kurze Antwort ist nein – kalpaitch

+2

@kalpaitch - nun, symfony2 tut es, also gibt es einen Weg. – otporan

+1

Werfen Sie einen Blick auf die Vorschläge hier: http://StackOverflow.com/Questions/4697705/PHP-Function-Overloading –

Antwort

8

Ich glaube, Sie missverstehen, was Symfony sagt. Sie können die Argumente für die Controller-Aktion nicht in einer bestimmten Reihenfolge übergeben, sondern müssen in einer bestimmten Reihenfolge sein. Was sie tun, ist dynamisch, jedoch herauszufinden, in welcher Reihenfolge Ihre Routing-Parameter in der Funktionsdefinition sind.

Zum Beispiel definieren wir eine Route:

pattern:  /hello/{first_name}/{last_name} 
defaults:  { _controller: AcmeHelloBundle:Hello:index, color: green } 

In der Route werden die Parameter first_name genannt, last_name und color.

Was sie sagen, ist, dass es egal ist, welche Reihenfolge Sie für die Parameter in der Aktion verwenden.

Jede der folgenden äquivalent:

public function indexAction($first_name, $last_name, $color) {...} 
public function indexAction($color, $last_name, $first_name) {...} 
public function indexAction($last_name, $color, $first_name) {...} 

Da Ihre Argumente die gleichen wie die Parameter in der Route genannt werden, Symfony herausfindet, was die richtige Reihenfolge der Argumente ist auf der Grundlage Ihrer Definition.

Wenn Sie waren die folgende Aktion manuell aufrufen:

public function indexAction($first_name, $last_name, $color) 

Dann werden die Argumente noch müssen in als $first_name, $last_name, $color weitergegeben werden und nicht in einer anderen Reihenfolge. Bei Verwendung einer anderen Reihenfolge würden den Argumenten nur die falschen Werte zugeordnet. Symfony ist es egal, in welcher Reihenfolge Sie Ihre Funktion definieren, da sie die Reihenfolge bestimmt, weil Ihre Routing-Parameter denselben Namen haben müssen wie Ihre Methodenargumente.

Hoffentlich, dass die Verwirrung aufhebt.

+0

Wie also ermitteln sie dynamisch die Reihenfolge, in der die Argumente in der Funktionsdefinition erscheinen? Ich denke nicht, dass dies programmatisch erfolgen kann, wenn die Datei nicht als Text geöffnet und geparst wird. – Mahn

+3

@Mahn Der Schlüssel ist, dass die Funktionsparameter genauso benannt werden müssen wie die Routenparameter. Um die Informationen über die Klasse, die Methode und ihre Argumente zu erhalten, verwenden sie wahrscheinlich [ReflectionClass] (http://php.net/reflectionclass) und dann [ReflectionMethod] (http://www.php.net/manual) /en/class.reflectionmethod.php) für die Klassenmethode, um die Reihenfolge der Argumente zu erhalten. – drew010

+0

Ah, das ist richtig, es kann auch mit den Reflection Klassen gemacht werden. – Mahn

2

Nein, aber Sie könnten Methoden mit unterschiedlichen Aufträgen überladen haben. Oder Sie könnten versuchen, es mit Reflektion oder intelligentem Raten über Parameter zu tun. Ich bezweifle, dass Sie eine elegante Lösung finden, die für alle Funktionen funktioniert.

1

Warum fügen Sie keine vierte Variable $ d hinzu, die die eingehende Reihenfolge für die Funktion definiert.

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); 
+0

Interessante Idee. Schade, dass dein Code kaputt ist. – Zecc

+0

@Zecc, yeah, tut mir leid, ich konnte den Code nicht testen, nur eine Skizze. Ich mag, was du oben getan hast. – Sablefoste

+0

Danke. Du hast mich inspiriert. Und ich hatte etwas Zeit, um es vollständig zu debuggen :) – Zecc

4

Sable Fostes Idee hat mich inspiriert.

können Sie einen anderen Parameter verwenden, um die Reihenfolge zu bestimmen und dann variable variables verwenden:

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'); 

Aber im Ernst, warum können Sie nicht ein Array verwenden? Es ist der beste Weg.


aktualisieren: Ich schrieb diese. Ich muss wirklich gelangweilt sein.

Jetzt fühle ich mich dreckig.

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...'); 
2

Ich habe das Gefühl, dass sie Reflexion verwenden, um die Deklaration der Schaltfunktion zu überprüfen; dann machen sie den Funktionsaufruf mit den Argumenten in der richtigen Reihenfolge.

Werfen Sie einen Blick auf http://www.php.net/manual/en/class.reflectionparameter.php. Es hat einen getName und getPosition.