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

Gre*_*bes 138 php namespaces class

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

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

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

If(get_class($object) == 'Name') {
... do this ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

lon*_*day 167

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

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

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

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

  • 我一般不喜欢在我的应用程序中做很多ReflectionClass voodoo,因为如果误用(受保护的方法变为公共等)会导致意外的结果.您可以在PHP魔术常量上使用简单的字符串替换:`str_replace(__ NAMESPACE__.'\\','',__ CLASS __);`.性能也快得多. (6认同)
  • 我不得不在前面添加一个斜杠,比如`$ reflect = new\ReflectionClass($ object);` (3认同)
  • @lonesomeday——只要说你的评论击中了我的一个烦恼就够了;开发人员对编程技术“说教”很简单,因为他们无法想象适当的用例。对我来说,这更多地说明了做出判断的人缺乏远见,而不是技术的不恰当。嘿,但别担心;绝大多数开发者都是这样认为的,所以你并不是独一无二的。 (3认同)
  • @FranklinPStrube 除非我遗漏了什么,否则会获取当前类的短名称,而不是对象的类。我同意使用反射通常意味着你做错了。 (2认同)
  • 许多人使用反射来覆盖成员可见性,这是​​不好的。不要那样做!但是,说一般使用 Reflections 是 Voodoo 和 Doing It Wrong 会给人们错误的印象。你不应该回避它们,你应该理解它们并知道它们什么时候是有益的以及在哪个抽象级别。 (2认同)

Hir*_*ter 119

(new \ReflectionClass($obj))->getShortName(); 是性能方面的最佳解决方案.

我很好奇哪个提供的解决方案是最快的,所以我已经进行了一些测试.

结果

Reflection: 1.967512512207 s ClassA
Basename:   2.6840535163879 s ClassA
Explode:    2.6507515668869 s ClassA
Run Code Online (Sandbox Code Playgroud)

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";
Run Code Online (Sandbox Code Playgroud)

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

  • 我想知道在你的测试中,在一个比A级的小对象更实质的对象上实例化ReflectionClass时,这个测试是否成立... (6认同)
  • 使用该代码运行PHP 10000次,您将获得更好的结果.以上可能会从某个池中获取反射,但这不是应用程序的常见行为.他们只需要一两次. (3认同)
  • 爆炸('\\', static::class)[0] ? 它不是返回名称空间的第一部分吗?应该返回最后一部分,而不是第一部分 (3认同)
  • 仅运行一次迭代而不是 100000 会得到截然不同的结果:反射:1.0967254638672 100000th/s ClassA Basename:0.81062316894531 100000th/s ClassA Explode:0.5061073080th/s ClassA08080 (2认同)

MaB*_*aBi 77

我在/sf/answers/1783094491/的测试中添加了substr ,这是我用i5测试(CentOS PHP 5.3.3,Ubuntu PHP 5.5.9)的紧固方式.

$classNameWithNamespace=get_class($this);
return substr($classNameWithNamespace, strrpos($classNameWithNamespace, '\\')+1);
Run Code Online (Sandbox Code Playgroud)

结果

Reflection: 0.068084406852722 s ClassA
Basename: 0.12301609516144 s ClassA
Explode: 0.14073524475098 s ClassA
Substring: 0.059865570068359 s ClassA 
Run Code Online (Sandbox Code Playgroud)

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";
Run Code Online (Sandbox Code Playgroud)

== ==更新

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

return substr(strrchr(get_class($this), '\\'), 1);
Run Code Online (Sandbox Code Playgroud)

以下是使用"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
Run Code Online (Sandbox Code Playgroud)

  • 仅仅因为我们列出了效率,我发现这是最快的,与此解决方案substr中提供的测试相比较(strrchr(get_class($ obj),'\\'),1); 反射:0.084223914146423 s ClassA - 基数:0.13206427097321 s ClassA - 爆炸:0.15331919193268 s ClassA - 子串:0.068068099021912 s ClassA - Strrchar:0.06472008228302 s ClassA - (5认同)
  • 警告:此代码不适用于全局名称空间中的类(即:其全名等于其短名)!我建议测试以下内容:`if($ pos = strrchr(static :: class,'\\')){..} else {...}`。 (2认同)
  • 为了使它也能在全局命名空间中工作,只需在类名前面加上一个反斜杠 :) - 即:`$classNameShort = substr(strrchr('\\' .get_class($this), '\\'), 1); ` (2认同)

spe*_*naz 22

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

<?php

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

// usage inside a class
// returns HelloWorld
$name = class_basename(__CLASS__);
Run Code Online (Sandbox Code Playgroud)

  • 我想他说的那样 (6认同)
  • 这不是内置的php函数,它看起来像laravel提供的辅助函数. (5认同)
  • 谢谢,我正在使用Laravel,这个答案为我节省了很多时间. (4认同)

小智 17

我用这个:

basename(str_replace('\\', '/', get_class($object)));
Run Code Online (Sandbox Code Playgroud)

  • 仅适用于Windows在Windows上,斜杠(/)和反斜杠(\)都用作目录分隔符.在其他环境中,它是正斜杠(/)http://php.net/manual/en/function.basename.php (13认同)

flo*_*ori 15

要将短名称作为单行(从PHP 5.4开始):

echo (new ReflectionClass($obj))->getShortName();
Run Code Online (Sandbox Code Playgroud)

这是一个干净的方法,合理的快速.


Xor*_*lse 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());
Run Code Online (Sandbox Code Playgroud)

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

  • 如果你要使用反正反射,使用$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%  |
+-----------------+--------+
Run Code Online (Sandbox Code Playgroud)


m13*_*13r 8

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

$ex = explode("\\", get_class($object));
$className = end($ex);
Run Code Online (Sandbox Code Playgroud)


小智 7

Yii方式

\yii\helpers\StringHelper::basename(get_class($model));
Run Code Online (Sandbox Code Playgroud)

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


Ozz*_*ech 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()));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

会有什么回报?

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
}
Run Code Online (Sandbox Code Playgroud)

扩展类名和命名空间适用于:

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
}
Run Code Online (Sandbox Code Playgroud)

全局命名空间中的类怎么样?

namespace {

    class ClassWithoutNamespace {
        use \Names;
    }

    echo ClassWithoutNamespace::getNamespace() . PHP_EOL; // empty string
    echo ClassWithoutNamespace::getBaseClassName() . PHP_EOL; // ClassWithoutNamespace
}
Run Code Online (Sandbox Code Playgroud)


Set*_*eth 5

我知道这是一篇旧文章,但这就是我使用的 - 比上面发布的所有内容更快,只需从您的类中调用此方法,比使用反射快得多

namespace Foo\Bar\Baz;

class Test {
    public function getClass() {
        return str_replace(__NAMESPACE__.'\\', '', static::class);
    }
}
Run Code Online (Sandbox Code Playgroud)