如何获取对象的非限定(短)类名?

Posted

技术标签:

【中文标题】如何获取对象的非限定(短)类名?【英文标题】:How do I get an object's unqualified (short) class name? 【发布时间】:2013-11-22 23:44:03 【问题描述】:

如何在 php 名称空间环境中检查对象的类而不指定完整的命名空间类。

例如,假设我有一个对象库/实体/合同/名称。

以下代码不起作用,因为 get_class 返回完整的命名空间类。

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

命名空间魔法关键字返回当前命名空间,如果被测对象有另一个命名空间,它就没有用了。

我可以简单地用命名空间指定完整的类名,但这似乎锁定了代码的结构。如果我想动态更改命名空间,也没有多大用处。

谁能想到一个有效的方法来做到这一点。我想一种选择是正则表达式。

【问题讨论】:

这似乎毫无意义,因为不同的命名空间可能在其中定义了相同的类名,那么您将如何处理呢?那是因为在您的示例中返回了完整的限定类名 我在移动设备上,所以我不能提交一个像样的答案,但解决方案是反射,特别是 ReflectionClass::getShortName - php.net/manual/en/reflectionclass.getshortname.php 对于寻找理由的人来说:它可能在公共基类中的辅助函数中很有用(即在这种情况下,多个命名空间绝不是问题)。 【参考方案1】:

在documentation page of 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

【讨论】:

【参考方案2】:

您可以通过反射来做到这一点。具体来说,您可以使用ReflectionClass::getShortName 方法,该方法获取没有命名空间的类的名称。

首先需要构建一个ReflectionClass实例,然后调用该实例的getShortName方法:

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

但是,我无法想象在许多情况下这是可取的。如果你想要求对象是某个类的成员,测试它的方法是instanceof。如果您想要一种更灵活的方式来表示某些约束,那么这样做的方法是编写一个接口并要求代码实现该接口。同样,正确的方法是使用instanceof。 (您可以使用ReflectionClass 来实现,但性能会差很多。)

【讨论】:

@Greg.Forbes 因为Tenant 在当前命名空间中不存在。请改用var_dump($tenant instanceof \Library\Entity\People\Tenant)。另外,研究如何使用 use 运算符,以及 PHP 命名空间背后的一般概念! 我必须像这样在前面添加一个斜线$reflect = new \ReflectionClass($object); 我一般不喜欢在我的应用程序中做很多 ReflectionClass voodoo,因为如果使用不当(受保护的方法被公开等)会导致意想不到的结果。您可以在 PHP 魔术常量上使用简单的字符串替换:str_replace(__NAMESPACE__ . '\\', '', __CLASS__);。它在性能方面也更快。 @FranklinPStrube 除非我遗漏了什么,否则它会获取当前类的短名称,而不是对象的类。我同意使用反射通常意味着你做错了。 许多人使用反射来覆盖成员可见性,这很糟糕。不要那样做!但是说反射的使用一般是巫毒和做错了会给人们错误的印象。你不应该回避它们,你应该理解它们并知道它们什么时候有用,在什么抽象级别。【参考方案3】:

我用这个:

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

【讨论】:

你也可以试试:$className = explode('\\', basename(get_class($this))); $className = array_pop($className);得到普通的类名。或者使用 substr. 仅适用于 Windows 在 Windows 上,斜杠 (/) 和反斜杠 () 都用作目录分隔符。其他环境下为正斜杠(/)php.net/manual/en/function.basename.php 我现在已经修好了。谢谢,@OzzyCzech。 @OzzyCzech 我刚刚在从 Windows 迁移到 Ubuntu 时遇到了这个问题……令人抓狂。使用 MaBi 更新中提到的解决方案结束。 @OzzyCzech 为什么只能在 Windows 上工作?如果几年前我没记错的话,问题是关于完全限定的命名空间名称,并且命名空间不是特定于操作系统的,并且总是带有像 windows 目录分隔符这样的反斜杠。【参考方案4】:

这是 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

【讨论】:

【参考方案5】:

引用php.net:

