如何获得对象的不合格(短)类名?


153

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

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

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

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

namespace magic关键字返回当前的名称空间,如果被测试的对象具有另一个名称空间,则该名称空间将无效。

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

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


这似乎毫无意义,因为不同的名称空间可能在其中定义了相同的类名,那么您将如何处理呢?那是因为在您的示例中返回了完整的合格班级名称
Alma Do

我在移动设备上,所以我不能提出一个像样的答案,但解决的办法是反射,特别是ReflectionClass :: getShortName - php.net/manual/en/reflectionclass.getshortname.php
lonesomeday

对于正在寻找理由的人:在通用基类的辅助函数中它可能很有用(即,在这种情况下,多个名称空间绝不是问题)。
达伦·库克

Answers:


182

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

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

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

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


1
@ Greg.Forbes,因为Tenant当前名称空间中不存在。试试吧var_dump($tenant instanceof \Library\Entity\People\Tenant)。另外,研究如何使用use运算符,以及PHP名称空间背后的一般概念!
lonesomeday

3
我必须像这样在前面加一个斜线$reflect = new \ReflectionClass($object);
prograhammer

7
我通常不喜欢在我的应用程序中执行很多ReflectionClass伏都教,因为如果使用不当(受保护的方法公开等),它可能导致意外的结果。您可以使用PHP魔术常量简单的字符串替换,而不是:str_replace(__NAMESPACE__ . '\\', '', __CLASS__);。从性能角度来看,它也更快。
富兰克林·P·斯特鲁伯

2
@FranklinPStrube除非我缺少什么,否则它将获得当前类的简称,而不是对象的类。我同意反射的使用通常意味着您做错了。
lonesomeday

1
许多人使用“反射”作为成员可见性替代,即BAD。不要那样做!但是说一般使用反射是伏都教(Voodoo)和“做错了”给人以错误的印象。您不应该避免使用它们,应该了解它们,并知道它们何时是有益的,以及在哪个抽象级别上。
Vanja D.

131

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

结果实际上使我感到惊讶。我认为爆炸解决方案将是最快的方法。


1
好答案。我运行的是完全相同的代码,但结果却不同(Macbook Pro i7,16 GB内存)。反射率:0.382,基本名:0.380,爆炸:0.399。我认为这取决于您的系统,什么才是最好的...
Tobias Nyholm 2014年

4
使用该代码运行PHP 10 000次,您将获得更好的结果。上面的内容可能会从某个池中获取反射,但这不是那里的应用程序的通常行为。他们只需要一次或两次。
LeMike 2014年

6
我想知道在您的测试中在比A类的小对象更重要的对象上实例化ReflectionClass时,此测试是否成立?
Joe Green

2
仅运行一次迭代而不是100000会得到非常不同的结果:反射:1.0967254638672 100000th / s ClassA基本名称:0.81062316894531 100000th / s ClassA爆炸:0.50067901611328 100000th / s ClassA
mcmurphy

1
爆炸('\\',static :: class)[0]吗?它不返回名称空间的第一部分吗?应该返回最后一部分,而不是返回第一部分
2oppin,

86

我在https://stackoverflow.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";

==更新==

如@MrBandersnatch的评论中所述,甚至还有一种更快的方法:

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

以下是使用“ SubstringStrChr”更新的测试结果(最多可节省0.001 s):

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

5
仅仅因为我们列出了效率,我发现这是最快的,与解决方案中提供的测试进行了比较substr(strrchr(get_class($ obj),'\\'),1); 反思:0.084223914146423小号ClassA的-基名:0.13206427097321小号ClassA的-爆炸:0.15331919193268小号ClassA的-字符串:0.068068099021912小号ClassA的- Strrchar:0.06472008228302小号ClassA的-
ctatro85

我刚遇到这个线程,并添加了一个额外的基准进行测试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
富兰克林·P·斯特鲁布

1
@MrBandersnatch你是正确的。我测试了您的解决方案,它为我节省了大约0.001 s。我用您的答案更新了答案!
MaBi 2015年

3
警告:此代码不适用于全局名称空间中的类(即:其全名等于其短名)!我建议您测试以下内容:if ($pos = strrchr(static::class, '\\')) { .. } else { ... }
Tristan Jahier '16

