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

时间:2013-11-11 08:28:23

标签: php class namespaces

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

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

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

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

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

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

任何人都可以想到一种有效的方法。我猜一个选项是正则表达式。

22 个答案:

答案 0 :(得分:160)

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

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

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

然而,我无法想象很多情况下这是可取的。如果您想要求对象是某个类的成员,那么测试它的方法是使用instanceof。如果您希望以更灵活的方式发出某些约束信号,那么执行此操作的方法是编写接口并要求代码实现该接口。同样,正确的方法是使用instanceof。 (您可以使用ReflectionClass执行此操作,但效果会更差。)

答案 1 :(得分:116)

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

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

答案 2 :(得分:75)

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

<强> == UPDATE ==

正如@MrBandersnatch的评论中提到的,甚至有更快的方法:

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

以下是使用&#34; SubstringStrChr&#34;更新的测试结果。 (节省高达约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

答案 3 :(得分:18)

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

<?php

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

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

答案 4 :(得分:16)

我用这个:

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

答案 5 :(得分:14)

将短名称作为单行(自PHP 5.4起):

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

这是一种干净的方法reasonable fast

答案 6 :(得分:11)

我发现自己处于一种独特的情况,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->getShortName()是最快的方法但是;使用反射来获取短名称,这几乎是最慢的方法。
  • 如果对象不在命名空间中,
  • '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%  |
+-----------------+--------+

答案 7 :(得分: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
}

答案 8 :(得分:6)

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

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

答案 9 :(得分:6)

Yii way

\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

答案 10 :(得分: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)

答案 11 :(得分: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&gt; = 5.5。

答案 12 :(得分:1)

documentation page of get_class上找到, me在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

答案 13 :(得分:1)

当类没有命名空间时,您可能会收到意外结果。即get_class返回Foo,然后$baseClassoo

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

这可以通过在get_class前加一个反斜杠来轻松修复:

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

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

答案 14 :(得分:0)

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

答案 15 :(得分:0)

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

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

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

答案 16 :(得分: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"

?>

答案 17 :(得分:0)

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

// both of the below calls will output: ShortClassName

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

所以即使你提供一个简短的类名或一个完全限定的(规范的)类名,这也是有效的。

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

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

答案 18 :(得分: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%,但YMMV。

答案 19 :(得分:0)

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

link-template.php

答案 20 :(得分:0)

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

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

甚至清除

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

答案 21 :(得分:0)

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

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