2012-12-21 20 views
15

PHP chiamate di metodo privato nella classe genitrice al posto del metodo di definire in classe corrente chiamato da call_user_funcsbagliato Metodo statico

class Car { 
    public function run() { 
     return call_user_func(array('Toyota','getName')); // should call toyota 
    } 
    private static function getName() { 
     return 'Car'; 
    } 
} 

class Toyota extends Car { 
    public static function getName() { 
     return 'Toyota'; 
    } 
} 

$car = new Car(); 
echo $car->run(); //Car instead of Toyota 

$toyota = new Toyota(); 
echo $toyota->run(); //Car instead of Toyota 
+4

Perché la funzione getName() è privata in Auto e pubblico in Toyota? – 1615903

+0

Quale versione di php stai usando? Perché su PHP 5.4 echeggia "Toyota" due volte anziché "Car". Se ho capito correttamente nel tuo caso, succede contrario. – Leri

+6

Sembra variare ampiamente tra le diverse versioni di PHP: http://3v4l.org/ekaEs - Bug in PHP! Il mondo deve davvero finire. – deceze

risposta

1

Si tratta di un bug che sembra aver oscillato dentro e fuori di esistenza per un lungo periodo (si veda @ test di deceze nei commenti sulla domanda). È possibile "risolvere" questo problema - ovvero, dare un comportamento coerente tra le versioni di PHP - utilizzando reflection:

Funziona in PHP 5.3.2 e versioni successive a causa di una dipendenza da ReflectionMethod::setAccessible() per richiamare metodi privati ​​/ protetti. Aggiungerò ulteriori spiegazioni per questo codice, cosa può e cosa non può fare e come funziona molto presto.

Purtroppo non è possibile testarlo direttamente su 3v4l.org perché il codice è troppo grande, tuttavia questo è il primo caso di utilizzo reale per il codice PHP minifying - funziona su 3v4l se lo fai, quindi sentiti libero giocare e vedere se riesci a romperlo. L'unico problema di cui sono a conoscenza è che attualmente non comprende parent. È anche limitato dalla mancanza del supporto $this nelle chiusure prima delle 5.4, ma non c'è davvero nulla che possa essere fatto a riguardo.

<?php 

function call_user_func_fixed() 
{ 
    $args = func_get_args(); 
    $callable = array_shift($args); 
    return call_user_func_array_fixed($callable, $args); 
} 

function call_user_func_array_fixed($callable, $args) 
{ 
    $isStaticMethod = false; 
    $expr = '/^([a-z_\x7f-\xff][\w\x7f-\xff]*)::([a-z_\x7f-\xff][\w\x7f-\xff]*)$/i'; 

    // Extract the callable normalized to an array if it looks like a method call 
    if (is_string($callable) && preg_match($expr, $callable, $matches)) { 
     $func = array($matches[1], $matches[2]); 
    } else if (is_array($callable) 
        && count($callable) === 2 
        && isset($callable[0], $callable[1]) 
        && (is_string($callable[0]) || is_object($callable[0])) 
        && is_string($callable[1])) { 
     $func = $callable; 
    } 

    // If we're not interested in it use the regular mechanism 
    if (!isset($func)) { 
     return call_user_func_array($func, $args); 
    } 

    $backtrace = debug_backtrace(); // passing args here is fraught with complications for backwards compat :-(
    if ($backtrace[1]['function'] === 'call_user_func_fixed') { 
     $called = 'call_user_func_fixed'; 
     $contextKey = 2; 
    } else { 
     $called = 'call_user_func_array_fixed'; 
     $contextKey = 1; 
    } 

    try { 
     // Get a reference to the target static method if possible 
     switch (true) { 
      case $func[0] === 'self': 
      case $func[0] === 'static': 
       if (!isset($backtrace[$contextKey]['object'])) { 
        throw new Exception('Use of self:: in an invalid context'); 
       } 

       $contextClass = new ReflectionClass($backtrace[$contextKey][$func[0] === 'self' ? 'class' : 'object']); 
       $contextClassName = $contextClass->getName(); 

       $method = $contextClass->getMethod($func[1]); 
       $ownerClassName = $method->getDeclaringClass()->getName(); 
       if (!$method->isStatic()) { 
        throw new Exception('Attempting to call instance method in a static context'); 
       } 
       $invokeContext = null; 

       if ($method->isPrivate()) { 
        if ($ownerClassName !== $contextClassName 
          || !method_exists($method, 'setAccessible')) { 
         throw new Exception('Attempting to call private method in an invalid context'); 
        } 

        $method->setAccessible(true); 
       } else if ($method->isProtected()) { 
        if (!method_exists($method, 'setAccessible')) { 
         throw new Exception('Attempting to call protected method in an invalid context'); 
        } 

        while ($contextClass->getName() !== $ownerClassName) { 
         $contextClass = $contextClass->getParentClass(); 
        } 
        if ($contextClass->getName() !== $ownerClassName) { 
         throw new Exception('Attempting to call protected method in an invalid context'); 
        } 

        $method->setAccessible(true); 
       } 

       break; 

      case is_object($func[0]): 
       $contextClass = new ReflectionClass($func[0]); 
       $contextClassName = $contextClass->getName(); 

       $method = $contextClass->getMethod($func[1]); 
       $ownerClassName = $method->getDeclaringClass()->getName(); 

       if ($method->isStatic()) { 
        $invokeContext = null; 

        if ($method->isPrivate()) { 
         if ($ownerClassName !== $contextClassName || !method_exists($method, 'setAccessible')) { 
          throw new Exception('Attempting to call private method in an invalid context'); 
         } 

         $method->setAccessible(true); 
        } else if ($method->isProtected()) { 
         if (!method_exists($method, 'setAccessible')) { 
          throw new Exception('Attempting to call protected method in an invalid context'); 
         } 

         while ($contextClass->getName() !== $ownerClassName) { 
          $contextClass = $contextClass->getParentClass(); 
         } 
         if ($contextClass->getName() !== $ownerClassName) { 
          throw new Exception('Attempting to call protected method in an invalid context'); 
         } 

         $method->setAccessible(true); 
        } 
       } else { 
        $invokeContext = $func[0]; 
       } 

       break; 

      default: 
       $contextClass = new ReflectionClass($backtrace[$contextKey]['object']); 
       $method = new ReflectionMethod($func[0], $func[1]); 
       $ownerClassName = $method->getDeclaringClass()->getName(); 
       if (!$method->isStatic()) { 
        throw new Exception('Attempting to call instance method in a static context'); 
       } 
       $invokeContext = null; 

       if ($method->isPrivate()) { 
        if (empty($backtrace[$contextKey]['object']) 
          || $func[0] !== $contextClass->getName() 
          || !method_exists($method, 'setAccessible')) { 
         throw new Exception('Attempting to call private method in an invalid context'); 
        } 

        $method->setAccessible(true); 
       } else if ($method->isProtected()) { 
        $contextClass = new ReflectionClass($backtrace[$contextKey]['object']); 

        if (empty($backtrace[$contextKey]['object']) || !method_exists($method, 'setAccessible')) { 
         throw new Exception('Attempting to call protected method outside a class context'); 
        } 

        while ($contextClass->getName() !== $ownerClassName) { 
         $contextClass = $contextClass->getParentClass(); 
        } 
        if ($contextClass->getName() !== $ownerClassName) { 
         throw new Exception('Attempting to call protected method in an invalid context'); 
        } 

        $method->setAccessible(true); 
       } 

       break; 
     } 

     // Invoke the method with the passed arguments and return the result 
     return $method->invokeArgs($invokeContext, $args); 
    } catch (Exception $e) { 
     trigger_error($called . '() expects parameter 1 to be a valid callback: ' . $e->getMessage(), E_USER_ERROR); 
     return null; 
    } 
} 
+0