在 Windows 上,斜杠 (/) 和反斜杠 () 都用作目录分隔符。在其他环境中,它是正斜杠 (/)。

基于此信息并从 arzzzen 答案扩展,这应该适用于 Windows 和 Nix* 系统:

<?php

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

注意:我对 ReflectionClassbasename+str_replace+get_class 进行了基准测试,使用反射比使用基本名称方法快大约 20%,但 YMMV。

【讨论】:

【参考方案6】:

适用于任何环境的最快和最简单的解决方案是:

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

?>

【讨论】:

这就是我希望 PHP 有内部类信息运算符的原因。实例化一个外部反射器来做应该像$Object-&gt;__class-&gt;getShortName() 这样简单的事情真的让我对 PHP 很生气。您的方法有效,但现在您将具体方法放入您的类中只是为了公开应该是一种语言结构。 没有“具体”(或者我们应该称之为程序)函数的 PHP 是不可能的。让我们等待 PHP 6(好吧,如果它来了)。【参考方案7】:

获取短名称作为单行符(从PHP 5.4 开始):

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

这是一种干净的方法,reasonable fast。

【讨论】:

我想知道这与基准测试中的字符串提取相比如何。看起来这会慢得多。【参考方案8】:

(new \ReflectionClass($obj))-&gt;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";

结果真的让我吃惊。我认为爆炸解决方案将是最快的方法......

【讨论】:

很好的答案。我运行的是相同的代码,但得到了不同的结果(Macbook Pro i7,16 GB 内存)。反射:0.382,基本名称:0.380,爆炸:0.399。我认为这取决于你的系统什么是最好的...... 使用该代码运行 PHP 10 000 次,您将获得更好的结果。以上可能会从某个池中获取反射,但这不是应用程序的通常行为。他们只需要一次或两次。 我想知道在您的测试中在比 A 类的小对象更实质的对象上实例化 ReflectionClass 时,这个测试是否成立... 只运行一次迭代而不是 100000 会得到截然不同的结果:反射:1.0967254638672 100000th/s ClassA Basename:0.81062316894531 100000th/s ClassA Explode:0.50067901611328 100000th explode('\\', static::class)[0] ?它不返回命名空间的第一部分吗?应该返回最后一部分,而不是第一部分【参考方案9】:
$shortClassName = join('',array_slice(explode('\\', $longClassName), -1));

【讨论】:

【参考方案10】:

我在https://***.com/a/25472778/2386943 的测试中添加了 substr 这是我可以用 i5 测试的最快方法(CentOS PHP 5.3.3、Ubuntu PHP 5.5.9)。

$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 在 cmets 中提到的,还有一种更快的方法可以做到这一点:

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

【讨论】:

仅仅因为我们列出效率,我发现这是最快的,与此解决方案中提供的测试进行比较 substr(strrchr(get_class($obj), '\\'), 1);反射:0.084223914146423 s ClassA -- Basename:0.13206427097321 s ClassA -- Explode:0.15331919193268 s ClassA -- 子字符串:0.068068099021912 s ClassA --Strrchar:0.06472008228302 ClassA -- 我刚刚遇到这个线程并添加了一个额外的基准来测试str_replace(__NAMESPACE__ . '\\', '', __CLASS__);。在一个弱虚拟机上的结果表明它几乎是所有这些的两倍。 php -f bench.php Reflection: 0.44037771224976 s ClassA Basename: 0.48089025020599 s ClassA Explode: 0.54955270290375 s ClassA Substring: 0.38200764656067 s ClassA Frank's Custom Benchmark: 0.22782742977142 s ClassA @MrBandersnatch 你是对的。我测试了您的解决方案,它为我节省了大约 0.001 秒。我用你的更新了我的答案! 警告:此代码不适用于全局命名空间中的类(即:它们的全名等于它们的短名)!我建议测试类似:if ($pos = strrchr(static::class, '\\')) .. else ... . 要让它在全局命名空间中也能工作,只需在类名前加上一个反斜杠:) - 即:$classNameShort = substr(strrchr('\\' . get_class($this), '\\'), 1);【参考方案11】:

