web-dev-qa-db-ja.com

オブジェクトの非修飾(短い)クラス名を取得するにはどうすればよいですか?

名前空間クラス全体を指定せずに、PHP名前空間環境内のオブジェクトのクラスを確認するにはどうすればよいですか。

たとえば、オブジェクトライブラリ/ Entity/Contract/Nameがあるとします。

Get_classが完全な名前空間付きクラスを返すため、次のコードは機能しません。

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

Namespace magicキーワードは、現在のネームスペースを返します。これは、テスト対象のオブジェクトに別のネームスペースがある場合は使用できません。

名前空間でクラス名全体を指定するだけでもかまいませんが、これはコードの構造を固定しているようです。また、名前空間を動的に変更したい場合はあまり役に立ちません。

誰もがこれを行うための効率的な方法を考えることができます。 1つのオプションは正規表現です。

131
Greg.Forbes

リフレクションを使用してこれを行うことができます。具体的には、名前空間なしでクラスの名前を取得する ReflectionClass::getShortName メソッドを使用できます。

まず、ReflectionClassインスタンスを構築し、そのインスタンスのgetShortNameメソッドを呼び出す必要があります。

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

ただし、これが望ましい多くの状況は想像できません。オブジェクトが特定のクラスのメンバーであることを要求する場合、テストする方法は instanceof です。特定の制約をより柔軟に通知する方法が必要な場合は、インターフェイスを記述し、そのインターフェイスをコードで実装する必要があります。繰り返しますが、これを行う正しい方法はinstanceofを使用することです。 (ReflectionClassを使用して実行できますが、パフォーマンスが大幅に低下します。)

160
lonesomeday

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

結果は実際に私を驚かせました。爆発的な解決策が最も速い方法だと思いました...

116
Hirnhamster

https://stackoverflow.com/a/25472778/238694 のテストにsubstrを追加しました。これがテストできる最速の方法です(CentOS PHP 5.3.3、Ubuntu PHP 5.5.9)両方ともi5。

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

== UPDATE ==

@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
75
MaBi

Laravel PHPフレームワークを使用している場合、これを行うより簡単な方法を次に示します。

<?php

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

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

私はこれを使用します:

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

短い名前をワンライナーとして取得するには( PHP 5.4 ):

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

これはクリーンなアプローチであり、 合理的な高速 です。

14
flori

instanceofを使用できず(特に名前空間の特性)、 needed 可能な限り最も効率的な方法で短縮名を使用できるというユニークな状況に陥ったので、少しベンチマークを行いました。私自身。この質問の回答からのすべての異なる方法とバリエーションが含まれています。

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

結果全体のリストは here ですが、ここにハイライトがあります:

  • Ifとにかくリフレクションを使用する場合、$obj->getShortName()を使用するのが最速の方法です however ;短い名前を取得するためにリフレクションonlyを使用することは、ほぼ最も遅い方法です。
  • 'strrpos'は、オブジェクトが名前空間にない場合、間違った値を返す可能性があるため、'safe strrpos'のほうが少し遅いですが、これが勝者です。
  • LinuxとWindowsの間で'basename'の互換性を確保するには、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%  |
+-----------------+--------+
11
Xorifelse

名前空間を分離するためにexplodeを使用し、クラス名を取得するためにendを使用できます。

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

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
}
6
OzzyCzech

Yii way

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

YiiはGiiコードジェネレーターでこのメソッドを使用します

メソッドのドキュメント

このメソッドは、PHP関数basename()に似ていますが、オペレーティングシステムに関係なく、\と/の両方をディレクトリ区切り文字として処理する点が異なります。このメソッドは、主にphp名前空間で動作するように作成されました。実際のファイルパスを使用する場合、phpのbasename()は正常に機能するはずです。注:このメソッドは、実際のファイルシステム、または「..」などのパスコンポーネントを認識しません。

詳しくは:

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

6
mariovials

クラス内から呼び出されたクラス名を知る必要があり、名前空間が必要ない場合は、これを使用できます

$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)
3
Nino Škopac

@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::classFoo\Bar\Baz\Aを返しますが、A::getClassShortName()Aを返します。

PHP> = 5.5で動作します。

2
Tristan Jahier

get_classのドキュメントページ にあり、そこでは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];
}

しかし、名前空間の考え方は、コードを構造化することです。また、複数の名前空間に同じ名前のクラスを作成できることも意味します。そのため、理論的には、渡すオブジェクトの名前が(削除された)クラス名であっても、予想とはまったく異なるオブジェクトである可能性があります。

それに加えて、特定のbase classをチェックすることもできます。この場合、get_classはまったくトリックを行いません。演算子 instanceof をチェックアウトすることをお勧めします。

1
GolezTrol

クラスに名前空間がない場合、予期しない結果が生じる可能性があります。つまりget_classFooを返し、$baseClassooになります。

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

これは、get_classの前にバックスラッシュを付けることで簡単に修正できます。

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

また、名前空間のないクラスも正しい値を返します。

1
Tim

名前空間を削除して、名前空間を持つクラス名の最後の\の後(または '\'がない場合は名前だけ)が必要な場合は、次のようにできます。

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

基本的に、文字またはバックスラッシュの任意の組み合わせを最後のバックスラッシュまで取得し、その後、バックスラッシュではない文字のみを文字列の最後まで返すのは正規表現です。 ?の追加最初のグループ化は、パターンマッチが存在しない場合、完全な文字列を返すことを意味します。

0
Scott

Php.netの引用:

Windowsでは、スラッシュ(/)とバックスラッシュ()の両方がディレクトリ区切り文字として使用されます。他の環境では、スラッシュ(/)です。

この情報に基づいて、arzzzenの回答から展開すると、これはWindowsシステムとNix *システムの両方で動作するはずです。

<?php

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

注:ReflectionClassのベンチマークをbasename+str_replace+get_classに対して行いました。リフレクションの使用は、ベース名アプローチを使用するよりも約20%高速ですが、YMMVです。

0
noisebleed

どの環境でも動作する、最も速くて簡単なソリューションは次のとおりです。

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

?>
0
Fleshgrinder

私はこれが古い投稿であることを知っていますが、これは私が使用するものです-上記の投稿よりも速く、クラスからこのメソッドを呼び出すだけで、Reflectionを使用するよりもはるかに速くなります

namespace Foo\Bar\Baz;

class Test {
    public function getClass() {
        return str_replace(__NAMESPACE__.'\\', '', static::class);
    }
}
0
Seth
$shortClassName = join('',array_slice(explode('\\', $longClassName), -1));
0
malhal

古き良き正規表現は、前に示したほとんどの方法よりも高速であるようです。

// both of the below calls will output: ShortClassName

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

そのため、短いクラス名または完全修飾(正規)クラス名を指定した場合でも機能します。

正規表現が行うことは、最後のセパレータが見つかるまですべての以前の文字を消費することです(これも消費されます)。したがって、残りの文字列は短いクラス名になります。

別のセパレーター(たとえば、/)を使用する場合は、代わりにそのセパレーターを使用します。入力パターンのバックスラッシュ(つまり\)とパターン文字(つまり/)も必ずエスケープしてください。

0