我如何得到一个对象的不合格(短)类名?

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

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

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

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

命名空间magic关键字返回当前的命名空间,如果被testing的对象有另一个命名空间,这是没用的。

我可以简单地用命名空间来指定完整的类名,但是这似乎locking了代码的结构。 如果我想dynamic改变命名空间,也没什么用处。

任何人都可以想到一个有效的方法来做到这一点。 我猜一个选项是正则expression式。

你可以用reflection来做到这一点。 具体而言,您可以使用ReflectionClass::getShortName方法,该方法获取不带名称空间的类的名称。

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

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

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

(new \ReflectionClass($obj))->getShortName(); 是关于性能的最佳解决scheme。

我很好奇哪个提供的解决scheme是最快的,所以我做了一个小testing。

结果

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

结果竟然让我吃了一惊。 我认为爆炸解决scheme将是最快的方法去…

我添加了substr到https://stackoverflow.com/a/25472778/2386943的testing,这是我可以testing(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); 

这里是更新的testing结果与“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+的简单解决scheme

 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框架,这是一个更简单的方法:

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

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

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

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(; )-详情

基于@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 ,无法使用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()); 

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

  • 如果你打算使用reflection,使用$obj->getShortName()是最快的方法; 使用reflection只是为了得到短名称,它几乎是最慢的方法。
  • 如果对象不在命名空间中, 'strrpos'可以返回一个错误的值,所以'safe strrpos'稍微慢一些,我会说这是胜利者。
  • 为了使Linux和Windows之间的'basename'兼容,你需要使用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的文档页面find,它是由我在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的基准testing, ReflectionClass速度比使用基本名称方法快了20%,但是YMMV。

在任何环境下工作的最快和最简单的解决scheme是:

 <?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)); 

基本上这是正则expression式得到字符或反斜杠的任何组合,直到最后一个反斜杠,然后只返回非反斜杠字符,直到string的结尾。 添加? 第一次分组之后意味着如果模式匹配不存在,则返回完整的string。