2012-12-21 21 views
15

PHP wywołuje metodę prywatnego w klasie nadrzędnej zamiast metody określania w bieżącej klasy zwanej przez call_user_funcNiewłaściwy Metoda statyczna

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

Dlaczego funkcja getName() jest prywatna w samochodzie i publicznym w Toyota? – 1615903

+0

Którą wersję php używasz? Ponieważ w PHP 5.4 echo "Toyota" dwa razy zamiast "Samochód". Jeśli dobrze zrozumiałem w twoim przypadku, dzieje się to inaczej. – Leri

+6

Wydaje się, że różni się znacznie w różnych wersjach PHP: http://3v4l.org/ekaEs - Błędy w PHP! Świat naprawdę musi się skończyć. – deceze

Odpowiedz

1

Jest to błąd, który pojawia się wahały się i znikają na długi czas (patrz testy @ deceze w komentarzach do pytania). Możliwe jest "naprawienie" tego problemu - to jest zapewnienie spójnego zachowania w różnych wersjach PHP - używanie reflection:

Działa w PHP 5.3.2 i późniejszych ze względu na zależność od ReflectionMethod::setAccessible(), aby wywołać metody prywatne/chronione. Dodam dalsze wyjaśnienie tego kodu, co może i czego nie może zrobić i jak działa bardzo krótko.

Niestety, nie jest możliwe przetestowanie tego bezpośrednio na 3v4l.org, ponieważ kod jest zbyt duży, jednak jest to pierwsza prawdziwa przypadek użycia dla kodu PHP minifying - działa to na 3v4l, jeśli to zrobisz, więc nie krępuj się bawić się i zobaczyć, czy możesz go złamać. Jedyny problem, o którym wiem, to że obecnie nie rozumie parent. Ogranicza się również brakiem wsparcia w zamknięciu przed 5.4, ale nie można nic na to poradzić.

<?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

Nie zapomnij wspomnieć o jego błąd PHP ... – Baba

+0

@Baba dodano notatkę do pierwszego akapitu. – DaveRandom

0

Problemem jest, jak sądzę, z różnymi poziomami dostępu do tych dwóch funkcji getName. Jeśli stworzysz wersję podstawową getname() public (taką samą jak wersja klasy pochodnej), to w php 5.3.15 (na moim Macu) otrzymasz Toyotę. Myślę, że z powodu różnych poziomów dostępu, kończy się to dwoma różnymi wersjami funkcji getname() w klasie Toyota, a nie pochodną wersją klasy przesłaniającą wersję klasy podstawowej. Innymi słowy, masz przeciążenie, a nie przesłonięcie. Dlatego też, gdy funkcja run() szuka funkcji getname() w klasie Toyoty do wykonania, znajduje dwa i bierze pierwszy, który będzie pierwszym, który zostanie zadeklarowany (z klasy bazowej).

To jest po prostu przypuszczenie z mojej strony, ale brzmi to wiarygodnie.

6

Znalazłem rozwiązanie z innego podejścia ..

<?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(); 
    ?> 

Korzystanie Late Static Binding ..

3

Możecie użyć czegoś takiego:

<?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(); 

?> 

wyjściowa:

Car 
Toyota 

PHP 5.4.5

0

użyć get_called_called funkcja todo to

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

Sądzę, że funkcje są nadrzędne i domyślnie przechodzą do pierwszego. Dopóki nie zmienisz parametrów jednej funkcji lub zmienisz jej nazwę, zawsze będzie ona domyślna dla funkcji klasy macierzystej.

1

Użyj modyfikatora "protected", jeśli chcesz uzyskać dostęp tylko od rodziców i potomków. IMO, to oczywiste. Na przykład:

<?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" 

Możesz użyć get_called_class() zamiast "static".

Powiązane problemy