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

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

(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(){
        return explode('\\', static::class)[0];
    }

    public function getClassReflection(){
        return (new \ReflectionClass($this))->getShortName();
    }

    public function getClassBasename(){
        return basename(str_replace('\\', '/', static::class));
    }
}

$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";

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

Ответ 3

Я добавил substr к тесту fooobar.com/questions/59316/... и что метод fastet, который я мог бы проверить (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";

== ОБНОВЛЕНИЕ ==

Как упоминалось в комментариях @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

Ответ 4

Вот более простой способ сделать это, если вы используете 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__);

Ответ 5

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

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

Ответ 6

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

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

Это чистый подход и достаточно быстрый.

Ответ 7

Я оказался в уникальной ситуации, когда 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%  |
+-----------------+--------+

Ответ 8

Вы можете использовать explode для разделения пространства имен и end чтобы получить имя класса:

$ex = explode("\\", get_class($object));
$className = end($ex);

Ответ 9

Вот простое решение для 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
}

Ответ 10

Yii путь

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

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

Методическая документация

Этот метод похож на функцию php basename() за исключением того, что он будет обрабатывать как \, так и//как разделители каталогов, независимо от операционной системы. Этот метод был в основном создан для работы с пространствами имен php. При работе с реальными путями к файлам php basename() должен работать нормально. Примечание: этот метод не знает о фактической файловой системе или компонентах пути, таких как "..".

Дополнительная информация:

https://github.com/yiisoft/yii2/blob/master/framework/helpers/BaseStringHelper.php http://www.yiiframework.com/doc-2.0/yii-helpers-basestringhelper.html#basename()[CN00 ]

Ответ 11

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

$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)

Ответ 12

Основываясь на ответе @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.

Ответ 13

Найдено на странице документации страницы 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.

Ответ 14

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

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

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

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

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

Ответ 15

Цитата php.net:

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

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

<?php

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

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

Ответ 16

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

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

?>

Ответ 17

$shortClassName = join('',array_slice(explode('\\', $longClassName), -1));

Ответ 18

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

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

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

Ответ 19

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

// both of the below calls will output: ShortClassName

echo preg_replace('/.*\\\\/', '', 'ShortClassName');
echo preg_replace('/.*\\\\/', '', 'SomeNamespace\SomePath\ShortClassName');

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

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

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

Ответ 20

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

namespace Foo\Bar\Baz;

class Test {
    public function getClass() {
        return str_replace(__NAMESPACE__.'\\', '', static::class);
    }
}

Ответ 21

Поскольку "ReflectionClass" может зависеть от версии, просто используйте следующее:

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

или даже очистить

if(class_basename(ClassName::class) == 'ClassName') {
... do this ...
}