15

PHPによって呼び出される現在のクラスで定義されたメソッドの代わりに、親クラスでプライベートメソッドを呼び出しますcall_user_func

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

7 に答える 7

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

を使用してLate Static Binding..

于 2012-12-21T12:09:14.183 に答える
3

次のようなものを使用できます。

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

?>

出力:

Car
Toyota

PHP 5.4.5

于 2013-04-11T10:04:20.277 に答える
1

これは、長い間存在の内外で変動しているように見えるバグです(質問に対するコメントの@decezeのテストを参照してください)。リフレクションを使用して、この問題を「修正」することができます。つまり、PHPバージョン間で一貫した動作を提供します。

ReflectionMethod::setAccessible()プライベート/保護されたメソッドを呼び出すことに依存しているため、PHP5.3.2以降で動作します。このコードの詳細、実行できることと実行できないこと、および動作方法については、後ほど説明します。

残念ながら、コードが大きすぎるため、これを3v4l.orgで直接テストすることはできませんが、これはPHPコードを縮小するための初めての実際のユースケースです。これを行うと3v4lで機能するため、自由に試してみてください。あなたがそれを壊すことができるかどうか見てください。私が知っている唯一の問題は、それが現在理解していないということparentです。また、$this5.4より前のクロージャでのサポートの欠如によって制限されていますが、これについてできることは実際には何もありません。

<?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;
    }
}
于 2013-04-09T18:02:31.153 に答える
1

親と子孫からのみアクセスを取得する場合は、「保護された」修飾子を使用します。IMO、それは明らかです。例えば:

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

'static'の代わりにget_called_class()を使用できます。

于 2013-04-15T04:29:07.290 に答える
0

問題は、2つのgetname関数のアクセスレベルが異なることだと思います。getname()の基本クラスバージョンを公開すると(派生クラスバージョンと同じ)、php 5.3.15(私のMacの場合)ではToyotaを取得します。アクセスレベルが異なるため、基本クラスのバージョンをオーバーライドする派生クラスのバージョンではなく、Toyotaクラスのgetname()関数の2つの異なるバージョンになってしまうと思います。つまり、オーバーライドするのではなく、オーバーロードが発生します。したがって、run()関数が実行するToyotaクラスのgetname()関数を探すとき、2つを見つけて、(基本クラスから)最初に宣言される最初の1つを取得します。

確かに、これは私の側の単なる仮定ですが、もっともらしいように聞こえます。

于 2012-12-21T11:36:44.217 に答える
0

これを行うには、get_called_called関数を使用します

public function run() {
    $self = get_called_class();
    return $self::getName();
}
于 2013-04-13T15:50:41.803 に答える
0

私はあなたが関数がお互いをオーバーライドしていて、デフォルトで最初のものに行くと信じています。1つの関数のパラメーターを変更するか、関数の名前を変更しない限り、デフォルトで親クラス関数になります。

于 2013-04-14T04:20:48.753 に答える