PHP Reflection: как узнать, унаследован ли метод / свойство / константа от свойства?

Я хочу исключить все унаследованные методы из признаков из списка , которые не переопределяются в классе. Итак, как узнать, унаследован ли элемент класса от признака?

Да, я могу проверить это так:

if ($trait->hasMethod($methodName) || $ref->getTraitAliases()[$methodName] !== null) { // } 

Но что, если метод признака переоценивается в классе? Как это знать? Один из способов – проверить, схожи ли тела метода, если да, я могу исключить его, но есть ли лучший способ достичь этого?

    Важные заметки

    Это происходит только из-за «академического» интереса, в реальной ситуации вам не следует заботиться – откуда был получен метод, поскольку он противоречит идее признаков, например, прозрачной замены.

    Кроме того, из-за того, как черты работают, любые подобные манипуляции могут считаться «хакерскими», поэтому поведение может различаться в разных версиях PHP, и я бы не стал полагаться на это.

    Различие: трудности

    В отражении для PHP есть методы getTraits() которые возвращают экземпляр ReflectionClass , указывая на отражение признака. Это можно использовать для извлечения всех методов, объявленных в свойствах, которые используются в классе. Однако – нет, это не поможет в вашем вопросе, поскольку не будет возможности отличить, какие методы были тогда переопределены в классе.

    Представьте, что есть свойство X с методами foo() и bar() и есть класс Z с помощью метода bar() . Затем вы сможете узнать, что методы foo() и bar() объявлены в признаке, но если вы попытаетесь использовать getMethods() в классе Z вы, очевидно, получите как foo() и bar() . Поэтому прямо вы не можете отличить этот случай.

    Различие: work-aroud

    Однако, да, есть способ все еще заставить его работать. Первый способ – как вы уже упоминали, – попытаться исследовать исходный код. Это довольно уродливо, но в самом конце это единственный надежный способ решения проблемы на 100%.

    Но – нет, есть другой, «менее уродливый» способ – проверять экземпляры классов ReflectionMethod , которые создаются для методов класса / признаков. Бывает, что PHP будет использовать один и тот же экземпляр для метода trait, но переопределит тот, который предназначен для метода, объявленного в классе.

    Эта «проверка» может быть выполнена с помощью spl_object_hash() . Простая настройка:

     trait x { public function foo() { echo 'Trait x foo()'; } public function bar() { echo 'Trait x bar()'; } } class z { use x; public function foo() { echo 'Class foo()'; } } 

    И теперь, чтобы получить хэши для обоих случаев:

     function getTraitMethodsRefs(ReflectionClass $class) { $traitMethods = call_user_func_array('array_merge', array_map(function(ReflectionClass $ref) { return $ref->getMethods(); }, $class->getTraits())); $traitMethods = call_user_func_array('array_merge', array_map(function (ReflectionMethod $method) { return [spl_object_hash($method) => $method->getName()]; }, $traitMethods)); return $traitMethods; } function getClassMethodsRefs(ReflectionClass $class) { return call_user_func_array('array_merge', array_map(function (ReflectionMethod $method) { return [spl_object_hash($method) => $method->getName()]; }, $class->getMethods())); } 

    Короче говоря, он просто извлекает все методы из атрибута класса (первая функция) или самого класса (вторая функция), а затем объединяет результаты, чтобы получить key=>value карту key=>value где ключ – хэш объекта, а значение – имя метода.

    Тогда нам нужно использовать это в том же экземпляре, как это:

     $obj = new z; $ref = new ReflectionClass($obj); $traitRefs = getTraitMethodsRefs($ref); $classRefs = getClassMethodsRefs($ref); $traitOnlyHashes = array_diff( array_keys($traitRefs), array_keys($classRefs) ); $traitOnlyMethods = array_intersect_key($traitRefs, array_flip($traitOnlyHashes)); 

    Таким образом, $traitOnlyMethods будет содержать только те методы, которые получены из свойства.

    Соответствующая скрипка здесь . Но обратите внимание на результаты – они могут отличаться от версии к версии, как в HHVM, она просто не работает (я предполагаю, что из-за того, как реализована реализация spl_object_hash – в любом случае, небезопасно полагаться на нее для разграничения объектов – см. документацию для функции).

    Итак, TD; DR; – да, это может быть (каким-то образом) выполнено даже без разбора исходного кода, но я не могу представить, почему это необходимо, так как черты предназначены для замены кода в классе.

    Прошу прощения, но принятый ответ Алмой До совершенно не прав .

    Это решение не может работать, даже если вы преодолеете проблему перераспределения значений spl_object_hash (). Эта проблема может быть решена путем рефакторинга функций get*MethodRefs() в одну функцию, которая вычисляет оба результата и гарантирует, что объекты ReflectionMethod для методов признаков все еще существуют, когда аналогичные объекты для методов класса создаются. Это предотвращает повторное использование значений spl_object_hash ().

    Проблема в том, что предположение о том, что «PHP будет использовать один и тот же экземпляр для метода признаков», является полностью ложным, и появление этого события было вызвано именно «удачной» утилитой spl_object_hash (). Объект, возвращаемый $traitRef->getMethod('someName') всегда будет отличаться от объекта, возвращаемого $classRef->getMethod('someName') , и поэтому будут соответствующие экземпляры ReflectionMethod в коллекциях, возвращаемых ->getMethods() , независимо от того, переопределен ли метод someName() в классе или нет. Эти объекты будут не только отличными, они даже не будут «равны»: экземпляр ReflectionMethod полученный из $traitRef будет иметь имя признака как значение его свойства class , а тот, который получен из $classRef будет иметь имя класса.

    Сценарий: https://3v4l.org/CqEW3

    Казалось бы, тогда только подходы, основанные на парсерах, жизнеспособны.

    Более простой способ сделать это – ReflectionMethod::getFileName() . Это вернет имя файла, а не класс.

    Для экзотического случая, когда trait и class находятся в одном файле, можно использовать ReflectionMethod::getStartLine() , и сравнить это с начальной и конечной строкой признаков и класса.

    Для экзотического случая, когда черта, класс и метод все в одной строке .. о, пожалуйста!