Reputation: 2814
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
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
Reputation: 1488
Here you go:
public function get_name()
{
return str_replace(__NAMESPACE__ . '\\', '', __CLASS__);
}
Upvotes: 2
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
Reputation: 954
Fastest that I found here for PHP 7.2
on Ububntu 18.04
preg_replace('/^(\w+\\\)*/', '', static::class)
Upvotes: 4
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
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
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
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
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
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
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
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
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:
$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.'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
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
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
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
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
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
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
Reputation: 30617
$shortClassName = join('',array_slice(explode('\\', $longClassName), -1));
Upvotes: 0
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
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
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