Greg.Forbes
Greg.Forbes

Reputation: 2814

How do I get an object's unqualified (short) class name?

How do I check the class of an object within the PHP name spaced environment without specifying the full namespaced class.

For example suppose I had an object library/Entity/Contract/Name.

The following code does not work as get_class returns the full namespaced class.

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

The namespace magic keyword returns the current namespace, which is no use if the tested object has another namespace.

I could simply specify the full classname with namespaces, but this seems to lock in the structure of the code. Also not of much use if I wanted to change the namespace dynamically.

Can anyone think of an efficient way to do this. I guess one option is regex.

Upvotes: 210

Views: 184750

Answers (24)

spetsnaz
spetsnaz

Reputation: 1291

If you are using Laravel PHP framework you can use class_basename($full_name).

Here an example usage:

<?php

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

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

If you don't have Laravel, define this:

/**
 * Get the class "basename" of the given object / class.
 * Credits: laravel/framework/src/Illuminate/Support/helpers.php
 *
 * @param  string|object  $class
 * @return string
 */
function class_basename($class)
{
    $class = is_object($class) ? get_class($class) : $class;

    return basename(str_replace('\\', '/', $class));
}

Upvotes: 50

X 47 48 - IR
X 47 48 - IR

Reputation: 1488

Here you go:

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

Upvotes: 2

Aurimas Niekis
Aurimas Niekis

Reputation: 43

Just my small function which from all benchmarks runs fastest on my mac with PHP 8.1.6. This function handles situations when provided string is empty or doesn't have any namespace (doesn't contain \ character inside).

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

Benchmark (class without namespace or \):

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

Benchmark (full namespace):

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

Upvotes: 0

Vasilii Suricov
Vasilii Suricov

Reputation: 954

Fastest that I found here for PHP 7.2 on Ububntu 18.04

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

Upvotes: 4

Martin Tonev
Martin Tonev

Reputation: 775

Because "ReflectionClass" can be version depend just use the follow:

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

or even clear

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

Upvotes: -1

lonesomeday
lonesomeday

Reputation: 237905

You can do this with reflection. Specifically, you can use the ReflectionClass::getShortName method, which gets the name of the class without its namespace.

First, you need to build a ReflectionClass instance, and then call the getShortName method of that instance:

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

However, I can't imagine many circumstances where this would be desirable. If you want to require that the object is a member of a certain class, the way to test it is with instanceof. If you want a more flexible way to signal certain constraints, the way to do that is to write an interface and require that the code implement that interface. Again, the correct way to do this is with instanceof. (You can do it with ReflectionClass, but it would have much worse performance.)

Upvotes: 253

Hirnhamster
Hirnhamster

Reputation: 7399

(new \ReflectionClass($obj))->getShortName(); is the best solution with regards to performance.

I was curious which of the provided solutions is the fastest, so I've put together a little test.

Results

Reflection: 1.967512512207 s ClassA
Basename:   2.6840535163879 s ClassA
Explode:    2.6507515668869 s ClassA

Code

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

The results actually surprised me. I thought the explode solution would be the fastest way to go...

Upvotes: 209

Seth
Seth

Reputation: 121

I know this is an old post but this is what i use - Faster than all posted above just call this method from your class, a lot quicker than using Reflection

namespace Foo\Bar\Baz;

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

Upvotes: 5

mariovials
mariovials

Reputation: 786

Yii way

\yii\helpers\StringHelper::basename(get_class($model));

Yii uses this method in its Gii code generator

Method documentation

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

More information:

https://github.com/yiisoft/yii2/blob/master/framework/helpers/BaseStringHelper.php http://www.yiiframework.com/doc-2.0/yii-helpers-basestringhelper.html#basename()-detail

Upvotes: 8

m13r
m13r

Reputation: 2731

You can use explode for separating the namespace and end to get the class name:

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

Upvotes: 8

Eugen Mihailescu
Eugen Mihailescu

Reputation: 3711

A good old regex seems to be faster than the most of the previous shown methods:

// both of the below calls will output: ShortClassName

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

So this works even when you provide a short class name or a fully qualified (canonical) class name.

What the regex does is that it consumes all previous chars until the last separator is found (which is also consumed). So the remaining string will be the short class name.

If you want to use a different separator (eg. / ) then just use that separator instead. Remember to escape the backslash (ie. \) and also the pattern char (ie. /) in the input pattern.

Upvotes: 3

Scott
Scott

Reputation: 8075

If you're just stripping name spaces and want anything after the last \ in a class name with namespace (or just the name if there's no '\') you can do something like this:

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

Basically it's regex to get any combination of characters or backslashes up and until the last backslash then to return only the non-backslash characters up and until the end of the string. Adding the ? after the first grouping means if the pattern match doesn't exist, it just returns the full string.

Upvotes: 0

Xorifelse
Xorifelse

