終於我們可以實作一些 ActiveRecord 方法了
<?php
class Model
{
public static function find()
{
echo static::$name;
}
}
class Product extends Model
{
protected static $name = 'Product';
}
Product::find();
?>
輸出:'Product'
PHP 實作了一項稱為延遲靜態綁定的功能,可用於在靜態繼承的上下文中參考被呼叫的類別。
更精確地說,延遲靜態綁定透過儲存最後一個「非轉發呼叫」中命名的類別來運作。在靜態方法呼叫的情況下,這是明確命名的類別(通常是 ::
運算子左邊的類別);在非靜態方法呼叫的情況下,這是物件的類別。「轉發呼叫」是由 self::
、parent::
、static::
引入的靜態呼叫,或在類別階層中往上時,由 forward_static_call() 引入的靜態呼叫。函式 get_called_class() 可用於擷取一個字串,其中包含被呼叫類別的名稱,並且 static::
會引入它的作用域。
此功能從內部角度來看被命名為「延遲靜態綁定」。「延遲綁定」來自於 static::
將不會使用方法定義的類別來解析,而是會使用執行時資訊來計算的事實。它也被稱為「靜態綁定」,因為它可以用於(但不限於)靜態方法呼叫。
self::
的限制對目前類別的靜態參考,如 self::
或 __CLASS__
,會使用函式所屬的類別(即定義的位置)來解析。
範例 #1 self::
用法
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
?>
上面的範例將輸出
A
延遲靜態綁定試圖透過引入一個關鍵字來解決這個限制,該關鍵字參考執行時最初呼叫的類別。基本上,一個允許從先前範例中的 test()
參考 B
的關鍵字。它決定不引入新的關鍵字,而是使用已經保留的 static
。
範例 #2 static::
的簡單用法
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // 此處使用延遲靜態綁定
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
?>
上面的範例將輸出
B
注意:
在非靜態上下文中,被呼叫的類別將是物件實例的類別。由於
$this->
將嘗試從相同的作用域呼叫私有方法,因此使用static::
可能會產生不同的結果。另一個差異是static::
只能參考靜態屬性。
範例 #3 在非靜態上下文中 static::
的用法
<?php
class A {
private function foo() {
echo "success!\n";
}
public function test() {
$this->foo();
static::foo();
}
}
class B extends A {
/* foo() 將會被複製到 B,因此它的作用域仍然是 A,並且呼叫會成功 */
}
class C extends A {
private function foo() {
/* 原始方法被取代;新方法的作用域是 C */
}
}
$b = new B();
$b->test();
$c = new C();
$c->test(); //失敗
?>
上面的範例將輸出
success! success! success! Fatal error: Call to private method C::foo() from context 'A' in /tmp/test.php on line 9
注意:
延遲靜態綁定的解析將會在完全解析的靜態呼叫處停止,並且沒有後備機制。另一方面,使用像
parent::
或self::
這類關鍵字的靜態呼叫將會轉發呼叫資訊。範例 #4 轉發和非轉發呼叫
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo();
self::foo();
}
public static function who() {
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __CLASS__."\n";
}
}
C::test();
?>上面的範例將輸出
A C C
終於我們可以實作一些 ActiveRecord 方法了
<?php
class Model
{
public static function find()
{
echo static::$name;
}
}
class Product extends Model
{
protected static $name = 'Product';
}
Product::find();
?>
輸出:'Product'
對於具有靜態工廠方法的抽象類別,您可以使用 `static` 關鍵字來取代 `self`,如下所示:
<?php
abstract class A{
static function create(){
//return new self(); //Fatal error: Cannot instantiate abstract class A
return new static(); //這是正確的方式
}
}
class B extends A{
}
$obj=B::create();
var_dump($obj);
?>
<?php
class A
{
}
class B extends A
{
public static function foo () {
echo 'new self: ';
var_dump(new self());
echo '<br>new parent: ';
var_dump(new parent());
echo '<br>new static: ';
var_dump(new static());
}
}
class C extends B
{
}
c::foo();
===========================
output:
//new self: object(B)#1 (0) { }
//new parent: object(A)#1 (0) { }
//new static: object(C)#1 (0) { }
請注意 `static::class` 並非總是如您預期的那樣運作
<?php
namespace NameSpace;
class Class
{
static function getClass()
{
return static::class;
}
}
Class::getClass()
?>
可能會根據上下文返回 `\NameSpace\Class` 或 `Class`
在上面的範例(#3)中,為了使其正常運作,您可以將子類別的方法從 'private' 變更為 'protected'(或 public),這樣就可以透過 'static' 來呼叫它。
<?php
class A {
private function foo() {
echo "success!\n";
}
public function test() {
$this->foo();
static::foo();
}
}
class B extends A {
/* foo() 將會被複製到 B,因此其作用域仍將為 A,且呼叫將會成功 */
}
class C extends A {
protected function foo() { //注意此處的變更
echo 'hello world!';
}
}
$b = new B();
$b->test();
$c = new C();
$c->test(); // 'success' 'hello world'
?>
`static::class` 和 `self::class` 可用於取得目前類別的名稱,
在 5.5 和 5.6 版本下運作正常
在 5.3 版本下會失敗。
<?php
class a{
function d() {
echo "=== self::class ===\n";
var_dump(self::class);
echo "=== static::class ===\n";
var_dump(static::class);
}
}
class b extends a{}
class c extends b{}
a::d();
b::d();
c::d();
/*
輸出:
=== self::class ===
string(1) "a"
=== static::class ===
string(1) "a"
=== self::class ===
string(1) "a"
=== static::class ===
string(1) "b"
=== self::class ===
string(1) "a"
=== static::class ===
string(1) "c"
*/
理解最簡單的方法是執行此腳本
<?php
class ParentClass
{
static $A = 'ParentVariable';
static function parentCall()
{
echo get_called_class() . ', self: ' . self::$A . "\n";
echo get_called_class() . ', static: ' . static::$A . "\n";
echo "---\n";
}
}
class ChildClass extends ParentClass
{
static $A = 'ChildVariable';
static function childCall()
{
echo get_called_class() . ', self: ' . self::$A . "\n";
echo get_called_class() . ', static: ' . static::$A . "\n";
echo get_called_class() . ', parent: ' . parent::$A . "\n";
echo "---\n";
}
}
echo "Late Static Bindings:\n";
ParentClass::parentCall();
ChildClass::parentCall();
ChildClass::childCall();
?>
----
輸出
延遲靜態綁定
ParentClass, self: ParentVariable
ParentClass, static: ParentVariable
---
ChildClass, self: ParentVariable
ChildClass, static: ChildVariable
---
ChildClass, self: ChildVariable
ChildClass, static: ChildVariable
ChildClass, parent: ParentVariable
class P_Class {
public static $val = "Parent";
public static function setVal($val){
static::$val = $val;
}
public static function getVal(){
return static::$val;
}
}
class C_Class extends P_Class{}
C_Class::setVal("Child");
var_dump(C_Class::getVal());
var_dump(P_Class::getVal());
輸出
string(5) "Child"
string(5) "Child"
@ php at mikebird
假設您正在運行 php5,您可以透過 `getInstance` 方法將參數傳遞給您的建構函式。
public static function getInstance($params = null) {
if (self::$objInstance == null) {
$strClass = static::getClass();
self::$objInstance = new $strClass($params);
}
return self::$objInstance;
}
這會將參數傳遞給您的建構函式。愛 PHP。
此函式可用作 PHP >= 5.1.0 中延遲靜態綁定的變通方法。 另一個地方有另一個類似版本的此函式,但使用了 `eval`。
<?php
function & static_var($class, $name)
{
if (is_object($class))
{
$class = get_class($class);
}
elseif ( ! is_string($class))
{
throw new Exception('必須給予一個物件或一個類別名稱', NULL);
}
$class = new ReflectionClass($class);
return $class->getStaticPropertyValue($name);
}
?>
我已經使用延遲靜態綁定實作了枚舉。
<?php
interface IEnum {
/**
* 只有實體類別應實作此函式,其行為應如同列舉。
*
* 此方法應回傳該類別的 __CLASS__ 常數屬性
*
* @return string __CLASS__
*/
public static function who();
}
abstract class Enum {
/**
* 列舉實作的選定值
*
* @var mixed
*/
public $value;
public function __construct($value) {
$this->value = $value;
}
/**
* 建立對應列舉類別的工廠方法。
*
* @param integer $type
* @return false|\class
*/
public static function Factory($type) {
if (empty($type)) {
return false;
}
// 使用延遲靜態綁定來取得類別。
$class = static::who();
if (array_key_exists($type, static::$_enums)) {
return new $class($type);
}
return false;
}
public function getValue() {
return $this->value;
}
public static function getValues() {
return array_keys(static::$_enums);
}
public function getString() {
return static::$_enums[$this->value];
}
public function __toString() {
return static::$_enums[$this->value];
}
}
class Fruits extends Enum implements IEnum {
public static $_enums = array(
1 => 'Apple'
2 => 'Orange'
3 => 'Banana'
)
public static function who() {
return __CLASS__;
}
}
// 用法
// 使用者從水果清單的下拉式選單輸入
$input = 3;
$fruit = Fruits::Factory($input);
$fruit->getValue(); // 3
$fruit->getString(); // Banana
?>
令人驚訝的是,即使你使用 self 而不是 static,常數也是延遲綁定的。
<?php
class A{
const X=1;
const Y=self::X;
}
class B extends A{
const X=1.0;
}
var_dump(B::Y); // float(1.0)
?>
至少在 PHP 5.3.0a2 中,有一個函數 get_called_class(),它會傳回呼叫靜態方法的類別。
<?php
class a {
static public function test() {
print get_called_class();
}
}
class b extends a {
}
a::test(); // "a"
b::test(); // "b"
?>
關於靜態參數,它們如預期般運作。
<?php
class A {
protected static $__CLASS__ = __CLASS__;
public static function constructor(){
return static::$__CLASS__;
}
}
class B extends A {
protected static $__CLASS__ = __CLASS__;
}
echo B::constructor(); // B
?>
只是快速提醒您要經常檢查您的語法。雖然我很喜歡 LSB,但我以為它沒有運作。
static::$sKey = 未設定
…直到我意識到我完全忘記將它設為變數變數。
$sKey = 'testStaticClassVarNameThatExistsInThisClassesScope';
static::$$sKey = 已設定
…當然,這適用於 PHP 的任何地方,但由於延遲靜態綁定(目前)是新的,我看到其他人寫了很多程式碼都有這個特定的錯誤。
PHP5.3 無法使用,但又需要 'static',所以我做了以下動作。
有任何異議嗎?我個人討厭使用 eval() 陳述式...
<?php
class mother
{
function setStatic( $prop, $val ) {
// 在此之後,self:: 指的是 mother,但下一個 $class 指的是...
//
$class = get_class( $this );
eval( "$class::\$$prop = \$$val;" );
}
}
class child extends mother
{
protected static $sProp;
function writer( $value ) {
parent::setStatic( 'sProp', $value );
}
function reader()
{
return self::$sProp;
}
}
$c = new child();
$c->writer( 3 );
echo $c->reader(); // 3
?>
我發現了一件有趣的事情。類別名稱字串必須直接從「平面」變數存取。從類別實例傳遞的陣列取得變數的延遲靜態綁定程式碼會擲回語法錯誤。是錯誤嗎?
<?php
class A {
public $metadata = array('class' => 'A');
public static function numbers()
{
return 123;
}
}
$instance = new A();
// 這會擲回錯誤
// Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)
var_dump( $instance->metadata['class']::numbers() );
// 取得類別名稱並將其儲存在「平面」變數中,現在就可以了
$class_name = $instance->metadata['class'];
var_dump( $class_name::numbers() );
// 其他測試 -------------------------------------------
$arr = array('class' => 'A');
// 這個也有效。
var_dump( $arr['class']::numbers() );
?>
範例,說明如何僅在子類別中尚未定義時,才從父類別設定靜態屬性,許多人會預期輸出會是「Foo Bar」,但實際上我們得到的是「Foo Foo」。
<?php
class Foo
{
public static string $A;
public static function init() {
return "Foo";
}
public static function get() {
if (!isset(static::$A)) {
static::$A = static::init();
}
return static::$A;
}
}
class Bar extends Foo {
public static function init() {
return "Bar";
}
}
$foo = new Foo();
$bar = new Bar();
echo $foo->get();
echo $bar->get();
?>
輸出
Foo
Foo
這是一個我針對靜態繼承問題所做的小型變通方法。它並不完美,但確實有效。
<?php
// BaseClass 類別將會被任何需要靜態繼承變通方法的類別擴充
class BaseClass {
// 暫時儲存類別名稱,用於 Entry::getStatic() 和 Entry::setNextStatic()
protected static $nextStatic = false;
// 傳回呼叫方法的類別的實際名稱,而不是宣告該方法的類別。
protected static function getStatic() {
// 如果已經儲存
if (self::$nextStatic) {
// 清理並傳回
$class = self::$nextStatic;
self::$nextStatic = false;
return $class;
}
// 初始化
$backTrace = debug_backtrace();
$class = false;
// 逐步檢查
for ($i=0; $i<count($backTrace); $i++) {
// 如果定義了類別
if (isset($backTrace[$i]['class'])) {
// 檢查它是否不是基本類別
if (!in_array($backTrace[$i]['class'], array('BaseClass', 'GenericClass'))) {
return $backTrace[$i]['class'];
} else {
$class = $backTrace[$i]['class'];
}
} else {
// 傳回最後已知的類別
return $class;
}
}
// 預設值
return $class;
}
// 如果在全域環境中呼叫靜態方法,則先前的方法將不起作用,因此我們需要告訴 BaseClass 是哪個類別
public static function setNextStatic($class) {
// 儲存值
self::$nextStatic = $class;
}
}
// 宣告各種靜態方法的通用類別
class GenericClass extends BaseClass {
public static $name = 'Generic';
public function getName() {
$static = get_class_vars(get_class($this));
return $static['name'];
}
public static function basicClassName() {
return self::$name;
}
public static function staticClassName() {
// 取得實際名稱
$staticName = self::getStatic();
// 傳回最終類別名稱
$static = get_class_vars($staticName);
return $static['name'];
}
}
// 最終類別
class SomeClass extends GenericClass {
public static $name = 'Some';
public static function returnClassNameWith($string) {
return $string.' : '.self::staticClassName();
}
}
// 實例呼叫
// 將會印出 'Some'
$a = new SomeClass();
echo 'Name of $a : '.$a->getName().'<br />';
// 靜態呼叫
// 將會印出 'Generic'
echo 'Basic call to SomeClass::$name : '.SomeClass::basicClassName().'<br />';
// 將會印出 'Generic'
echo 'Global call to SomeClass::$name : '.SomeClass::staticClassName().'<br />';
// 將會印出 'Some'
BaseClass::setNextStatic('SomeClass');
echo 'Global call to SomeClass::$name with pre-set : '.SomeClass::staticClassName().'<br />';
// 將會印出 'Some'
echo 'Internal call to SomeClass::$name : '.SomeClass::returnClassNameWith('This is a ').'<br />';
?>
這個變通方法有兩個問題
- 如果您從全域環境呼叫靜態方法,您需要在呼叫方法之前宣告類別的名稱,否則此變通方法將無法運作(請參閱第三個和第四個範例)。但我假設良好的程式設計會在全域範圍內很少呼叫靜態方法,因此如果您使用它,應該很快就能修復此問題。
- 此變通方法無法存取私有或受保護的靜態變數,因為它使用了 get_class_vars()。如果您找到任何更好的解決方案,請告訴我們。
使用 Php 5.3.0,升級將會很容易:只需從基本類別中刪除方法,並將任何對 getStatic() 和 setNextStatic() 的呼叫搜尋/取代為 static:: - 或者可以使用 PHP_VERSION 值上的選擇器來包含具有變通方法的 BaseClass 檔案或使用 static 的 BaseClass 檔案:
我認為這也會很有幫助。
我的問題是,單獨使用 'static' 本身是否可以解析為延遲靜態類別?
我提出這個問題的原因是,它可以協助從基底類別建立衍生類別的新實例,方法是呼叫衍生類別的靜態方法,而不是必須建立衍生類別的新實例 - 或為每個衍生類別明確定義 'getClass' 方法。
範例
<?php
// 我發布的此範例沒有任何實際用途
// 只是一個隨機實作
class Base {
static function useful() {
// 建立衍生類別的實例清單
$list=array();
for ($i=0;$i<10;$i++) $list[]=new static(); // 這是問題所在
return $list;
}
}
class Derived extends Base {
static function somethingElse() {
//...
$list=static::useful();
}
}
?>
我不確定這會對語法分析造成什麼樣的詞法/任何名稱的問題。我不認為它會與您在其他情況下使用 static 的任何情況發生衝突 - 變數/方法宣告。
更重要的是,是否有辦法取得關鍵字 'self'、'parent' 或 'static' 所指的類別名稱?
範例
<?php
class Base {
static function stuff() {
echo "Self: ".get_class(self);
echo "Parent: ".get_class(parent);
echo "Derived: ".get_class(static);
}
}
class Derived extends Base {
static function stuff() {
static::stuff();
}
}
?>
我不認為 PHP 核心為了支援所有這些功能而需要大幅膨脹,但能夠利用 PHP 的動態特性會很好。
還有另一個附註
如果您在基底方法的方法中處於實例層級範圍,並且想要取得頂層靜態,這裡有一個醜陋的變通方法(來自 Thacmus /lib/core.php - 請參閱 SVN 儲存庫)
<?php
// 從類別取得靜態變數的參考 [?]
//$class - 類別名稱或物件 (使用 get_class())
//$var - 不多說了
function& get_static($class,$var) { // 'static_get'?
if (!is_string($class)) $class=get_class($class);
if (!@property_exists($class,$var)) {
trigger_error("靜態屬性不存在: $class::\$$var");
//debug_callstack(); // 這只是 debug_backtrace() 的 HTML 包裝函式
return null;
}
// 儲存一個參考,以便可以參照基礎資料
// 程式碼 [[ return eval('return &'.$class.'::$'.$var.';') ]] 無效 - 無法傳回參考...
// 若要建立參考,請使用 [[ $ref=&get_static(...) ]]
eval('$temp=&'.$class.'::$'.$var.';'); // 使用
return $temp;
}
?>
如果您使用的是 PHP < 5.3.0,您可能會對以下針對延遲靜態綁定的解決方案感興趣:http://de2.php.net/manual/de/function.get-class.php#77698
針對 PHP < 5.3 的 get_called_class
<?php
/**
* 傳回被呼叫的類別名稱
*
* @author Michael Grenier
* @param int $i_level 可選的
* @return string
*/
function get_called_class ($i_level = 1)
{
$a_debug = debug_backtrace();
$a_called = array();
$a_called_function = $a_debug[$i_level]['function'];
for ($i = 1, $n = sizeof($a_debug); $i < $n; $i++)
{
if (in_array($a_debug[$i]['function'], array('eval')) ||
strpos($a_debug[$i]['function'], 'eval()') !== false)
continue;
if (in_array($a_debug[$i]['function'], array('__call', '__callStatic')))
$a_called_function = $a_debug[$i]['args'][0];
if ($a_debug[$i]['function'] == $a_called_function)
$a_called = $a_debug[$i];
}
if (isset($a_called['object']) && isset($a_called['class']))
return (string)$a_called['class'];
$i_line = (int)$a_called['line'] - 1;
$a_lines = explode("\n", file_get_contents($a_called['file']));
preg_match("#([a-zA-Z0-9_]+){$a_called['type']}
{$a_called['function']}( )*\(#", $a_lines[$i_line], $a_match);
unset($a_debug, $a_called, $a_called_function, $i_line, $a_lines);
if (sizeof($a_match) > 0)
$s_class = (string)trim($a_match[1]);
else
$s_class = (string)$a_called['class'];
if ($s_class == 'self')
return get_called_class($i_level + 2);
return $s_class;
}
?>
如果您有一個專案包含許多單例類別,這應該會讓事情變得更簡單和整潔,例如:
<?php
class Singleton {
public static $objInstance;
public static function &getInstance() {
if (self::$objInstance == null) {
$strClass = static::getClass();
self::$objInstance = new $strClass;
}
return self::$objInstance;
}
public static function getClass() {
return __CLASS__;
}
}
class Foo extends Singleton {
public $intBar;
public function __construct() {
$this->intBar = 1;
}
public static function getClass() {
return __CLASS__;
}
}
$objFooTwo = Foo::getInstance();
$objFooTwo->intBar = 2;
$objFooOne = Foo::getInstance();
if ($objFooOne->intBar == $objFooTwo->intBar) {
echo '這是一個單例';
} else {
echo '這不是一個單例';
}
?>
以上將會輸出 '這是一個單例'。這種方法明顯的缺點是無法將引數傳遞給建構函式。
使用 get_called_class() 來建立單例實例的簡單基礎類別。先前由 php at mikebird dot co dot uk 發表的文章解釋了如何執行此操作,但擴充的靜態變數需要您在子類別中定義它們才能運作。
<?php
abstract class Singleton {
private static $instances = array();
public function __construct() {
$class = get_called_class();
if (array_key_exists($class, self::$instances))
trigger_error("嘗試建構類別 \"$class\" 的第二個實例", E_USER_WARNING);
}
public static function getInstance() {
$class = get_called_class();
if (array_key_exists($class, self::$instances) === false)
self::$instances[$class] = new $class();
return self::$instances[$class];
}
}
class A extends Singleton {
}
class B extends Singleton {
}
$a1 = A::getInstance();
$a2 = A::getInstance();
$b1 = B::getInstance();
$b2 = B::getInstance();
if (get_class($a1) == "A" &&
get_class($a2) == "A" &&
get_class($b1) == "B" &&
get_class($b2) == "B" &&
$a1 === $a2 &&
$b1 === $b2)
echo "All good\n";
else
echo "FAIL!\n";
?>
您可能注意到使用了 self:: 而不是 static::,這是因為我們希望靜態變數是私有的,而使用 static:: 將不允許我們這樣做。
這也適用於變數嗎?
如果以下程式碼可以運作的話,那就太棒了
<?php
class A {
protected static $table = "table";
public static function connect(){
// 在這裡做一些事情
echo static::$table;
return static::getInstance(); // function getInstance() 現在可以根據呼叫它的上下文返回類別 A 或 B
}
...
}
class B extends A {
protected static $table = "subtable";
...
}
$table = B::connect(); // 希望輸出將會是:subtable
?>
考慮一下這個
<?php
class A
{
// 一些程式碼....
public static function getInstance()
{
return new self();
}
}
class B extends A
{
//程式碼...
}
$obj = B::getInstance();
// 相較之下
class A
{
// 一些程式碼....
public static function getInstance()
{
return new static();
}
}
class B extends A
{
//程式碼...
}
$obj = B::getInstance();
?>
對於靜態變數和常數也以相同的方式運作
我一直很想看到這個問題得到解決。我非常期待 PHP 5.3 的正式發佈...
在我的例子中,我一直嘗試執行以下操作
class A {
function __construct() {
echo "我是由 " . static::__CLASS__ . " 呼叫的";
}
}
class B extends A {
function Foo() {
echo "我是類別 " . __CLASS__;
}
}
$b = new B; // 應該輸出 "我是由 B 呼叫的"
$b->Foo(); // 應該輸出 "我是類別 B"
目前我執行以下變通方法
class A {
function __construct($child) {
echo "我是由 " . $child . " 呼叫的";
}
}
class B extends A {
function __construct() {
parent::__construct(__CLASS__);
}
function Foo() {
echo "我是類別 " . __CLASS__;
}
}
$b = new B; // 輸出 "我是由 B 呼叫的"
$b->Foo(); // 輸出 "我是類別 B"
如您所見,我目前的變通方法有一些額外開銷,並且不如延遲靜態綁定方法那樣可靠。
嘗試透過單例模式為物件重新建立可繼承的靜態部分。
<?php
/**
* PHP < 5.3 的「可繼承靜態」
* << Library/Inheritable.php >>
*/
abstract class Inheritable_Static extends Singleton
{
}
abstract class Inheritable
{
public static function getStatic($className)
{
// 使用抽象單例
return Singleton::getInstance($className . '_Static') ;
}
public function goStatic()
{
return self::getStatic(get_class($this)) ;
}
}
/**
* 抽象
* << Library/SayIt/Abstract.php >>
*/
abstract class SayIt_Abstract_Static extends Inheritable_Static
{
public $format ;
}
abstract class SayIt_Abstract extends Inheritable
{
protected $_name ;
public function __construct($name)
{
$this->_name = $name ;
}
final public function sayIt()
{
echo sprintf($this->goStatic()->format, $this->_name) . "\n" ;
}
}
/**
* 具體
* << Library/SayIt/Hello.php >>
*/
class SayIt_Hello_Static extends SayIt_Abstract_Static
{
}
class SayIt_Hello extends SayIt_Abstract
{
public static function getStatic() { return parent::getStatic(__CLASS__) ; }
}
/**
* 測試
*/
SayIt_Hello::getStatic()->format = 'Hello %s' ;
$w = new SayIt_Hello('World') ;
$j = new SayIt_Hello('Joe') ;
echo $w->sayIt() ; // Hello World
echo $j->sayIt() ; // Hello Joe
當從父類別取得擴展類別的靜態屬性時,您必須小心,在下面的範例中,您可以看到使用 property_exists(方法 getA2)而不是使用帶有靜態關鍵字的 isset(方法 getA1)來檢查靜態屬性是否存在會產生更直觀的結果
<?php
class Foo
{
public static string $A;
public static function init() {
return static::class;
}
public static function getA1() {
if (!isset(static::$A)) {
static::$A = static::class;
}
return static::$A;
}
public static function getA2() {
if (property_exists(static::class, 'A')) {
static::$A = static::class;
}
return static::$A;
}
}
class Bar extends Foo {}
$foo = new Foo();
echo $foo->getA1();
echo $foo->getA2();
echo $foo->getA1();
$bar = new Bar();
echo $bar->getA1();
echo $bar->getA2();
echo $bar->getA1();
?>
輸出
Foo
Foo
Foo
Foo
Bar
Bar
請注意,$bar->getA1() 返回的是 "Foo" 而非許多人預期看到的 "Bar"。