請注意,如果您正在尋找用於在函式(或方法)內宣告靜態變數的 static 關鍵字用法,則應閱讀「變數/變數範圍」。我自己直到最近才了解 PHP 的這個知識缺口,並且必須透過 Google 搜尋才能找到答案。我認為此頁面應該有一個指向靜態函式變數的「另請參閱」連結。
https://php.dev.org.tw/manual/en/language.variables.scope.php
將類別屬性或方法宣告為 static,即可在不實例化類別的情況下存取它們。在實例化的類別物件中也可以靜態地存取它們。
由於靜態方法可以在不建立物件實例的情況下呼叫,因此在宣告為 static 的方法內無法使用虛擬變數 $this。
以靜態方式呼叫非靜態方法會擲出 Error。
在 PHP 8.0.0 之前,以靜態方式呼叫非靜態方法已被棄用,並會產生 E_DEPRECATED
警告。
範例 #1 靜態方法範例
<?php
class Foo {
public static function aStaticMethod() {
// ...
}
}
Foo::aStaticMethod();
$classname = 'Foo';
$classname::aStaticMethod();
?>
靜態屬性是透過 範圍解析運算子 (::
) 來存取,並且不能透過物件運算子 (->
) 來存取。
可以使用變數來參考類別。變數的值不能是關鍵字(例如 self
、parent
和 static
)。
範例 #2 靜態屬性範例
<?php
class Foo
{
public static $my_static = 'foo';
public function staticValue() {
return self::$my_static;
}
}
class Bar extends Foo
{
public function fooStatic() {
return parent::$my_static;
}
}
print Foo::$my_static . "\n";
$foo = new Foo();
print $foo->staticValue() . "\n";
print $foo->my_static . "\n"; // 未定義的「屬性」my_static
print $foo::$my_static . "\n";
$classname = 'Foo';
print $classname::$my_static . "\n";
print Bar::$my_static . "\n";
$bar = new Bar();
print $bar->fooStatic() . "\n";
?>
以上範例在 PHP 8 中的輸出類似於
foo foo Notice: Accessing static property Foo::$my_static as non static in /in/V0Rvv on line 23 Warning: Undefined property: Foo::$my_static in /in/V0Rvv on line 23 foo foo foo foo
請注意,如果您正在尋找用於在函式(或方法)內宣告靜態變數的 static 關鍵字用法,則應閱讀「變數/變數範圍」。我自己直到最近才了解 PHP 的這個知識缺口,並且必須透過 Google 搜尋才能找到答案。我認為此頁面應該有一個指向靜態函式變數的「另請參閱」連結。
https://php.dev.org.tw/manual/en/language.variables.scope.php
這裡靜態訪問的屬性會優先使用被調用類別的屬性。而 self 關鍵字則強制只使用當前類別的屬性。參考以下範例
<?php
class a{
static protected $test="class a";
public function static_test(){
echo static::$test; // 結果為 class b
echo self::$test; // 結果為 class a
}
}
class b extends a{
static protected $test="class b";
}
$obj = new b();
$obj->static_test();
?>
static 關鍵字仍然可以在函式內部使用(以非物件導向的方式)。所以如果你需要一個儲存在你的類別中的值,但它非常特定於函式,你可以這樣使用
class aclass {
public static function b(){
static $d=12; // 僅在第一次函式呼叫時設定為 12
$d+=12;
return "$d\n";
}
}
echo aclass::b(); //24
echo aclass::b(); //36
echo aclass::b(); //48
echo aclass::$d; //致命錯誤
要檢查在類別中宣告的方法是否為靜態方法,您可以使用以下程式碼。PHP5 有一個 Reflection Class,非常有用。
try {
$method = new ReflectionMethod( 'className::methodName' );
if ( $method->isStatic() )
{
// 方法是靜態的。
}
}
catch ( ReflectionException $e )
{
// 方法不存在
echo $e->getMessage();
}
*您可以閱讀更多關於 Reflection 類別的資訊:https://php.dev.org.tw/manual/en/class.reflectionclass.php
你誤解了繼承的意義:當你從基底類別繼承時,成員並不會被複製。成員是透過繼承共享的,並且可以根據可見性(公開、保護、私有)由衍生類別存取。
靜態成員和非靜態成員的區別僅在於非靜態成員與類別的實例綁定,而靜態成員與類別綁定,而不是與特定實例綁定。
也就是說,靜態成員由類別的所有實例共享,而非靜態成員則為每個類別實例存在。
因此,在您的範例中,根據物件導向設計的原則,靜態屬性具有正確的值。
class Base
{
public $a;
public static $b;
}
class Derived extends Base
{
public function __construct()
{
$this->a = 0;
parent::$b = 0;
}
public function f()
{
$this->a++;
parent::$b++;
}
}
$i1 = new Derived;
$i2 = new Derived;
$i1->f();
echo $i1->a, ' ', Derived::$b, "\n";
$i2->f();
echo $i2->a, ' ', Derived::$b, "\n";
輸出:
1 1
1 2
這也是可行的:
class Foo {
public static $bar = 'a static property';
}
$baz = (new Foo)::$bar;
echo $baz;
需要注意的是,在「範例 #2」中,您也可以如下方式呼叫一個變數定義的靜態方法:
<?php
class Foo {
public static function aStaticMethod() {
// ...
}
}
$classname = 'Foo';
$methodname = 'aStaticMethod';
$classname::{$methodname}(); // 相信從 PHP 5.3.0 開始支援
?>
理解靜態屬性在類別繼承中的行為非常重要:
- 在父類別和子類別中都定義的靜態屬性,每個類別將持有**不同的**值。在子類別方法中正確使用 self:: 和 static:: 至關重要,才能參考到預期的靜態屬性。
- **僅**在父類別中定義的靜態屬性將共享一個**共同的**值。
<?php
declare(strict_types=1);
class staticparent {
static $parent_only;
static $both_distinct;
function __construct() {
static::$parent_only = 'fromparent';
static::$both_distinct = 'fromparent';
}
}
class staticchild extends staticparent {
static $child_only;
static $both_distinct;
function __construct() {
static::$parent_only = 'fromchild';
static::$both_distinct = 'fromchild';
static::$child_only = 'fromchild';
}
}
$a = new staticparent;
$a = new staticchild;
echo 'Parent: parent_only=', staticparent::$parent_only, ', both_distinct=', staticparent::$both_distinct, "<br/>\r\n";
echo 'Child: parent_only=', staticchild::$parent_only, ', both_distinct=', staticchild::$both_distinct, ', child_only=', staticchild::$child_only, "<br/>\r\n";
?>
將輸出:
Parent: parent_only=fromchild, both_distinct=fromparent
Child: parent_only=fromchild, both_distinct=fromchild, child_only=fromchild
<?php
trait t {
protected $p;
public function testMe() {echo 'static:'.static::class. ' // self:'.self::class ."\n";}
}
class a { use t; }
class b extends a {}
echo (new a)->testMe();
echo (new b)->testMe();
輸出
static:a // self:t
static:b // self:t
靜態變數會在子類別之間共享
<?php
class MyParent {
protected static $variable;
}
class Child1 extends MyParent {
function set() {
self::$variable = 2;
}
}
class Child2 extends MyParent {
function show() {
echo(self::$variable);
}
}
$c1 = new Child1();
$c1->set();
$c2 = new Child2();
$c2->show(); // 顯示 2
?>
在 PHP 5.2.x 或更早的版本中,由於缺乏後期靜態綁定,您可能會在子類別中初始化靜態變數時遇到問題。
<?php
class A {
protected static $a;
public static function init($value) { self::$a = $value; }
public static function getA() { return self::$a; }
}
class B extends A {
protected static $a; // 重新定義 $a 供自己使用
// 繼承 init() 方法
public static function getA() { return self::$a; }
}
B::init('lala');
echo 'A::$a = '.A::getA().'; B::$a = '.B::getA();
?>
這將輸出
A::$a = lala; B::$a =
如果 (幾乎) 所有子類別的 init() 方法都相同,則不需在每個子類別中都實作 init() 方法,如此可避免產生冗餘程式碼。
解決方案 1
將所有成員變數改為非靜態。但是:這會在每個類別物件中產生冗餘資料。
解決方案 2
將 A 類別中的靜態變數 $a 改為陣列,使用子類別的名稱作為索引。如此一來,您就不必為子類別重新定義 $a,並且父類別的 $a 可以設為私有。
DataRecord 類別的簡短範例(無錯誤檢查)
<?php
abstract class DataRecord {
private static $db; // MySQLi 連線,所有子類別都相同
private static $table = array(); // 子類別的資料表陣列
public static function init($classname, $table, $db = false) {
if (!($db === false)) self::$db = $db;
self::$table[$classname] = $table;
}
public static function getDB() { return self::$db; }
public static function getTable($classname) { return self::$table[$classname]; }
}
class UserDataRecord extends DataRecord {
public static function fetchFromDB() {
$result = parent::getDB()->query('select * from '.parent::getTable('UserDataRecord').';');
// 等等...
return $result; // UserDataRecord 物件的陣列
}
}
$db = new MySQLi(...);
UserDataRecord::init('UserDataRecord', 'users', $db);
$users = UserDataRecord::fetchFromDB();
?>
希望這能幫助一些因為某些原因需要在 PHP 5.2.x 伺服器上操作的人。當然,後期靜態綁定讓這種解決方法變得過時。
要檢查函式是否以靜態方式呼叫,您需要執行
<?php
函式 foo () {
$isStatic = !(isset($this) && get_class($this) == __CLASS__);
}
?>
更多資訊請參考 (http://blog.phpdoc.info/archives/4-Schizophrenic-Methods.html)。
(我很快就會將這個添加到手冊中)。
從 PHP 5.3 開始,您可以使用新的 static 關鍵字功能。以下是一個抽象單例類別的範例
<?php
抽象類別 Singleton {
protected static $_instance = NULL;
/**
* 阻止直接建立物件
*/
final private 函式 __construct() { }
/**
* 阻止物件複製
*/
final private 函式 __clone() { }
/**
* 返回新的或現有的 Singleton 實例
* @return Singleton
*/
final public static 函式 getInstance(){
if(null !== static::$_instance){
return static::$_instance;
}
static::$_instance = new static();
return static::$_instance;
}
}
?>
<?php
類別 foo {
private static $getInitial;
public static 函式 getInitial() {
if (self::$getInitial == null)
self::$getInitial = new foo();
return self::$getInitial;
}
}
foo::getInitial();
/*
這是使用帶有靜態方法的新類別的範例..
希望它有幫助
*/
?>
我注意到您不能在 HEREDOC 字串中使用靜態成員。以下程式碼
類別 A
{
public static $BLAH = "user";
函式 __construct()
{
echo <<<EOD
<h1>Hello {self::$BLAH}</h1>
EOD;
}
}
$blah = new A();
在原始碼中產生以下內容
<h1>Hello {self::}</h1>
解決方案
在使用靜態成員之前,將其儲存在區域變數中,如下所示
類別 B
{
public static $BLAH = "user";
函式 __construct()
{
$blah = self::$BLAH;
echo <<<EOD
哈囉 {$blah}
EOD;
}
}
輸出的原始碼會是
哈囉 user
關於類別中複雜靜態變數的初始化,您可以透過建立一個名為 init() 之類的靜態函式,並在類別定義之後立即呼叫它,來模擬靜態建構函式。
<?php
class Example {
private static $a = "Hello";
private static $b;
public static function init() {
self::$b = self::$a . " World!";
}
}
Example::init();
?>
實際上,我們可以說當我們不想建立物件實例時,會使用靜態方法。
例如:
validateEmail($email) {
if(T) return true;
return false;
}
//這樣做不太合理
$obj = new Validate();
$result = $obj->validateEmail($email);
//這樣做更合理
$result = Validate::validateEmail($email);
嗨,這是我簡單的單例模式範例,我想它對某些人可能有用。您可以使用此模式來連接到資料庫,例如。
<?php
類別 MySingleton
{
私有 靜態 $instance = null;
私有 函數 __construct()
{
$this-> name = 'Freddy';
}
公開 靜態 函數 getInstance()
{
if(self::$instance == null)
{
echo "已建立物件!<br>";
self::$instance = new self;
}
return self::$instance;
}
公開 函數 sayHello()
{
echo "嗨,我的名字是 {$this-> name}!<br>";
}
公開 函數 setName($name)
{
$this-> name = $name;
}
}
//
$objA = MySingleton::getInstance(); // 已建立物件!
$objA-> sayHello(); // 嗨,我的名字是 Freddy!
$objA-> setName("Alex");
$objA-> sayHello(); // 嗨,我的名字是 Alex!
$objB = MySingleton::getInstance();
$objB-> sayHello(); // 嗨,我的名字是 Alex!
$objB-> setName("Bob");
$objA-> sayHello(); // 嗨,我的名字是 Bob!
?>
在 PHP 中,使用靜態元素的繼承是一個惡夢。請參考以下程式碼:
<?php
類別 BaseClass{
公開 靜態 $property;
}
類別 DerivedClassOne 繼承 BaseClass{
}
類別 DerivedClassTwo 繼承 BaseClass{
}
DerivedClassOne::$property = "foo";
DerivedClassTwo::$property = "bar";
echo DerivedClassOne::$property; //你可能天真地預期輸出 "foo"...
?>
你預期會輸出什麼? "foo"?錯了。 答案是 "bar"!!!靜態變數不會被繼承,它們指向的是 BaseClass::$property。
在這個例子中,我認為繼承在靜態變數/方法的情況下無法運作,實在很可惜。請記住這一點,並在除錯時節省你的時間。
致上最誠摯的問候 - michal
最簡單的靜態建構子。
因為 PHP 沒有靜態建構子,而您可能需要初始化靜態類別變數,有一個簡單的方法,就是在類別定義之後直接呼叫您自己的函式。
例如:
<?php
function Demonstration()
{
return 'This is the result of demonstration()';
}
class MyStaticClass
{
//public static $MyStaticVar = Demonstration(); //!!! 失敗:語法錯誤
public static $MyStaticVar = null;
public static function MyStaticInit()
{
//這是靜態建構子
//因為在函式中,所有操作都是允許的,包括使用其他函式進行初始化
self::$MyStaticVar = Demonstration();
}
} MyStaticClass::MyStaticInit(); //呼叫靜態建構子
echo MyStaticClass::$MyStaticVar;
//This is the result of demonstration()
?>
如何基於靜態屬性實現單一儲存位置。
<?php
class a {
public function get () {
echo $this->connect();
}
}
class b extends a {
private static $a;
public function connect() {
return self::$a = 'b';
}
}
class c extends a {
private static $a;
public function connect() {
return self::$a = 'c';
}
}
$b = new b ();
$c = new c ();
$b->get();
$c->get();
?>
值得一提的是,靜態變數(以下意義)會持續存在
<?php
class StaticVars
{
public static $a=1;
}
$b=new StaticVars;
$c=new StaticVars;
echo $b::$a; //輸出 1
$c::$a=2;
echo $b::$a; //輸出 2!
?>
請注意,即使 $b 和 $c 是完全不同的物件,$c::$a=2 也會更改 $b::$a 的值。
我使用實例化來直接訪問靜態屬性。
一個簡單的小技巧,您可以應用(使用物件來訪問類別中的靜態屬性)範圍解析運算符
<?php
class Shopinson {
const MY_CONSTANT = 'MY_CONSTANT 的值 ';
}
class Godwin extends Shopinson
{
public static $myconstant = ' Paamayim Nekudotayim 或雙冒號。';
public function SaySomething(){
echo parent::MY_CONSTANT .PHP_EOL; // 輸出:MY_CONSTANT 的值
echo self::$myconstant; // 輸出:Paamayim Nekudotayim 或雙冒號。
}
}
$my_class = new Godwin();
print $my_class::$myconstant;
$my_class::SaySomething();
echo Godwin::$myconstant;
Godwin::SaySomething();
?>
print $my_class::$myconstant;
如果您嘗試編寫這樣做的類別
<?php
class Base
{
static function Foo ()
{
self::Bar();
}
}
class Derived extends Base
{
function Bar ()
{
echo "Derived::Bar()";
}
}
Derived::Foo(); // 我們希望它印出 "Derived::Bar()"
?>
那麼你會發現 PHP 無法做到(除非有人知道正確的方法?),因為 'self::' 指的是擁有程式碼的類別,而不是在運行時實際調用的類別。(__CLASS__ 也不起作用,因為:A. 它不能出現在 :: 之前,以及 B. 它的行為類似於 'self')
但如果您必須這樣做,那麼這裡有一個(只是稍微有點討厭的)解決方法
<?php
類別 Base
{
函式 Foo ( $class = __CLASS__ )
{
call_user_func(array($class,'Bar'));
}
}
類別 Derived 繼承 Base
{
函式 Foo ( $class = __CLASS__ )
{
parent::Foo($class);
}
函式 Bar ()
{
echo "Derived::Bar()";
}
}
Derived::Foo(); // 這次可以正常運作了。
?>
請注意,Base::Foo() 不可以再宣告為 'static',因為靜態方法無法被覆寫(這表示如果錯誤級別包含 E_STRICT,將會觸發錯誤)。
如果 Foo() 需要參數,請在 $class=__CLASS__ 之前列出它們,並且在大多數情況下,您可以在整個程式碼中忽略該參數。
主要的注意事項是,您必須在每個子類別中覆寫 Foo(),並且在呼叫 parent::Foo() 時必須始終包含 $class 參數。
當嘗試實作單例類別時,您可能還想要
a) 將 __clone 設為私有來禁用它
b) 透過定義 __clone 來丟出例外,阻止使用者嘗試複製
<?php
類別 foo
{
公開 靜態 $myStaticClass;
公開 函式 __construct()
{
self::$myStaticClass = new bar();
}
}
類別 bar
{
公開 函式 __construct(){}
}
?>
請注意,這樣做無效。
請使用 self::$myStaticClass = new bar(); 而不是 self::myStaticClass = new bar();(注意 $ 符號)。
我花了一個小時才弄清楚這一點。
選擇的答案解決了問題。有一個有效的用例(設計模式),其中具有靜態成員函式的類別需要呼叫非靜態成員函式,並且在此之前,此靜態成員也應使用建構子來實例化單例。
**案例:**
例如,我正在實作 Swoole HTTP 請求事件,並將其回呼作為具有靜態成員的類別提供。靜態成員執行兩件事;它透過在類別建構子中進行初始化來建立類別的單例物件,而此靜態成員執行的第二件事是呼叫非靜態方法 'run()' 來處理請求(透過與 Phalcon 橋接)。因此,沒有建構子和非靜態呼叫的靜態類別對我來說是行不通的。