177

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

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

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

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

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

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

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

4

22 回答 22

217

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

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

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

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

于 2013-11-11T15:09:46.003 回答
165

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

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

于 2014-08-24T14:36:09.983 回答
97

我将 substr 添加到https://stackoverflow.com/a/25472778/2386943的测试中 ,这是我可以使用 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 秒):

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
于 2014-12-13T10:10:47.743 回答
38

如果你使用 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));
}
于 2016-11-03T09:41:43.527 回答
21

我用这个:

basename(str_replace('\\', '/', get_class($object)));
于 2014-01-09T08:58:51.480 回答
17

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

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

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

于 2014-08-14T12:49:31.873 回答
14

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

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

  • 如果您无论如何都要使用反射,那么 using$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%  |
+-----------------+--------+
于 2016-12-21T13:44:06.040 回答
8

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

$ex = explode("\\", get_class($object));
$className = end($ex);
于 2018-02-01T06:46:51.737 回答
7

易道

\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

于 2017-04-16T17:48:26.417 回答
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
}
于 2014-02-28T12:35:58.863 回答
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)
于 2016-09-21T12:54:50.363 回答
2

我知道这是一篇旧帖子,但这是我使用的 - 比上面发布的所有帖子都快,只需从你的班级调用这个方法,比使用反射快得多

namespace Foo\Bar\Baz;

class Test {
    public function getClass() {
        return str_replace(__NAMESPACE__.'\\', '', static::class);
    }
}
于 2019-01-22T22:59:19.727 回答
2

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

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

这可以通过get_class使用反斜杠前缀轻松解决:

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

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

于 2016-02-05T09:51:18.637 回答
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。

于 2016-05-31T16:08:20.347 回答
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

于 2013-11-11T08:33:10.173 回答
1

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

// both of the below calls will output: ShortClassName

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

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

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

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

于 2017-11-24T18:51:46.123 回答
0
$shortClassName = join('',array_slice(explode('\\', $longClassName), -1));
于 2014-09-12T21:36:07.770 回答
0

引用php.net:

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

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

<?php

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

注意:我做了一个基准测试,ReflectionClass使用basename+str_replace+get_class反射比使用 basename 方法快大约 20%,但是 YMMV。

于 2014-03-18T15:26:36.480 回答
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"

?>
于 2014-04-08T21:54:49.570 回答
0

我在这里找到的最快PHP 7.2Ububntu 18.04

preg_replace('/^(\w+\\\)*/', '', static::class)
于 2020-05-18T22:36:43.260 回答
0

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

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

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

于 2017-03-13T21:29:56.467 回答
0

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

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

甚至清楚

if(class_basename(ClassName::class) == 'ClassName') {
... do this ...
}
于 2019-10-11T13:10:23.393 回答