Как получить неквалифицированное (короткое) имя класса объекта?

Как проверить класс объекта в среде с разнесением имен PHP без указания полного класса с именами.

Например, предположим, что у меня есть объектная библиотека / Entity / Contract / Name.

Следующий код не работает, так как get_class возвращает полный класс с именами.

If(get_class($object) == 'Name') { ... do this ... } 

Ключевое слово magicpace namespace возвращает текущее пространство имен, которое бесполезно, если тестируемый объект имеет другое пространство имен.

Я мог бы просто указать полное имя класса с пространствами имен, но это, похоже, блокирует структуру кода. Также не очень полезно, если бы я хотел динамически менять пространство имен.

Может ли кто-нибудь подумать об эффективном способе этого. Я предполагаю, что одним из вариантов является регулярное выражение.

Вы можете сделать это с отражением. В частности, вы можете использовать метод ReflectionClass::getShortName , который получает имя класса без его пространства имен.

Во-первых, вам нужно создать экземпляр ReflectionClass , а затем вызвать метод getShortName этого экземпляра:

 $reflect = new ReflectionClass($object); if ($reflect->getShortName() === 'Name') { // do this } 

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

(new \ReflectionClass($obj))->getShortName(); является лучшим решением в отношении производительности.

Мне было любопытно, какой из предлагаемых решений является самым быстрым, поэтому я собрал небольшое испытание.

Результаты

 Reflection: 1.967512512207 s ClassA Basename: 2.6840535163879 s ClassA Explode: 2.6507515668869 s ClassA 

Код

 namespace foo\bar\baz; class ClassA{ public function getClassExplode(){ $c = array_pop(explode('\\', get_class($this))); return $c; } public function getClassReflection(){ $c = (new \ReflectionClass($this))->getShortName(); return $c; } public function getClassBasename(){ $c = basename(str_replace('\\', '/', get_class($this))); return $c; } } $a = new ClassA(); $num = 100000; $rounds = 10; $res = array( "Reflection" => array(), "Basename" => array(), "Explode" => array(), ); for($r = 0; $r < $rounds; $r++){ $start = microtime(true); for($i = 0; $i < $num; $i++){ $a->getClassReflection(); } $end = microtime(true); $res["Reflection"][] = ($end-$start); $start = microtime(true); for($i = 0; $i < $num; $i++){ $a->getClassBasename(); } $end = microtime(true); $res["Basename"][] = ($end-$start); $start = microtime(true); for($i = 0; $i < $num; $i++){ $a->getClassExplode(); } $end = microtime(true); $res["Explode"][] = ($end-$start); } echo "Reflection: ".array_sum($res["Reflection"])/count($res["Reflection"])." s ".$a->getClassReflection()."\n"; echo "Basename: ".array_sum($res["Basename"])/count($res["Basename"])." s ".$a->getClassBasename()."\n"; echo "Explode: ".array_sum($res["Explode"])/count($res["Explode"])." s ".$a->getClassExplode()."\n"; 

Результаты меня действительно удивили. Я думал, что решение взрыва будет самым быстрым способом …

Я добавил substr для проверки https://stackoverflow.com/a/25472778/2386943, и это тот способ, который я смог проверить (CentOS PHP 5.3.3, Ubuntu PHP 5.5.9) как с i5.

 $classNameWithNamespace=get_class($this); return substr($classNameWithNamespace, strrpos($classNameWithNamespace, '\\')+1); 

Результаты

 Reflection: 0.068084406852722 s ClassA Basename: 0.12301609516144 s ClassA Explode: 0.14073524475098 s ClassA Substring: 0.059865570068359 s ClassA 

Код

 namespace foo\bar\baz; class ClassA{ public function getClassExplode(){ $c = array_pop(explode('\\', get_class($this))); return $c; } public function getClassReflection(){ $c = (new \ReflectionClass($this))->getShortName(); return $c; } public function getClassBasename(){ $c = basename(str_replace('\\', '/', get_class($this))); return $c; } public function getClassSubstring(){ $classNameWithNamespace = get_class($this); return substr($classNameWithNamespace, strrpos($classNameWithNamespace, '\\')+1); } } $a = new ClassA(); $num = 100000; $rounds = 10; $res = array( "Reflection" => array(), "Basename" => array(), "Explode" => array(), "Substring" => array() ); for($r = 0; $r < $rounds; $r++){ $start = microtime(true); for($i = 0; $i < $num; $i++){ $a->getClassReflection(); } $end = microtime(true); $res["Reflection"][] = ($end-$start); $start = microtime(true); for($i = 0; $i < $num; $i++){ $a->getClassBasename(); } $end = microtime(true); $res["Basename"][] = ($end-$start); $start = microtime(true); for($i = 0; $i < $num; $i++){ $a->getClassExplode(); } $end = microtime(true); $res["Explode"][] = ($end-$start); $start = microtime(true); for($i = 0; $i < $num; $i++){ $a->getClassSubstring(); } $end = microtime(true); $res["Substring"][] = ($end-$start); } echo "Reflection: ".array_sum($res["Reflection"])/count($res["Reflection"])." s ".$a->getClassReflection()."\n"; echo "Basename: ".array_sum($res["Basename"])/count($res["Basename"])." s ".$a->getClassBasename()."\n"; echo "Explode: ".array_sum($res["Explode"])/count($res["Explode"])." s ".$a->getClassExplode()."\n"; echo "Substring: ".array_sum($res["Substring"])/count($res["Substring"])." s ".$a->getClassSubstring()."\n"; 

== UPDATE ==

Как уже упоминалось в комментариях @MrBandersnatch, есть даже более быстрый способ сделать это:

 return substr(strrchr(get_class($this), '\\'), 1); 

Ниже приведены обновленные результаты тестирования с помощью «SubstringStrChr» (экономит до примерно 0,001 с):

 Reflection: 0.073065280914307 s ClassA Basename: 0.12585079669952 s ClassA Explode: 0.14593172073364 s ClassA Substring: 0.060415267944336 s ClassA SubstringStrChr: 0.059880912303925 s ClassA 

Я использую это:

 basename(str_replace('\\', '/', get_class($object))); 

Чтобы получить короткое имя как однострочный (начиная с PHP 5.4 ):

 echo (new ReflectionClass($obj))->getShortName(); 

Это чистый подход и разумная скорость .

Вот простое решение для PHP 5.4+

 namespace { trait Names { public static function getNamespace() { return implode('\\', array_slice(explode('\\', get_called_class()), 0, -1)); } public static function getBaseClassName() { return basename(str_replace('\\', '/', get_called_class())); } } } 

Каким будет возвращение?

 namespace x\y\z { class SomeClass { use \Names; } echo \x\y\z\SomeClass::getNamespace() . PHP_EOL; // x\y\z echo \x\y\z\SomeClass::getBaseClassName() . PHP_EOL; // SomeClass } 

Расширенное имя класса и пространство имен хорошо подходят для:

 namespace d\e\f { class DifferentClass extends \x\y\z\SomeClass { } echo \d\e\f\DifferentClass::getNamespace() . PHP_EOL; // d\e\f echo \d\e\f\DifferentClass::getBaseClassName() . PHP_EOL; // DifferentClass } 

Что относительно класса в глобальном пространстве имен?

 namespace { class ClassWithoutNamespace { use \Names; } echo ClassWithoutNamespace::getNamespace() . PHP_EOL; // empty string echo ClassWithoutNamespace::getBaseClassName() . PHP_EOL; // ClassWithoutNamespace } 

Вот более простой способ сделать это, если вы используете Laravel PHP framework:

 <?php // usage anywhere // returns HelloWorld $name = class_basename('Path\To\YourClass\HelloWorld'); // usage inside a class // returns HelloWorld $name = class_basename(__CLASS__); 

Yii путь

 \yii\helpers\StringHelper::basename(get_class($model)); 

Yii использует этот метод в своем генераторе кода Gii

Методология

 This method is similar to the php function basename() except that it will treat both \ and / as directory separators, independent of the operating system. This method was mainly created to work on php namespaces. When working with real file paths, php's basename() should work fine for you. Note: this method is not aware of the actual filesystem, or path components such as "..". 

Дополнительная информация: https://github.com/yiisoft/yii2/blob/master/framework/helpers/BaseStringHelper.php http://www.yiiframework.com/doc-2.0/yii-helpers-basestringhelper.html#basename ( ) -detail

Если вам нужно знать имя класса, которое было вызвано внутри класса, и не хотите пространство имён, вы можете использовать этот

 $calledClass = get_called_class(); $name = strpos($calledClass, '\\') === false ? $calledClass : substr($calledClass, strrpos($calledClass, '\\') + 1); 

Это замечательно, когда у вас есть метод внутри класса, который расширяется другими классами. Кроме того, это также работает, если пространства имен вообще не используются.

Пример:

 <?php namespace One\Two { class foo { public function foo() { $calledClass = get_called_class(); $name = strpos($calledClass, '\\') === false ? $calledClass : substr($calledClass, strrpos($calledClass, '\\') + 1); var_dump($name); } } } namespace Three { class bar extends \One\Two\foo { public function bar() { $this->foo(); } } } namespace { (new One\Two\foo)->foo(); (new Three\bar)->bar(); } // test.php:11:string 'foo' (length=3) // test.php:11:string 'bar' (length=3) 

Основываясь на ответе @MaBi, я сделал следующее:

 trait ClassShortNameTrait { public static function getClassShortName() { if ($pos = strrchr(static::class, '\\')) { return substr($pos, 1); } else { return static::class; } } } 

Что вы можете использовать так:

 namespace Foo\Bar\Baz; class A { use ClassShortNameTrait; } 

A::class возвращает Foo\Bar\Baz\A , но A::getClassShortName() возвращает A

Работает для PHP> = 5.5.

Я оказался в уникальной ситуации, когда instanceof нельзя было использовать (в частности, с именами), и мне нужно было короткое имя наиболее эффективным способом, поэтому я сделал небольшой собственный тест. Он включает в себя все различные методы и варианты ответов в этом вопросе.

 $bench = new \xori\Benchmark(1000, 1000); # https://github.com/Xorifelse/php-benchmark-closure $shell = new \my\fancy\namespace\classname(); # Just an empty class named `classname` defined in the `\my\fancy\namespace\` namespace $bench->register('strrpos', (function(){ return substr(static::class, strrpos(static::class, '\\') + 1); })->bindTo($shell)); $bench->register('safe strrpos', (function(){ return substr(static::class, ($p = strrpos(static::class, '\\')) !== false ? $p + 1 : 0); })->bindTo($shell)); $bench->register('strrchr', (function(){ return substr(strrchr(static::class, '\\'), 1); })->bindTo($shell)); $bench->register('reflection', (function(){ return (new \ReflectionClass($this))->getShortName(); })->bindTo($shell)); $bench->register('reflection 2', (function($obj){ return $obj->getShortName(); })->bindTo($shell), new \ReflectionClass($shell)); $bench->register('basename', (function(){ return basename(str_replace('\\', '/', static::class)); })->bindTo($shell)); $bench->register('explode', (function(){ $e = explode("\\", static::class); return end($e); })->bindTo($shell)); $bench->register('slice', (function(){ return join('',array_slice(explode('\\', static::class), -1)); })->bindTo($shell)); print_r($bench->start()); 

Список всего результата здесь, но вот основные моменты:

  • Если вы все $obj->getShortName() будете использовать отражение, использование $obj->getShortName() – самый быстрый способ; используя отражение, чтобы получить короткое имя, это почти самый медленный метод.
  • 'strrpos' может возвращать неправильное значение, если объект не находится в пространстве имен, поэтому, пока 'safe strrpos' немного медленнее, я бы сказал, что это победитель.
  • Чтобы сделать 'basename' совместимым между Linux и Windows, вам нужно использовать str_replace() что делает этот метод самым медленным из всех.

Упрощенная таблица результатов, скорость измеряется по сравнению с самым медленным методом:

 +-----------------+--------+ | registered name | speed | +-----------------+--------+ | reflection 2 | 70.75% | | strrpos | 60.38% | | safe strrpos | 57.69% | | strrchr | 54.88% | | explode | 46.60% | | slice | 37.02% | | reflection | 16.75% | | basename | 0.00% | +-----------------+--------+ 

Найдено на странице документации get_class , где она была опубликована мной в nwhiting dot com .

 function get_class_name($object = null) { if (!is_object($object) && !is_string($object)) { return false; } $class = explode('\\', (is_string($object) ? $object : get_class($object))); return $class[count($class) - 1]; } 

Но идея пространств имен заключается в структурировании вашего кода. Это также означает, что вы можете иметь классы с одинаковым именем в нескольких пространствах имен. Таким образом, теоретически объект, который вы передаете, может иметь имя класса (разделенное), но все же являться совершенно другим объектом, чем вы ожидаете.

Кроме того, вы можете проверить определенный базовый класс , и в этом случае get_class вообще не делает этого. Вы можете проверить instanceof оператора.

Вы можете получить неожиданный результат, если класс не имеет пространства имен. Т.е. get_class возвращает Foo , тогда $baseClass будет oo .

 $baseClass = substr(strrchr(get_class($this), '\\'), 1); 

Это можно легко устранить, префикс get_class с обратной косой чертой:

 $baseClass = substr(strrchr('\\'.get_class($this), '\\'), 1); 

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

Цитирование php.net:

В Windows как слэш (/), так и обратная косая черта () используются как символ разделителя каталога. В других средах это косая черта (/).

Основываясь на этой информации и расширяясь от ответа arzzzen, это должно работать как на системах Windows, так и на Nix *:

 <?php if (basename(str_replace('\\', '/', get_class($object))) == 'Name') { // ... do this ... } 

Примечание. Я сделал тест ReflectionClass против basename+str_replace+get_class а использование отражения примерно на 20% быстрее, чем использование подхода basename, но YMMV.

Самое быстрое и самое удобное решение, которое работает в любой среде:

 <?php namespace \My\Awesome\Namespace; class Foo { private $shortName; public function fastShortName() { if ($this->shortName === null) { $this->shortName = explode("\\", static::class); $this->shortName = end($this->shortName); } return $this->shortName; } public function shortName() { return basename(strtr(static::class, "\\", "/")); } } echo (new Foo())->shortName(); // "Foo" ?> 
 $shortClassName = join('',array_slice(explode('\\', $longClassName), -1)); 

Если вы просто удаляете пространства имен и хотите что-либо после последнего \ в имени класса с пространством имен (или просто имя, если оно отсутствует), вы можете сделать что-то вроде этого:

 $base_class = preg_replace('/^([\w\\\\]+\\\\)?([^\\\\]+)$/', '$2', get_class($myobject)); 

В основном это регулярное выражение, чтобы получить любую комбинацию символов или обратную косую черту и до последней обратной косой черты, чтобы вернуть только символы без обратного сползания вверх и до конца строки. Добавление? после первого группирования означает, что совпадение шаблона не существует, оно просто возвращает полную строку.

Хорошее старое регулярное выражение, кажется, быстрее, чем большинство предыдущих показанных методов:

 // both of the below calls will output: ShortClassName echo preg_replace('/.*\\\\/', '', 'ShortClassName'); echo preg_replace('/.*\\\\/', '', 'SomeNamespace\SomePath\ShortClassName'); 

Таким образом, это работает, даже если вы предоставляете короткое имя класса или полное (каноническое) имя класса.

То, что делает регулярное выражение, состоит в том, что он потребляет все предыдущие символы до тех пор, пока не будет найден последний разделитель (который также потребляется). Таким образом, оставшаяся строка будет коротким именем класса.

Если вы хотите использовать другой разделитель (например, /), тогда просто используйте этот разделитель. Не забудьте избежать обратного слэша (т. Е. \), А также шаблон char (т. Е. /) В шаблоне ввода.