>= 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; // parse error
(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 "我的名字是 " , get_class($this) , "\n";
}
}
// 建立一個物件
$bar = new foo();
// 外部呼叫
echo "它的名字是 " , 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; // parse error
許多人在其他評論中想要取得不包含命名空間的類別名稱。一些取得類別名稱的怪異程式碼建議 - 不是我會寫的方式!所以想加入我自己的方式。
<?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();
?>
輸出
陣列
(
[namespace] => 陣列
(
[0] => Engine
)
[classname] => Kernel
)
在 Perl(以及其他一些語言)中,您可以在物件和類別(又稱靜態)環境中呼叫某些方法。我為我在 PHP5 中的一個類別製作了一個這樣的方法,但發現 PHP5 中的靜態方法「不知曉」呼叫子類別的名稱,因此我使用回溯來判斷它。我不喜歡像這樣的駭客行為,但只要 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("你必須在你的單例類別中提供 <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 是 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() {
// 單例
}
}
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(); //輸出 Parent
?>
<?php
class Parent{
public function getClass(){
echo get_class($this);
}
}
class Child extends Parent{
}
$obj = new Child();
$obj->getClass(); // Parent
?>
這個筆記是對 davidc 在 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();
// 輸出 BooBoof
$coocoof = new CooCoof;
echo $coocoof->retrieve_class();
// 輸出 CooCoof
?>
__CLASS__ 和 get_class($this) 在繼承的類別中運作方式不同。到目前為止,我一直無法確定一種可靠的方法來從靜態方法中取得實際的類別。
嘗試此頁面上描述的各種單例基類方法,我建立了一個基類和橋接函數,如果 get_called_class() 不可用,它就可以運作。與此處列出的其他方法不同,我選擇不阻止使用 __construct() 或 __clone()。
<?php
abstract class Singleton {
protected static $m_pInstance;
final public static function getInstance(){
$class = static::getClass();
if(!isset(static::$m_pInstance[$class])) {
static::$m_pInstance[$class] = new $class;
}
return static::$m_pInstance[$class];
}
final public static function getClass(){
return get_called_class();
}
}
// 我不記得在哪裡找到這個,但這是為了讓 php < 5.3 可以使用這個方法。
if (!function_exists('get_called_class')) {
function get_called_class($bt = false, $l = 1) {
if (!$bt)
$bt = debug_backtrace();
if (!isset($bt[$l]))
throw new Exception("找不到被呼叫的類別 -> 堆疊層級太深。");
if (!isset($bt[$l]['type'])) {
throw new Exception('未設定類型');
}
else
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);
if (!isset($matches[1])) {
// 一定是邊緣情況。
throw new Exception("找不到呼叫者類別:原始方法呼叫被遮蔽。");
}
switch ($matches[1]) {
case 'self':
case 'parent':
return get_called_class($bt, $l + 1);
default:
return $matches[1];
}
// 不會到這裡。
case '->': switch ($bt[$l]['function']) {
case '__get':
// 邊緣情況 -> 取得呼叫物件的類別
if (!is_object($bt[$l]['object']))
throw new Exception("邊緣情況失敗。在非物件上呼叫 __get。");
return get_class($bt[$l]['object']);
default: return $bt[$l]['class'];
}
default: throw new Exception("未知的回溯方法類型");
}
}
}
class B extends Singleton {
}
class C extends 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 類別名稱,已去除命名空間
*/
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];
}
?>
為了讓大家可以好好進行單元測試!
<?php
namespace testme\here;
class TestClass {
public function test()
{
return get_class_name(get_class());
}
}
class GetClassNameTest extends \PHPUnit_Framework_TestCase
{
public function testGetClassName()
{
$class = new TestClass();
$std = new \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(array()));
$this->assertEquals('TestClass', $class->test());
}
}
?>
如錯誤 #30934 中所述(實際上這不是錯誤,而是設計決策的結果),"self" 關鍵字在編譯時會被綁定。 除此之外,這表示在基底類別方法中,任何使用 "self" 關鍵字都會參照該基底類別,而不管實際呼叫該方法的(衍生)類別為何。 當嘗試從衍生類別的繼承方法中呼叫被覆寫的靜態方法時,這會變成問題。 例如:
<?php
class Base
{
protected $m_instanceName = '';
public static function classDisplayName()
{
return 'Base Class';
}
public function instanceDisplayName()
{
//這裡,我們希望 "self" 參照實際類別,這可能是繼承此方法的衍生類別,而不一定是這個基底類別
return $this->m_instanceName . ' - ' . self::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();
?>
在上面的範例中,假設使用執行期綁定(其中關鍵字「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 開始可用的「延遲靜態綁定」,現在可以以最小的子類別開銷來實作抽象的 Singleton 類別。延遲靜態綁定在此處說明: http://nl2.php.net/manual/en/language.oop5.late-static-bindings.php
簡而言之,它引入了一個新的「static::」關鍵字,該關鍵字會在執行期評估。在下面的程式碼中,我使用它來判斷子 Singleton 類別的類別名稱。
<?
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("你必須在你的單例類別中提供 <code>protected static \$__CLASS__ = __CLASS__;</code> 陳述式!");
}
return static::$__CLASS__;
}
}
?>
然後可以將範例 Singleton 類別實作如下
<?
class A extends Singleton {
protected static $__CLASS__ = __CLASS__; // 在每個 singleton 類別中提供此項。
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
abstract class Singleton {
protected static $_instances = array();
protected function __construct() {}
protected static function getInstance() {
$bt = debug_backtrace();
$class = $bt[count($bt) - 1]['class'];
if (!isset(self::$_instances[$class])) {
self::$_instances[$class] = new $class();
}
return self::$_instances[$class];
}
}
class A extends Singleton {
public static function getInstance() {
return parent::getInstance();
}
}
class B extends Singleton {
public static function getInstance() {
return parent::getInstance();
}
}
class C extends A {
public static function 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()` 方法取得的類別作為參數,效能是否會提高。
藉由將 `getInstance()` 設定為 `Singleton` 類別中的 `protected`,該函式必須被覆寫(良好的 OOP 實務)。
需要提到的一點是,如果 `$class` 為 null 或未定義,則不會有錯誤檢查,這將導致嚴重錯誤。然而,目前我看不出當 `getInstance()` 為 `protected` 時,也就是必須在子類別中覆寫時,這會如何發生 -- 但在良好的程式設計實務中,您應該始終進行錯誤檢查。
給 Bryan
在這個模型中,如果您的單例變數實際上是一個陣列,它仍然可以運作。考慮以下情況:
<?php
abstract class Singleton {
protected final static $instances = array();
protected __construct(){}
protected function getInstance() {
$class = get_real_class(); // 虛構函式,傳回最終類別名稱,而不是程式碼執行的類別
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
}
class A extends Singleton {
}
class B extends 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()` 設定為 `protected abstract`,接受類別名稱作為參數,並為每個子類別擴展此呼叫,使用 `get_class()` 在其本地物件範圍中並將其傳遞給父類別的公共最終方法。
或者建立像這樣的單例工廠
<?php
final class SingletonFactory {
protected static $instances = array();
protected getInstance($class) {
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
}
?>
後者的缺點當然是,類別本身無法決定它是否為單例,而是由呼叫程式碼來決定,並且真正想要或*需要*成為單例的類別無法強制執行此操作,即使將其建構子設定為 `protected` 也不行。
基本上,這些設計模式以及各種其他元操作(對物件的本質進行操作,而不是對物件持有的資料進行操作)可以從確切知道此物件的最終類型中受益匪淺,而無法原生存取此資訊會導致需要使用變通方法。
給 yicheng zero-four at gmail dot com:另一個可能更好的例子,其中在靜態方法中找出實際類別(而不是我們所在的類別)應該非常有用,就是單例模式。
目前沒有辦法建立抽象單例類別,可以僅透過擴展它來使用,而無需變更擴展類別。考慮以下範例
<?php
abstract class Singleton
{
protected static $__instance = false;
public static function getInstance()
{
if (self::$__instance == false)
{
// 這實際上不是我們想要的,$class 將始終是 'Singleton' :(
$class = get_class();
self::$__instance = new $class();
}
return self::$__instance;
}
}
class Foo extends Singleton
{
// ...
}
$single_foo = Foo::getInstance();
?>
這段程式碼會導致嚴重錯誤,指出:無法在 ... 的第 11 行中實例化抽象類別 Singleton
我發現避免這種情況的最佳方法是簡單地變更擴展(Foo)類別
<?php
abstract class Singleton
{
protected static $__instance = false;
protected static function getInstance($class)
{
if (self::$__instance == false)
{
if (class_exists($class))
{
self::$__instance = new $class();
}
else
{
throw new Exception('無法實例化未定義的類別 [' . $class . ']', 1);
}
}
return self::$__instance;
}
}
class Foo extends Singleton
{
// 您必須在每個擴展類別中覆載 getInstance 方法:
public static function getInstance()
{
return parent::getInstance(get_class());
}
}
$single_foo = Foo::getInstance();
?>
這當然沒什麼可怕的,您可能需要在擴展類別中變更一些內容(至少是建構子的存取權限),但仍然...它不像它可能的那樣好 ;)
如果您在繼承類別中省略參數,請小心。
它會傳回呼叫它的方法的類別名稱。
<?php
class A {
function foo() {
return get_class();
}
}
class B extends A {
function bar() {
return get_class();
}
}
$instance = new B();
echo $instance->bar(); // 顯示 'B';
echo $instance->foo(); // 顯示 'A';
?>
若要取得不含命名空間的類別名稱,可以使用這個簡單的技巧
<?php
namespace My\Long\Namespace;
class MyClass {
static function getClassName() {
return basename(__CLASS__);
// 或者使用 get_class();
return basename(get_class());
}
}
echo \My\Long\Namespace\MyClass::getClassName(); // 顯示: MyClass
?>
class A
{
function __construct(){
//parent::__construct();
echo $this->m = 'From constructor A: '.get_class();
echo $this->m = 'From constructor A:- argument = $this: '.get_class($this);
echo $this->m = 'From constructor A-parent: '.get_parent_class();
echo $this->m = 'From constructor A-parent:- argument = $this: '.get_parent_class($this);
}
}
class B extends A
{
function __construct(){
parent::__construct();
echo $this->m = 'From constructor B: '.get_class();
echo $this->m = 'From constructor B:- argument = $this: '.get_class($this);
echo $this->m = 'From constructor B-parent: '.get_parent_class();
echo $this->m = 'From constructor B-parent:- argument = $this: '.get_parent_class($this);
}
}
$b = new B();
//----------------輸出--------------------
From constructor A: A
From constructor A:- argument = $this: B
From constructor A-parent
From constructor A-parent:- argument = $this: A
From constructor B: B
From constructor B:- argument = $this: B
From constructor B-parent: A
From constructor B-parent:- argument = $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
?>
在 PHP 5.3 中,可以使用新的 get_called_class 函式來編寫一個完全獨立的 Singleton 基底類別。當在靜態方法中呼叫時,此函式會回傳呼叫發生時所針對的類別名稱。
<?php
abstract class Singleton {
protected function __construct() {
}
final public static function getInstance() {
static $aoInstance = array();
$calledClassName = get_called_class();
if (! isset ($aoInstance[$calledClassName])) {
$aoInstance[$calledClassName] = new $calledClassName();
}
return $aoInstance[$calledClassName];
}
final private function __clone() {
}
}
class DatabaseConnection extends Singleton {
protected $connection;
protected function __construct() {
// @todo 連線到資料庫
}
public function __destruct() {
// @todo 中斷與資料庫的連線
}
}
$oDbConn = new DatabaseConnection(); // 嚴重錯誤
$oDbConn = DatabaseConnection::getInstance(); // 回傳單一實例
?>
完整撰寫於 2008 年 10 月:http://danbettles.blogspot.com
請注意,常數 __CLASS__ 與 get_class($this) 不同
<?
class test {
function whoami() {
echo "Hello, I'm whoami 1 !\r\n";
echo "Value of __CLASS__ : ".__CLASS__."\r\n";
echo "Value of get_class() : ".get_class($this)."\r\n\r\n";
}
}
class test2 extends test {
function whoami2() {
echo "Hello, I'm whoami 2 !\r\n";
echo "Value of __CLASS__ : ".__CLASS__."\r\n";
echo "Value of get_class() : ".get_class($this)."\r\n\r\n";
parent::whoami(); // 呼叫父類別 whoami() 函式
}
}
$test=new test;
$test->whoami();
$test2=new test2;
$test2->whoami();
$test2->whoami2();
?>
輸出結果為
Hello, I'm whoami 1 !
Value of __CLASS__ : test
Value of get_class() : test
Hello, I'm whoami 1 !
Value of __CLASS__ : test
Value of get_class() : test2
Hello, I'm whoami 2 !
Value of __CLASS__ : test2
Value of get_class() : test2
Hello, I'm whoami 1 !
Value of __CLASS__ : test
Value of get_class() : test2
事實上,__CLASS__ 回傳函式所在的類別名稱,而 get_class($this) 回傳所建立的類別名稱。
請小心使用回溯方法來找出靜態方法的呼叫類別,你應該在陣列中往回查找,找到與你的 getInstance() 函式相符的項目。在回溯中,你想要的類別名稱不一定是陣列中的最後一個項目。
我不會在這裡張貼完整的單例類別,但對 Frederik Krautwald 的方法(如下所示)做了以下修改
<?php
$bt = debug_backtrace();
// Frederik 的這個方法 count($bt)-1) 在從 include 檔案內呼叫 getInstance 時會失效。
//$class = $bt[count($bt) - 1]['class'];
for( $i=count($bt)-1 ; $i > 0 ; $i--)
{
if($bt[$i]['function'] == 'getInstance')
{
$class = $bt[$i]['class'];
break;
}
}
?>