Reputation: 7911

I found myself in a unique situation where instanceof could not be used (specifically namespaced traits) and I needed the short name in the most efficient way possible so I've done a little benchmark of my own. It includes all the different methods & variations from the answers in this question.

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

A list of the of the entire result is here but here are the highlights:

  • If you're going to use reflection anyways, using $obj->getShortName() is the fastest method however; using reflection only to get the short name it is almost the slowest method.
  • 'strrpos' can return a wrong value if the object is not in a namespace so while 'safe strrpos' is a tiny bit slower I would say this is the winner.
  • To make 'basename' compatible between Linux and Windows you need to use str_replace() which makes this method the slowest of them all.

A simplified table of results, speed is measured compared to the slowest method:

+-----------------+--------+
| 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%  |
+-----------------+--------+

Upvotes: 15

onin
onin

Reputation: 5760

If you need to know the class name that was called from inside a class, and don't want the namespace, you can use this one

$calledClass = get_called_class();
$name = strpos($calledClass, '\\') === false ?
    $calledClass : substr($calledClass, strrpos($calledClass, '\\') + 1);

This is great when you have a method inside a class which is extended by other classes. Furthermore, this also works if namespaces aren't used at all.

Example:

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

Upvotes: 3

flori
flori

Reputation: 15857

To get the short name as an one-liner (since PHP 5.4):

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

It is a clean approach and reasonable fast.

Upvotes: 18

Tristan Jahier
Tristan Jahier

Reputation: 1147

Based on @MaBi 's answer, I made this:

trait ClassShortNameTrait
{
    public static function getClassShortName()
    {
        if ($pos = strrchr(static::class, '\\')) {
            return substr($pos, 1);
        } else {
            return static::class;
        }
    }
}

Which you may use like that:

namespace Foo\Bar\Baz;

class A
{
    use ClassShortNameTrait;
}

A::class returns Foo\Bar\Baz\A, but A::getClassShortName() returns A.

Works for PHP >= 5.5.

Upvotes: 2

OzzyCzech
OzzyCzech

Reputation: 10352

Here is simple solution for 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()));
        }
    }
}

What will be return?

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
}

Extended class name and namespace works well to:

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
}

What about class in global namespace?

namespace {

    class ClassWithoutNamespace {
        use \Names;
    }

    echo ClassWithoutNamespace::getNamespace() . PHP_EOL; // empty string
    echo ClassWithoutNamespace::getBaseClassName() . PHP_EOL; // ClassWithoutNamespace
}

Upvotes: 6

Tim
Tim

Reputation: 3188

You may get an unexpected result when the class doesn't have a namespace. I.e. get_class returns Foo, then $baseClass would be oo.

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

This can easily be fixed by prefixing get_class with a backslash:

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

Now also classes without a namespace will return the right value.

Upvotes: 3

MaBi
MaBi

Reputation: 1462

I added substr to the test of https://stackoverflow.com/a/25472778/2386943 and that's the fastet way I could test (CentOS PHP 5.3.3, Ubuntu PHP 5.5.9) both with an i5.

$classNameWithNamespace=get_class($this);
return substr($classNameWithNamespace, strrpos($classNameWithNamespace, '\\')+1);

Results

Reflection: 0.068084406852722 s ClassA
Basename: 0.12301609516144 s ClassA
Explode: 0.14073524475098 s ClassA
Substring: 0.059865570068359 s ClassA 

Code

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==

As mentioned in the comments by @MrBandersnatch there is even a faster way to do this:

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

Here are the updated test results with "SubstringStrChr" (saves up to about 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

Upvotes: 120

arzzzen
arzzzen

Reputation: 361

I use this:

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

Upvotes: 21

malhal
malhal

Reputation: 30617

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

Upvotes: 0

Fleshgrinder
Fleshgrinder

Reputation: 16273

The fastest and imho easiest solution that works in any environment is:

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

?>

Upvotes: 0

noisebleed
noisebleed

Reputation: 1309

Quoting php.net:

On Windows, both slash (/) and backslash () are used as directory separator character. In other environments, it is the forward slash (/).

Based on this info and expanding from arzzzen answer this should work on both Windows and Nix* systems:

<?php

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

Note: I did a benchmark of ReflectionClass against basename+str_replace+get_class and using reflection is roughly 20% faster than using the basename approach, but YMMV.

Upvotes: 0

GolezTrol
GolezTrol

Reputation: 116110

Found on the documentation page of get_class, where it was posted by me at 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];
}

But the idea of namespaces is to structure your code. That also means that you can have classes with the same name in multiple namespaces. So theoretically, the object you pass could have the name (stripped) class name, while still being a totally different object than you expect.

Besides that, you might want to check for a specific base class, in which case get_class doesn't do the trick at all. You might want to check out the operator instanceof.

Upvotes: 1

Related Questions