如何获得对象的非限定(短)类名?

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

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

由于 get _ class 返回完整的命名空间类,下面的代码不起作用。

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

名称空间 magic 关键字返回当前名称空间,如果测试对象具有另一个名称空间,则该名称空间没有用处。

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

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

147823 次浏览

Get _ class 的文档页面上找到的,是由 我在 nwhiting.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

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

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

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

然而,我无法想象在许多情况下这是可取的。如果要求对象是某个类的成员,则使用 instanceof进行测试。如果希望以更灵活的方式发出特定约束的信号,那么可以编写一个接口,并要求代码实现该接口。同样,正确的方法是使用 instanceof。(您可以使用 ReflectionClass来完成,但是它的性能会差得多。)

我用这个:

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

下面是 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
}

引用 php.net:

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

基于这些信息,并从 arzzzen 的答案中扩展出来,这个应该可以同时在 Windows 和 Nix * 系统上使用:

<?php


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

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

在任何环境下,最快、最简单的解决方案是:

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


?>

要获得作为一行程序的短名称(从 PHP 5.4开始) :

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

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

就性能而言,(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";

结果让我大吃一惊,我以为爆炸溶液是最快的方法。

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

我在 < a href = “ https://stackoverflow. com/a/25472778/2386943”> https://stackoverflow.com/a/25472778/2386943的测试中添加了 subr 这是我用 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";

= = 更新 = =

正如@MrBander候选人在评论中提到的那样,还有一种更快捷的方法可以做到这一点:

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

当类没有命名空间时,可能会得到意外的结果。也就是说,get_class返回 Foo,那么 $baseClass就是 oo

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

这可以很容易地通过在 get_class前面加上一个反斜杠来修复:

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

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

根据@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。

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

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

如果您使用的是 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));
}

我发现自己在一个独特的情况下,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%  |
+-----------------+--------+

如果您只是剥离名称空间,并希望在类名中的最后一个名称后面有任何东西带名称空间(或者只是名称,如果没有’) ,您可以这样做:

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

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

太好了

\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 -细节

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

// both of the below calls will output: ShortClassName


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

因此,即使您提供了一个简短的类名或者一个完全限定的(规范的)类名,这也是可行的。

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

如果您想使用不同的分隔符(例如/) ,那么只需使用该分隔符即可。记住避开反斜杠(即。)和模式字符(即。/)在输入模式。

可以使用 explode分隔名称空间,使用 end获得类名:

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

我知道这是一个老文章,但这是我使用-更快比所有上述张贴只要调用这个方法从您的类,很多比使用反射

namespace Foo\Bar\Baz;


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

因为“ RefectionClass”可以是版本,只需使用以下命令:

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

甚至都不清楚

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

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

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

只是我的小函数,从所有基准测试运行速度最快的 Mac 与 PHP 8.1.6。此函数处理提供的字符串为空或没有任何名称空间(内部不包含 \字符)的情况。

<?php


declare(strict_types=1);


use function strrpos;
use function substr;


class Utils {
/** @param class-string $class */
public static function classShortName(string $class): string
{
$pos = strrpos($class, '\\');


if ($pos === false) {
return $class;
}


return substr($class, $pos + 1);
}
}

基准测试(没有命名空间或 \的类) :

Reflection: 0.0082374811172485 s ClassA
Basename: 0.0071691036224365 s ClassA
Explode: 0.0077154636383057 s ClassA
Substring: 0.0076151371002197 s lassA // Doesn't work correctly
PregReplace: 0.0064111948013306 s lassA // Doesn't work correctly
Utils::classShortName: 0.0043857336044312 s ClassA

基准(完整名称空间) :

Reflection: 0.0093500852584839 s ClassA
Basename: 0.012896633148193 s ClassA
Explode: 0.013392925262451 s ClassA
Substring: 0.0083461999893188 s ClassA // almost same function runs slower ?!
PregReplace: 0.011274862289429 s ClassA
Utils::classShortName: 0.0075617074966431 s ClassA

给你:

public function get_name()
{
return str_replace(__NAMESPACE__ . '\\', '', __CLASS__);
}