>= 5.5
::class
完全限定类名,而不是 get_class
<?php
namespace my\library\mvc;
class Dispatcher {}
print Dispatcher::class; // FQN == my\library\mvc\Dispatcher
$disp = new Dispatcher;
print $disp::class; // 解析错误
(PHP 4, PHP 5, PHP 7, PHP 8)
get_class — 返回对象的类名
返回 object
是哪个类的实例的名称。
如果 object
是一个存在于命名空间中的类的实例,则返回该类的限定命名空间名称。
如果 get_class() 被调用且参数不是对象,则会抛出 TypeError。在 PHP 8.0.0 之前,会抛出 E_WARNING
级别的错误。
如果 get_class() 在类外部被调用且没有参数,则会抛出 Error。在 PHP 8.0.0 之前,会抛出 E_WARNING
级别的错误。
版本 | 描述 |
---|---|
8.3.0 | 调用 get_class() 且不带参数现在会发出 E_DEPRECATED 警告;之前,在类内部调用此函数会返回该类的名称。 |
8.0.0 | 在类外部调用此函数且不带任何参数,现在会抛出 Error。之前,会抛出 E_WARNING ,并且函数会返回 false 。 |
7.2.0 | 在此版本之前,object 的默认值为 null ,其效果与不传递任何值相同。现在,null 已从 object 的默认值中移除,并且不再是有效的输入。 |
范例 #1 使用 get_class()
<?php
class foo {
function name()
{
echo "My name is " , get_class($this) , "\n";
}
}
// 创建一个对象
$bar = new foo();
// 外部调用
echo "Its name is " , get_class($bar) , "\n";
// 内部调用
$bar->name();
?>
上面的例子会输出
Its name is foo My name is foo
范例 #2 在超类中使用 get_class()
<?php
abstract class bar {
public function __construct()
{
var_dump(get_class($this));
var_dump(get_class());
}
}
class foo extends bar {
}
new foo;
?>
上面的例子会输出
string(3) "foo" string(3) "bar"
范例 #3 使用带命名空间类的 get_class()
<?php
namespace Foo\Bar;
class Baz {
public function __construct()
{
}
}
$baz = new \Foo\Bar\Baz;
var_dump(get_class($baz));
?>
上面的例子会输出
string(11) "Foo\Bar\Baz"
>= 5.5
::class
完全限定类名,而不是 get_class
<?php
namespace my\library\mvc;
class Dispatcher {}
print Dispatcher::class; // FQN == my\library\mvc\Dispatcher
$disp = new Dispatcher;
print $disp::class; // 解析错误
许多人在其他评论中希望获取没有命名空间的类名。一些奇怪的代码建议来做到这一点 - 不是我想要的!所以我想添加我自己的方法。
<?php
function get_class_name($classname)
{
if ($pos = strrpos($classname, '\\')) return substr($classname, $pos + 1);
return $pos;
}
?>
我还做了一些快速基准测试,发现 strrpos() 也是最快的。微优化 = 宏优化!
39.0954 毫秒 - preg_match()
28.6305 毫秒 - explode() + end()
20.3314 毫秒 - strrpos()
(为了参考,以下是使用的调试代码。c() 是一个基准测试函数,它运行每个闭包 10,000 次。)
<?php
c(
function($class = 'a\b\C') {
if (preg_match('/\\\\([\w]+)$/', $class, $matches)) return $matches[1];
return $class;
},
function($class = 'a\b\C') {
$bits = explode('\\', $class);
return end($bits);
},
function($class = 'a\b\C') {
if ($pos = strrpos($class, '\\')) return substr($class, $pos + 1);
return $pos;
}
);
?>
人们似乎混淆了 __METHOD__、get_class($obj) 和 get_class() 在类继承方面的作用。
以下是一个很好的示例,应该能永远解决这个问题。
<?php
class Foo {
function doMethod(){
echo __METHOD__ . "\n";
}
function doGetClassThis(){
echo get_class($this).'::doThat' . "\n";
}
function doGetClass(){
echo get_class().'::doThat' . "\n";
}
}
class Bar extends Foo {
}
class Quux extends Bar {
function doMethod(){
echo __METHOD__ . "\n";
}
function doGetClassThis(){
echo get_class($this).'::doThat' . "\n";
}
function doGetClass(){
echo get_class().'::doThat' . "\n";
}
}
$foo = new Foo();
$bar = new Bar();
$quux = new Quux();
echo "\n--doMethod--\n";
$foo->doMethod();
$bar->doMethod();
$quux->doMethod();
echo "\n--doGetClassThis--\n";
$foo->doGetClassThis();
$bar->doGetClassThis();
$quux->doGetClassThis();
echo "\n--doGetClass--\n";
$foo->doGetClass();
$bar->doGetClass();
$quux->doGetClass();
?>
输出
--doMethod--
Foo::doMethod
Foo::doMethod
Quux::doMethod
--doGetClassThis--
Foo::doThat
Bar::doThat
Quux::doThat
--doGetClass--
Foo::doThat
Foo::doThat
Quux::doThat
如果您使用命名空间,此函数将返回包括命名空间在内的类名,因此请注意您的代码是否对类名进行任何检查。 例如
namespace Shop;
<?php
class Foo
{
public function __construct()
{
echo "Foo";
}
}
// 不同的文件
include('inc/Shop.class.php');
$test = new Shop\Foo();
echo get_class($test);// 返回 Shop\Foo
?>
如果您希望得到文件路径,并且您的文件结构如下:
project -> system -> libs -> controller.php
project -> system -> modules -> foo -> foo.php
并且 foo.php 中的 foo() 继承了 controller.php 中的 controller(),如下所示:
<?PHP
namespace system\modules\foo;
class foo extends \system\libs\controller {
public function __construct() {
parent::__construct();
}
}
?>
如果您想在 controller() 中知道 foo.php 的路径,这可能会有所帮助
<?PHP
namespace system\libs;
class controller {
protected function __construct() {
$this->getChildPath();
}
protected function getChildPath() {
echo dirname(get_class($this));
}
}
?>
<?PHP
$f = new foo(); // system\modules\foo
?>
获取不带命名空间的类名的最简单方法
<?php
namespace a\b\c\d\e\f;
class Foo {
public function __toString() {
$class = explode('\\', __CLASS__);
return end($class);
}
}
echo new Foo(); // 打印 Foo
?>
需要一种快速解析命名空间类名的方法?试试这个
<?php
namespace Engine;
function parse_classname ($name)
{
return array(
'namespace' => array_slice(explode('\\', $name), 0, -1),
'classname' => join('', array_slice(explode('\\', $name), -1)),
);
}
final class Kernel
{
final public function __construct ()
{
echo '<pre>', print_r(parse_classname(__CLASS__),1), '</pre>';
// 或者使用此方法来获取类名,只写一行代码:
// echo join('', array_slice(explode('\\', __CLASS__), -1));
}
}
new Kernel();
?>
输出
Array
(
[namespace] => Array
(
[0] => Engine
)
[classname] => Kernel
)
在 Perl(以及其他一些语言)中,您可以以对象和类(又名静态)两种方式调用某些方法。 我在我的一个 PHP5 类中创建了一个这样的方法,但发现 PHP5 中的静态方法不知道调用子类的名称,因此我使用回溯来确定它。 我不喜欢这种 hack 方法,但只要 PHP 没有其他选择,就必须这样做
public function table_name() {
$result = null;
if (isset($this)) { // 对象上下文
$result = get_class($this);
}
else { // 类上下文
$result = get_class();
$trace = debug_backtrace();
foreach ($trace as &$frame) {
if (!isset($frame['class'])) {
break;
}
if ($frame['class'] != $result) {
if (!is_subclass_of($frame['class'], $result)) {
break;
}
$result = $frame['class'];
}
}
}
return $result;
}
/**
* 获取不带命名空间的对象类名
*/
function get_real_class($obj) {
$classname = get_class($obj);
if (preg_match('@\\\\([\w]+)$@', $classname, $matches)) {
$classname = $matches[1];
}
return $classname;
}
我之前评论中的代码并不完全正确。我认为这个才是正确的。
<?
abstract class Singleton {
protected static $__CLASS__ = __CLASS__;
protected function __construct() {
}
abstract protected function init();
/**
* 获取此单例的实例。 如果不存在实例,则创建一个新实例并返回。
* 如果存在实例,则返回现有实例。
*/
public static function getInstance() {
static $instance;
$class = self::getClass();
if ($instance === null) {
$instance = new $class();
$instance->init();
}
return $instance;
}
/**
* 返回扩展此类的子类的类名
*
* @return string 类名
*/
private static function getClass() {
$implementing_class = static::$__CLASS__;
$original_class = __CLASS__;
if ($implementing_class === $original_class) {
die("您必须在您的 Singleton 类中提供一个 <code>protected static \$__CLASS__ = __CLASS__;</code> 语句!");
}
return $implementing_class;
}
}
?>
关于从命名空间类名获取类名,然后使用 basename(),它似乎非常有效。
<?php
namespace Foo\Bar;
abstract class Baz
{
public function report()
{
echo
'__CLASS__ ', __CLASS__, ' ', basename(__CLASS__), PHP_EOL,
'get_called_class ', get_called_class(), ' ', basename(get_called_class()), PHP_EOL;
}
}
class Snafu extends Baz
{
}
(new Snafu)->report();
?>
产生以下输出...
__CLASS__ Foo\Bar\Baz Baz
get_called_class Foo\Bar\Snafu Snafu
虽然您可以从类的实例中调用类的静态方法,就好像它们是对象实例方法一样,但很高兴知道,由于类在 PHP 代码中由其名称作为字符串表示,所以 get_class() 的返回值也是如此。
<?php
$t->Faculty();
SomeClass::Faculty(); // $t instanceof SomeClass
"SomeClass"::Faculty();
get_class($t)::Faculty();
?>
第一个是合法的,但最后一个清楚地表明了 Faculty() 是一个静态方法(因为方法的名称肯定不是)。
好吧,如果您在别名类上调用 get_class(),您将获得原始类名。
<?php
class Person {}
class_alias('Person', 'User');
$me = new User;
var_dump( get_class($me) ); // 'Person'
?>
下面有关于如何创建允许子类化的单例的讨论。似乎使用 get_called_class,现在有一个比下面讨论的更简洁的解决方案,不需要为每个子类覆盖方法。
例如:
<?php
abstract class MySuperclass {
static private $instances = array();
static public function getInstance(): ACFBlock {
$className = get_called_class();
if (!isset(self::$instances[$className])) {
self::$instances[$className] = new static();
}
return self::$instances[$className];
}
private function __construct() {
// Singleton
}
}
class MySubclass extends MySuperclass {
}
MySubclass::getInstance();
<?php
class Parent{
}
class Child extends Parent{
}
$c = new Child();
echo get_class($c) //Child
?>
<?php
class Parent{
public function getClass(){
echo get_class();
}
}
class Child extends Parent{
}
$obj = new Child();
$obj->getClass(); //outputs Parent
?>
<?php
class Parent{
public function getClass(){
echo get_class($this);
}
}
class Child extends Parent{
}
$obj = new Child();
$obj->getClass(); // Parent
?>
此注释是对 davidc at php dot net 早期帖子的回复。不幸的是,为从静态方法中获取类名而发布的解决方案不适用于继承的类。
观察以下内容
<?php
class BooBoof {
public static function getclass() {
return __CLASS__;
}
public function retrieve_class() {
return get_class($this);
}
}
class CooCoof extends BooBoof {
}
echo CooCoof::getclass();
// outputs BooBoof
$coocoof = new CooCoof;
echo $coocoof->retrieve_class();
// outputs CooCoof
?>
__CLASS__ 和 get_class($this) 在继承类中使用方式不同。到目前为止,我无法确定从静态方法中获取实际类名的可靠方法。
尝试了此页面上描述的各种单例基类方法,我创建了一个基类和桥接函数,允许它在没有 get_called_class() 的情况下工作,如果它不可用。与这里列出的其他方法不同,我选择不阻止使用 __construct() 或 __clone()。
<?php
抽象类 Singleton {
受保护的静态 $m_pInstance;
最终的公共静态函数 getInstance(){
$class = 静态::getClass();
如果(!isset(静态::$m_pInstance[$class])) {
静态::$m_pInstance[$class] = 新的 $class;
}
返回静态::$m_pInstance[$class];
}
最终的公共静态函数 getClass(){
返回 get_called_class();
}
}
// 我不记得我在哪里找到了这个,但这是为了让 php < 5.3 使用这种方法。
如果 (!function_exists('get_called_class')) {
函数 get_called_class($bt = false, $l = 1) {
如果 (!$bt)
$bt = debug_backtrace();
如果 (!isset($bt[$l]))
抛出新的 Exception("无法找到调用类 -> 堆栈级别过深。");
如果 (!isset($bt[$l]['type'])) {
抛出新的 Exception('type 未设置');
}
否则
switch ($bt[$l]['type']) {
case ':':
$lines = file($bt[$l]['file']);
$i = 0;
$callerLine = '';
do {
$i++;
$callerLine = $lines[$bt[$l]['line'] - $i] . $callerLine;
} while (stripos($callerLine, $bt[$l]['function']) === false);
preg_match('/([a-zA-Z0-9\_]+)::' . $bt[$l]['function'] . '/', $callerLine, $matches);
如果 (!isset($matches[1])) {
// 必须是一个边缘情况。
抛出新的 Exception("找不到调用类: 原始方法调用被遮蔽。");
}
switch ($matches[1]) {
case 'self':
case 'parent':
返回 get_called_class($bt, $l + 1);
默认:
返回 $matches[1];
}
// 不会到这里。
case '->': switch ($bt[$l]['function']) {
case '__get':
// 边缘情况 -> 获取调用对象的类
如果 (!is_object($bt[$l]['object']))
抛出新的 Exception("边缘情况失败。__get 在非对象上调用。");
返回 get_class($bt[$l]['object']);
默认: 返回 $bt[$l]['class'];
}
默认: 抛出新的 Exception("未知的回溯方法类型");
}
}
}
class B 扩展 Singleton {
}
class C 扩展 Singleton {
}
$b = B::getInstance();
echo 'class: '.get_class($b);
echo '<br />';
$c = C::getInstance();
echo echo 'class: '.get_class($c);
?>
这将返回
class: b
class: c
用于拉取带有命名空间前缀的类的名称的方法。
<?php
/**
* 使用 get_class 返回类的名称,命名空间已去除。
* 这在类作用域内不起作用,因为 get_class() 是解决方法,
* 正在使用 get_class_name(get_class());
*
* @param object|string $object 要检索名称的对象或类名称
* @return string 带有去除命名空间的类的名称
*/
函数 get_class_name($object = null)
{
如果 (!is_object($object) && !is_string($object)) {
返回 false;
}
$class = explode('\\', (is_string($object) ? $object : get_class($object)));
返回 $class[count($class) - 1];
}
?>
对于每个人的单元测试友好性!
<?php
命名空间 testme\here;
class TestClass {
公共函数 test()
{
返回 get_class_name(get_class());
}
}
class GetClassNameTest 扩展 \PHPUnit_Framework_TestCase
{
公共函数 testGetClassName()
{
$class = 新的 TestClass();
$std = 新的 \stdClass();
$this->assertEquals('TestClass', get_class_name($class));
$this->assertEquals('stdClass', get_class_name($std));
$this->assertEquals('Test', get_class_name('Test'));
$this->assertFalse(get_class_name(null));
$this->assertFalse(get_class_name(数组()));
$this->assertEquals('TestClass', $class->test());
}
}
?>
如错误 #30934 中所述(实际上不是错误,而是设计决策的结果),"self" 关键字在编译时绑定。 除了其他事情之外,这意味着在基类方法中,任何使用 "self" 关键字都将引用该基类,而不管实际调用的(派生)类是什么。 当试图从派生类中的继承方法中调用重写后的静态方法时,这会变得很麻烦。 例如
<?php
class Base
{
受保护的 $m_instanceName = '';
公共静态函数 classDisplayName()
{
返回 'Base Class';
}
公共函数 instanceDisplayName()
{
// 这里,我们希望 "self" 引用实际的类,它可能是继承了此方法的派生类,不一定就是这个基类
返回 $this->m_instanceName . ' - ' . self::classDisplayName();
}
}
class Derived 扩展 Base
{
公共函数 Derived( $name )
{
$this->m_instanceName = $name;
}
公共静态函数 classDisplayName()
{
返回 'Derived Class';
}
}
$o = 新的 Derived('My Instance');
echo $o->instanceDisplayName();
?>
在上面的例子中,假设运行时绑定(其中关键字“self”指的是调用方法的实际类,而不是定义方法的类)将产生以下输出
我的实例 - 派生类
但是,假设编译时绑定(其中关键字“self”指的是定义方法的类),这是 PHP 的工作方式,输出将是
我的实例 - 基类
这里奇怪的是“$this”在运行时绑定到对象的实际类(显然),但“self”在编译时绑定,这对我来说似乎不直观。“self”始终是定义它的类名的同义词,程序员知道这一点,所以他/她可以使用类名;程序员无法知道调用方法的实际类名(因为方法可以调用派生类上的方法),我认为这是“self”应该有用的地方。
但是,撇开设计决策问题,仍然存在如何实现类似于“self”在运行时绑定的行为的问题,以便在派生类上或从派生类内部调用的静态和非静态方法都作用于该派生类。get_class() 函数可用于模拟“self”关键字的运行时绑定功能,用于静态方法
<?php
class Base
{
protected $m_instanceName = '';
public static function classDisplayName()
{
return 'Base Class';
}
public function instanceDisplayName()
{
$realClass = get_class($this);
return $this->m_instanceName . ' - ' . call_user_func(array($realClass, 'classDisplayName'));
}
}
class Derived extends Base
{
public function Derived( $name )
{
$this->m_instanceName = $name;
}
public static function classDisplayName()
{
return 'Derived Class';
}
}
$o = new Derived('My Instance');
echo $o->instanceDisplayName();
?>
输出
我的实例 - 派生类
我意识到有些人可能会回答“为什么不直接使用类名加上“Class”而不是 classDisplayName() 方法”,这错过了重点。重点不是返回的实际字符串,而是想要从继承的非静态方法中使用真实的类来调用重写的静态方法。以上只是一个简化版本,真实世界的问题过于复杂,无法用作示例。
如果之前已经提到过,请原谅。
@ Frederik
<?
$class = $bt[count($bt) - 1]['class'];
?>
应该是
<?
$class = $bt[count($bt) - 2]['class'];
?>
;-)
这是对 luke at liveoakinteractive dot com 和 davidc at php dot net 的回复。静态方法和变量,根据定义,绑定到类类型而不是对象实例。您不应该需要动态地找出静态方法属于哪个类,因为您代码的上下文应该让它很明显。您的问题表明您可能还没有完全理解 OOP(我花了一段时间才明白)。
Luke,从您的特定代码片段观察到的行为在您考虑时是有意义的。getclass() 方法在 BooBoof 中定义,因此 __CLASS__ 宏将绑定到 BooBoof 并在 BooBoof 类中定义。CooCoof 是 BooBoof 的子类这一事实仅仅意味着它获得了对 BooBoof::getclass() 的快捷方式。因此,实际上,您实际上是在(以一种复杂的方式)询问:“调用 BooBoof::getclass() 方法的类是什么?”正确的解决方案(如果您实际上想要/需要这样做)是在 CooCoof 定义中简单地实现 CooCoof::getclass() { return __CLASS__; },以及您希望模仿此行为的任何子类。CooCoof::getclass() 将具有预期的行为。
更多怪异现象
class Parent {
function displayTableName() {
echo get_class($this);
echo get_class();
}
}
class Child {
function __construct() {
$this->displayTableName();
}
}
将返回
- Child
- Parent
所以当他们说“在 PHP5 中不需要对象”时,他们并不是真的这么认为。
使用 PHP 5.3.0 中可用的延迟静态绑定,现在可以实现具有最少开销的抽象单例类。延迟静态绑定在此处解释:http://nl2.php.net/manual/en/language.oop5.late-static-bindings.php
简而言之,它引入了新的“static::”关键字,它在运行时进行评估。在下面的代码中,我使用它来确定子单例类的类名。
<?
abstract class Singleton {
protected static $__CLASS__ = __CLASS__;
protected static $instance;
protected function __construct() {
static::$instance = $this;
$this->init();
}
abstract protected function init();
protected function getInstance() {
$class = static::getClass();
if (static::$instance===null) {
static::$instance = new $class;
}
return static::$instance;
}
private static function getClass() {
if (static::$__CLASS__ == __CLASS__) {
die("您必须在您的 Singleton 类中提供一个 <code>protected static \$__CLASS__ = __CLASS__;</code> 语句!");
}
return static::$__CLASS__;
}
}
?>
单例类的示例可以如下实现
<?
class A extends Singleton {
protected static $__CLASS__ = __CLASS__; // 在每个单例类中提供此项。
protected function someFunction() {
$instance = static::getInstance();
// ...
}
}
?>
希望这能帮助您节省一些时间 :)
在阅读了之前的评论后,这是我为获取子类的最终类名所做的最好的方法
<?php
class Singleton
{
private static $_instances = array();
protected final function __construct(){}
/**
* @param string $classname
* @return Singleton
*/
protected static function getInstance()
{
$classname = func_get_arg(0);
if (! isset(self::$_instances[$classname]))
{
self::$_instances[$classname] = new $classname();
}
return self::$_instances[$classname];
}
}
class Child extends Singleton
{
/**
* @return Child
*/
public static function getInstance()
{
return parent::getInstance(get_class());
}
}
?>
子类必须重写“getInstance”并且不能重写“__construct”。
由于 PHP 5 引擎允许在静态调用的函数中获取最终类,这是对下面发布的示例的修改版本。
<?php
抽象类 Singleton {
受保护的静态 $_instances = array();
受保护的函数 __construct() {}
受保护的静态函数 getInstance() {
$bt = debug_backtrace();
$class = $bt[count($bt) - 1]['class'];
if (!isset(self::$_instances[$class])) {
self::$_instances[$class] = new $class();
}
return self::$_instances[$class];
}
}
类 A 扩展 Singleton {
公共静态函数 getInstance() {
return parent::getInstance();
}
}
类 B 扩展 Singleton {
公共静态函数 getInstance() {
return parent::getInstance();
}
}
类 C 扩展 A {
公共静态函数 getInstance() {
return parent::getInstance();
}
}
$a = A::getInstance();
$b = B::getInstance();
$c = C::getInstance();
echo "\$a is a " . get_class($a) . "<br />";
echo "\$b is a " . get_class($b) . "<br />";
echo "\$c is a " . get_class($c) . "<br />";
?>
我不确定如果跳过 debug_backtrace(),而是在 getInstance() 中接受一个通过 get_class() 方法获取的传递类作为参数,性能是否会提高,如下面的帖子中所述。
通过在 Singleton 类中将 getInstance() 设置为受保护的,该函数需要被重写(良好的 OOP 实践)。
需要注意的是,如果 $class 为空或未定义,则没有错误检查,这会导致致命错误。但是,目前我无法理解在 getInstance() 受保护的情况下(即必须在子类中重写)如何发生这种情况 - 但是,按照良好的编码习惯,你应该始终进行错误检查。
致:Bryan
在这个模型中,如果你的单例变量实际上是一个数组,它仍然是可行的。考虑
<?php
抽象类 Singleton {
受保护的最终静态 $instances = array();
受保护的 __construct(){}
受保护的函数 getInstance() {
$class = get_real_class(); // 想象一个返回最终类名的函数,而不是代码执行的类
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
}
类 A 扩展 Singleton {
}
类 B 扩展 Singleton {
}
$a = A::getInstance();
$b = B::getInstance();
echo "\$a is a " . get_class($a) . "<br />";
echo "\$b is a " . get_class($b) . "<br />";
?>
这将输出
$a is a A
$b is a B
正如其他地方所述,唯一的替代方案是将 getInstance() 设为受保护的抽象,接受类名作为参数,并用一个公共最终方法扩展此调用,该方法用于每个子类,该方法在其本地对象范围内使用 get_class() 并将其传递给超类。
或者,创建一个像这样的单例工厂
<?php
最终类 SingletonFactory {
受保护的静态 $instances = array();
受保护的 getInstance($class) {
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
}
?>
当然,这种后一种模型的缺点是,类本身无法决定它是否是单例,而是调用代码决定,而一个真正想要或*需要*成为单例的类无法强制执行这一点,即使通过将它的构造函数设为受保护的也不行。
基本上,这些设计模式以及各种其他元操作(在对象性质上进行操作,而不是在对象保存的数据上进行操作)可以从准确地知道此对象的最终类型中获益良多,而无法访问此信息的本地访问会迫使使用变通方法。
致:yicheng zero-four at gmail dot com:另一个可能是更好的例子,在静态方法中找出真实类(而不是我们所在的类)应该非常有用,那就是单例模式。
目前还没有办法创建抽象单例类,该类可以通过扩展它来使用,而无需更改扩展类。考虑这个例子
<?php
抽象类 Singleton
{
受保护的静态 $__instance = false;
公共静态函数 getInstance()
{
if (self::$__instance == false)
{
// 这实际上不是我们想要的,$class 将始终为 'Singleton' :(
$class = get_class();
self::$__instance = new $class();
}
return self::$__instance;
}
}
类 Foo 扩展 Singleton
{
// ...
}
$single_foo = Foo::getInstance();
?>
这段代码会导致一个致命错误,提示:无法在 ... 第 11 行实例化抽象类 Singleton
我发现的最好的避免这种情况的方法需要简单的更改,但仍然需要更改扩展类 (Foo)
<?php
抽象类 Singleton
{
受保护的静态 $__instance = false;
受保护的静态函数 getInstance($class)
{
if (self::$__instance == false)
{
if (class_exists($class))
{
self::$__instance = new $class();
}
else
{
throw new Exception('无法实例化未定义的类 [' . $class . ']', 1);
}
}
return self::$__instance;
}
}
类 Foo 扩展 Singleton
{
// 你必须在每个扩展类中重载 getInstance 方法:
公共静态函数 getInstance()
{
return parent::getInstance(get_class());
}
}
$single_foo = Foo::getInstance();
?>
这当然没什么大不了的,你可能需要在扩展类中更改一些东西(至少更改构造函数的访问权限),但仍然... 它并不像它可能的那样好;)
如果你省略了继承类的参数,请小心。
它将返回调用它的方法的类名。
<?php
类 A {
函数 foo() {
返回 get_class();
}
}
类 B 扩展 A {
函数 bar() {
返回 get_class();
}
}
$instance = 新 B();
echo $instance->bar(); // 输出 'B';
echo $instance->foo(); // 输出 'A';
?>
要获取不带命名空间的类名,可以使用这个简单技巧
<?php
命名空间 My\Long\Namespace;
类 MyClass {
静态函数 getClassName() {
返回 basename(__CLASS__);
// 或者使用 get_class();
返回 basename(get_class());
}
}
echo \My\Long\Namespace\MyClass::getClassName(); // 显示 : MyClass
?>
类 A
{
函数 __construct(){
//parent::__construct();
echo $this->m = '来自构造函数 A: '.get_class();
echo $this->m = '来自构造函数 A:- 参数 = $this: '.get_class($this);
echo $this->m = '来自构造函数 A-父类: '.get_parent_class();
echo $this->m = '来自构造函数 A-父类:- 参数 = $this: '.get_parent_class($this);
}
}
类 B 扩展 A
{
函数 __construct(){
parent::__construct();
echo $this->m = '来自构造函数 B: '.get_class();
echo $this->m = '来自构造函数 B:- 参数 = $this: '.get_class($this);
echo $this->m = '来自构造函数 B-父类: '.get_parent_class();
echo $this->m = '来自构造函数 B-父类:- 参数 = $this: '.get_parent_class($this);
}
}
$b = 新 B();
//----------------输出--------------------
来自构造函数 A: A
来自构造函数 A:- 参数 = $this: B
来自构造函数 A-父类
来自构造函数 A-父类:- 参数 = $this: A
来自构造函数 B: B
来自构造函数 B:- 参数 = $this: B
来自构造函数 B-父类: A
来自构造函数 B-父类:- 参数 = $this: A
使用 get_class() 获取类名,它将帮助您获取类名,如果您使用另一个类扩展该类,并且想要获取对象所属类的名称,请使用 get_class($object)
当您创建一个类 {B 的 $b 对象} 的对象,该对象有一个超类 {类 A} 时。
在超类 {A} 中使用这些代码
--------------------------------------------
要获取类名 B {对象实例}: get_class($this)
要获取类名 A {超类}: get_class() 或 get_parent_class($this)
如果您想要不带命名空间的类名,或者您是因为 basename() 返回了 FQCN (完全限定类名) 的点 (.) 而来到这里,以下是解决方案
<?php
// FQCN: App\Http\Controllers\CustomerReportController
substr(self::class, (int)strrpos(self::class, '\\') + 1)
// 返回: CustomerReportController
?>
可以使用新的 get_called_class 函数在 PHP 5.3 中编写完全独立的单例基类。当在静态方法中调用时,此函数将返回调用该函数的类的名称。
<?php
抽象类 Singleton {
受保护函数 __construct() {
}
最终 公共静态函数 getInstance() {
静态 $aoInstance = 数组();
$calledClassName = get_called_class();
如果 (! 存在 ($aoInstance[$calledClassName])) {
$aoInstance[$calledClassName] = 新 $calledClassName();
}
返回 $aoInstance[$calledClassName];
}
最终 私有函数 __clone() {
}
}
类 DatabaseConnection 扩展 Singleton {
受保护 $connection;
受保护函数 __construct() {
// @todo 连接数据库
}
公共函数 __destruct() {
// @todo 断开与数据库的连接
}
}
$oDbConn = 新 DatabaseConnection(); // 致命错误
$oDbConn = DatabaseConnection::getInstance(); // 返回单个实例
?>
2008 年 10 月的完整文章:http://danbettles.blogspot.com
请注意,常量 __CLASS__ 与 get_class($this) 不同
<?
类 test {
函数 whoami() {
echo "你好,我是 whoami 1 !\r\n";
echo "的值 __CLASS__ : ".__CLASS__."\r\n";
echo "的值 get_class() : ".get_class($this)."\r\n\r\n";
}
}
类 test2 扩展 test {
函数 whoami2() {
echo "你好,我是 whoami 2 !\r\n";
echo "的值 __CLASS__ : ".__CLASS__."\r\n";
echo "的值 get_class() : ".get_class($this)."\r\n\r\n";
parent::whoami(); // 调用父类 whoami() 函数
}
}
$test=新 test;
$test->whoami();
$test2=新 test2;
$test2->whoami();
$test2->whoami2();
?>
输出为
你好,我是 whoami 1 !
的值 __CLASS__ : test
的值 get_class() : test
你好,我是 whoami 1 !
的值 __CLASS__ : test
的值 get_class() : test2
你好,我是 whoami 2 !
的值 __CLASS__ : test2
的值 get_class() : test2
你好,我是 whoami 1 !
的值 __CLASS__ : test
的值 get_class() : test2
实际上,__CLASS__ 返回函数所在的类的名称,get_class($this) 返回创建的类的名称。
小心使用回溯方法查找静态方法的调用类,您应该逐步向后遍历数组并找到与 getInstance() 函数匹配项。在回溯中,您想要的类名并不总是数组中的最后一项。
我不会在此处发布完整的单例类,但对 Frederik Krautwald 的方法(见下文)进行了以下修改
<?php
$bt = debug_backtrace();
// 此方法由 Frederik 的 count($bt)-1) 方法在从包含文件调用 getInstance 时将失败。
//$class = $bt[count($bt) - 1]['class'];
对于( $i=count($bt)-1 ; $i > 0 ; $i--)
{
如果($bt[$i]['function'] == 'getInstance')
{
$class = $bt[$i]['class'];
中断;
}
}
?>