Non dimenticarti di menzionare un bug PHP ... – Baba

+0

@Baba aggiunge una nota al primo paragrafo. – DaveRandom

0

Il problema è, credo, con i diversi livelli di accesso delle due funzioni getName. Se si rende pubblica la versione della classe base di getname() (uguale alla versione della classe derivata), quindi in PHP 5.3.15 (sul mio Mac), si ottiene Toyota. Penso che, a causa dei diversi livelli di accesso, si finisca con due diverse versioni della funzione getname() nella classe Toyota, piuttosto che la versione della classe derivata che sovrascrive la versione della classe base. In altre parole, si ha un sovraccarico piuttosto che un override. Pertanto, quando la funzione run() cerca una funzione getname() nella classe Toyota da eseguire, trova due e prende il primo, che sarebbe il primo a essere dichiarato (dalla classe base).

Concesso che questo è solo supposizione da parte mia, ma suona plausibile.

6

ho trovato una soluzione con un approccio diverso ..

<?php 
class Car { 
    public static function run() { 
    return static::getName(); 
    } 
    private static function getName() { 
    return 'Car'; 
    } 
    } 

    class Toyota extends Car { 
    public static function getName() { 
     return 'Toyota'; 
     } 
    } 
echo Car::run(); 
echo Toyota::run(); 
    ?> 

Utilizzando Late Static Binding ..

3

si potrebbe usare qualcosa di simile a questo:

<?php 

class Car { 
    public function run() { 
     return static::getName(); 
    } 

    private static function getName(){ 
     return 'Car'; 
    } 
} 

class Toyota extends Car { 
    public static function getName(){ 
     return 'Toyota'; 
    } 
} 

$car = new Car(); 
echo $car->run(); 

echo PHP_EOL; 

$toyota = new Toyota(); 
echo $toyota->run(); 

?> 

uscita:

Car 
Toyota 

PHP 5.4.5

0

utilizzare la funzione di todo get_called_called questo

public function run() { 
    $self = get_called_class(); 
    return $self::getName(); 
} 
0

Credo che le funzioni si annullino a vicenda e che per impostazione predefinita si passi al primo. A meno che non si modifichino i parametri di una funzione o si rinomino la funzione, essa sarà sempre predefinita per la funzione della classe genitore.

1

Utilizzare il modificatore "protetto" se si desidera ottenere l'accesso solo da genitori e discendenti. IMO, è ovvio. Ad esempio:

<?php 

class Car { 
    public function run() { 
     return call_user_func(array('static','getName')); 
    } 
    protected static function getName() { 
     return 'Car'; 
    } 
} 

class Toyota extends Car { 
    protected static function getName() { 
     return 'Toyota'; 
    } 
} 

$car = new Car(); 
echo $car->run(); // "Car" 

$toyota = new Toyota(); 
echo $toyota->run(); // "Toyota" 

È possibile utilizzare get_called_class() anziché "statico".

Problemi correlati