1
为了使其也可以在全局名称空间中工作,只需在类名前加上反斜杠:)-即:$classNameShort = substr(strrchr('\\' . get_class($this), '\\'), 1);
rosell.dk

25

如果您使用Laravel PHP框架,这是一种更简单的方法:

<?php

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

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

8
这不是内置的php函数,它看起来像laravel提供的帮助程序函数。
史蒂夫·布佐纳斯

6
我认为他是说
Scott

4
谢谢,我正在使用Laravel,这个答案为我节省了很多时间。
Jeremy Wadhams


18

我用这个:

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

您也可以尝试:$ className = explode('\\',basename(get_class($ this))); $ className = array_pop($ className); 获得普通的类名。或使用substr。
dompie 2014年

13
仅在Windows上有效在Windows上,斜杠(/)和反斜杠()均用作目录分隔符。在其他环境中,它是斜杠( /)php.net/manual/en/function.basename.php
OzzyCzech 2014年

我已经修复了。谢谢@OzzyCzech。
Theodore R. Smith

1
@OzzyCzech我刚从Windows迁移到Ubuntu时遇到了这个问题……令人发疯。使用了MaBi更新中提到的解决方案。
克里斯·贝克

@OzzyCzech为什么只能在Windows上运行?问题是关于完全限定的名称空间名称,如果几年前我也没错,并且名称空间不是特定于操作系统的,并且总是带有反斜杠(例如Windows目录分隔符)。
FantomX1


12

我发现自己处于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()是最快的方法。然而使用反射来获得简称,这几乎是最慢的方法。
  • '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);

7

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


欢迎使用堆栈溢出。请提供更多信息以供您回答。这是做什么用的,以及如何使用它。
Jens

1
这在Windows上对我有用,但在Linux上却不行,可能是因为名称空间以Windows目录反斜杠'\'的形式出现,而linux basename将目录分隔符视为正斜杠'/'。所以我用strtr解决了这个问题。basename(strtr($ class,'\\','/'))
FantomX1

6

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

3

如果您需要知道从类内部调用的类名称,并且不需要名称空间,则可以使用此名称空间

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

2

基于@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。


2

我知道这是旧文章,但这是我使用的-比上面所有文章都快,只从您的类中调用此方法,比使用Reflection快得多

namespace Foo\Bar\Baz;

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

不幸的是,仅当您在所需名称的类中调用它时才有效,而不仅仅是在任何类名上作为字符串调用。
jurchiks

1

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


1

当类没有名称空间时,您可能会得到意外的结果。即get_class返回Foo,那么$baseClass将会是oo

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

可以通过在前面get_class加上反斜杠来解决此问题:

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

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


1

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

// both of the below calls will output: ShortClassName

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

因此,即使您提供了简短的类名或完全限定的(规范)类名,此方法也可以使用。

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

如果要使用其他分隔符(例如/),则只需使用该分隔符即可。请记住在输入模式中转义反斜杠(即\)以及模式char(即/)。


1

因为“ ReflectionClass”可以是版本依赖的,所以只需使用以下命令:

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

甚至清晰

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

0

引用php.net:

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

根据此信息并从arzzzen答案扩展此方法,该方法应在Windows和Nix *系统上均有效:

<?php

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

注:我做了一个标杆ReflectionClass反对basename+str_replace+get_class和使用反射大约20%的速度比使用基本名称的方法,但情况因人而异。


0

在任何环境中都能运行的最快,最简单的解决方案是:

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

?>

1
这就是为什么我希望PHP具有内部类信息运算符。实例化一个外部反射器来完成应该$Object->__class->getShortName()让我非常恼火的PHP。您的方法行得通,但是现在您将具体方法放到类中只是为了暴露应该是什么语言构造。
AgmLauncher 2014年

没有“具体的”(或者我们应该称它们为过程式)功能的PHP是不可能的。让我们等待PHP 6(好吧,如果有的话)。
Fleshgrinder

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

0

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

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

基本上,它是正则表达式,用于获取字符或反斜杠的任何组合,直到最后一个反斜杠,然后仅返回非反斜杠的字符直至字符串的末尾。加?第一个分组表示如果不存在模式匹配项后,它将仅返回完整的字符串。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.