2012-12-21 17 views
15

PHPcall_user_funcYanlış Statik Yöntem

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

Neden getName() işlevi Arabada özel ve Toyota'da herkese açık? – 1615903

+0

Hangi php sürümünü kullanıyorsunuz? Çünkü PHP 5.4'te 'Toyota' yerine iki kez 'Toyota'yı onaylıyor. Durumunda doğru bir şekilde anlaşsaydım, tersi olur. – Leri

+6

Farklı PHP sürümlerinde yaygın olarak değişiyor: http://3v4l.org/ekaEs - PHP'deki Hatalar! Dünya gerçekten sona ermeli. – deceze

cevap

1

Bu uzun süre varlığını içine ve dışına dalgalanma gibi görünüyor bir hata olduğunu (bkz @ deceze'nin testindeki soruları test eder).

PHP 5.3.2 yılında İşleri ve sonraki nedeniyle Özel/korumalı yöntemleri çağırmak için ReflectionMethod::setAccessible() bir bağımlılık için: -, yani PHP sürümleri arasında tutarlı bir davranış vermek - reflection kullanarak sorunu "çözmek" mümkündür. Bu kod için daha fazla açıklama ekleyeceğim, yapabilecekleri ve yapamayacakları ve çok kısa sürede nasıl çalıştıkları.

Maalesef bu kodu 3v4l.org üzerinde test etmek mümkün değil çünkü kod çok büyük, ancak bu, minifying PHP kodu için ilk gerçek kullanım vakasıdır - bunu yaparsanız 3v4l üzerinde çalışır, bu yüzden kendinizi özgür hissedin. etrafında oynamak ve kırıp kırmayacağınızı görmek için. Farkında olduğum tek sorun şu anda parent'u anlamadığı. Ayrıca, 5.4'ten önceki kapanışlarda $this desteğinden yoksun olmakla sınırlıdır, bununla ilgili gerçekten yapılacak hiçbir şey yoktur.

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

'u kullanan bir çözüm istiyorum Bir PHP hatasını bildirmeyi unutmayın ... – Baba

+0

@Baba, birinci paragrafa bir not ekledi. – DaveRandom

0

problem, iki getName fonksiyonların farklı erişim seviyesi ile, Bence olan denilen mevcut sınıf tanımlamak yerine yöntemin üst sınıf özel yöntemi çağırır. Eğer getname() public'in temel sınıf sürümünü (türetilmiş sınıf sürümü ile aynı) yaparsanız, php 5.3.15'te (Mac'imde), Toyota'yı alırsınız. Farklı erişim seviyeleri nedeniyle, temel sınıf sürümünü geçersiz kılan türetilmiş sınıf sürümü yerine, Toyota sınıfındaki getname() işlevinin iki farklı sürümü ile sonuçlandığını düşünüyorum. Diğer bir deyişle, geçersiz kılmak yerine aşırı yüklenme var. Bu nedenle, run() işlevi yürütmek için Toyota sınıfında bir getname() işlevini ararken, iki tane bulur ve ilkini (temel sınıftan) bildiren ilkini alır.

Bunun sadece benim tarafımdan bir varsayım olduğunu kabul ediyorum, ama mantıklı geliyor. Late Static Binding kullanma

farklı bir yaklaşımla bir çözüm bulduk
6

..

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

..

3

Böyle bir şey kullanabilirsiniz:

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

?> 

Çıktı:

Car 
Toyota 

PHP 5.4.5

0

get_called_called fonksiyon todo kullanmak bu

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

İşlevlerin birbirini geçersiz kıldığına ve varsayılan olarak birinciye gideceğine inanıyorum. Bir fonksiyonun parametrelerini değiştirmezseniz veya işlevi yeniden adlandırmazsanız, daima ana sınıf işlevine geri döner.

1

Yalnızca ebeveynlerden ve torunlarından erişim elde etmek istiyorsanız "korumalı" değiştiriciyi kullanın. IMO, açık. Örneğin,

'Statik' yerine get_called_class() öğesini kullanabilirsiniz.

İlgili konular