当类没有命名空间时,您可能会得到意想不到的结果。 IE。 get_class 返回Foo,然后$baseClass 将是oo

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

这可以通过在 get_class 前加上反斜杠来轻松解决:

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

现在,没有命名空间的类也将返回正确的值。

【讨论】:

【参考方案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】:

如果您需要知道从类内部调用的类名,并且不想要命名空间,您可以使用这个

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

【讨论】:

【参考方案14】:

如果你使用 Laravel PHP 框架,这里有一个更简单的方法:

<?php

// usage anywhere
// returns HelloWorld
$name = class_basename('Path\To\YourClass\HelloWorld');

// usage inside a class
// returns HelloWorld
$name = class_basename(__CLASS__);


/**
 * Get the class "basename" of the given object / class.
 *
 * @param  string|object  $class
 * @return string
 */
function class_basename($class)

    $class = is_object($class) ? get_class($class) : $class;

    return basename(str_replace('\\', '/', $class));

【讨论】:

这不是内置的php函数,它看起来像laravel提供的辅助函数。 我想他是这么说的 谢谢,我正在使用 Laravel,这个答案为我节省了很多时间。 github.com/laravel/framework/blob/…【参考方案15】:

我发现自己处于一种特殊情况,无法使用 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());

整个结果的列表是here,但这里是亮点:

如果你还是要使用反射,使用$obj-&gt;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%  |
+-----------------+--------+

【讨论】:

【参考方案16】:

如果您只是剥离名称空间,并且想要在具有名称空间的类名中最后一个 \ 之后的任何内容(或者如果没有“\”,则只是名称),您可以执行以下操作:

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

基本上,它是正则表达式来获取字符或反斜杠的任意组合,直到最后一个反斜杠,然后只返回非反斜杠字符,直到字符串的末尾。添加 ?在第一个分组之后意味着如果模式匹配不存在,它只返回完整的字符串。

【讨论】:

【参考方案17】:

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()-detail

【讨论】:

欢迎来到 Stack Overflow。请为您的答案提供更多信息。这有什么作用以及如何使用它。 这在 Windows 上对我有用,但在 Linux 上却不行,可能是因为命名空间是 Windows 目录反斜杠 '\' 的形式,而 linux basename 考虑目录分隔符正斜杠 '/'。所以我用strtr解决了这个问题。基名(strtr($class,'\\','/'))【参考方案18】:

一个好的旧正则表达式似乎比前面显示的大多数方法更快:

// both of the below calls will output: ShortClassName

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

因此,即使您提供短类名或完全限定(规范)类名,这也有效。

正则表达式的作用是消耗所有先前的字符,直到找到最后一个分隔符(也被消耗)。所以剩下的字符串将是短类名。

如果您想使用不同的分隔符(例如 / ),则只需使用该分隔符即可。请记住在输入模式中转义反斜杠(即。\)以及模式字符(即。/)。

【讨论】:

【参考方案19】:

您可以使用explode 分隔命名空间,使用end 获取类名:

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

【讨论】:

【参考方案20】:

我知道这是一篇旧帖子,但这是我使用的 - 比上面发布的所有帖子都快,只需从您的课程中调用此方法,比使用反射快得多

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

【讨论】:

class_basename 不是标准的 php 函数,需要安装 laravel (illuminate/helpers) 才能使用。由于额外的包装要求,这种解决方案太重了。【参考方案22】:

我在PHP 7.2Ububntu 18.04 上找到的最快

preg_replace('/^(\w+\\\)*/', '', static::class)

【讨论】:

以上是关于如何获取对象的非限定(短)类名?的主要内容,如果未能解决你的问题,请参考以下文章

如果只有完全限定名称,如何获取 java 类的二进制名称?

如何在 Eclipse 中获取 Java 文件的完整类名?

类的反射机制(摘)

获取类的全限定类名(包名+类名)

从 Blade 中的别名类获取完全限定的类名

为啥即使从类内部获取成员函数指针值也需要类名限定?