想在 PHP 類別裡放一個固定值,或讓多個物件共用一份狀態時,你可能會遇到 conststatic。兩者都用 :: 存取,看起來像失散多年的雙胞胎;但一個是「不能改的規則」,另一個是「可以改的共用狀態」。這篇會用範例帶你分清楚它們的用途、限制,以及該怎麼選,避免讓 AI 幫你寫出一鍋靜態義大利麵。

先用一句話分清楚

const 用來宣告類別常數:值建立後不能重新指定,適合表達固定規則或設定。

static 則是 static 關鍵字的總稱,可用於:

  • 靜態屬性:屬於類別、由同一個類別的程式碼共用,而且可以修改。
  • 靜態方法:不需要物件實例就能呼叫的方法。
  • 函式或方法內的靜態區域變數:在多次呼叫之間保留值。

因此,conststatic 不是同一個層級的東西。const 是一種常數宣告方式;static 是一種「不綁定到單一物件」的語意。

static:共用狀態與不需要物件的方法

靜態屬性

靜態屬性使用 ::$ 存取,例如 Counter::$count。它不屬於某個特定物件,而是屬於類別本身:

<?php

class Counter
{
	public static int $count = 0;

	public static function increment(): void
	{
		self::$count++;
	}
}

Counter::increment();
Counter::increment();

echo Counter::$count; // 2

這裡不需要先 new Counter()。每次呼叫 increment(),都會修改同一份 Counter::$count

要注意,靜態屬性是「共用」,不是「自動安全」。如果不同流程都能任意修改它,程式的狀態就可能變得難以追蹤。共用狀態很方便,方便到有時候會偷偷變成全域變數,然後在除錯時躲在角落喝咖啡。

靜態方法

靜態方法也不需要物件實例,通常適合不依賴物件狀態的工具型操作:

<?php

class TextFormatter
{
	public static function normalize(string $text): string
	{
		return trim(mb_strtolower($text));
	}
}

echo TextFormatter::normalize('  HELLO  '); // hello

靜態方法裡沒有 $this,因為根本沒有「目前這個物件」。如果方法需要讀取或修改某個物件的屬性,就應該改成一般的實例方法。

函式內的靜態變數

static 也可以用在函式或方法內,讓區域變數在同一個 PHP 執行期間的多次呼叫之間保留值:

<?php

function nextNumber(): int
{
	static $number = 0;

	return ++$number;
}

echo nextNumber(); // 1
echo nextNumber(); // 2

這裡的 $number 不是類別屬性,也不是常數;它只是這個函式自己的持久區域狀態。需要注意的是,這種狀態會讓函式不再完全純粹,測試與重設狀態時要多留意。

const:不會改變的類別規則

類別常數在類別主體中使用 const 宣告,透過 ClassName::CONSTANT 或類別內的 self::CONSTANT 存取:

<?php

class HttpStatus
{
	public const OK = 200;
	public const NOT_FOUND = 404;
}

function isSuccess(int $status): bool
{
	return $status === HttpStatus::OK;
}

echo isSuccess(HttpStatus::OK) ? '成功' : '失敗'; // 成功

類別常數的值不能像變數一樣重新指定。下面這種寫法不是更新值,而是錯誤的 PHP 程式碼:

<?php

class Config
{
	public const VERSION = '1.0';
}

// 錯誤:類別常數不能重新指定
Config::VERSION = '2.0';

如果版本需要在執行期間變更,就不該用 const;請改用設定物件、一般屬性,或其他適合管理可變狀態的方式。常數的工作是守規則,不是每天改版號。

常數不能放在方法裡

類別常數要直接宣告在類別主體,不能在方法內使用 const 宣告:

<?php

class Example
{
	// 正確:常數宣告在類別主體
	public const MESSAGE = 'Hello';

	public function show(): string
	{
		return self::MESSAGE;
	}
}

若你需要在函式內建立一次、之後重複使用的可變狀態,那是函式內的 static;若值從設計上就不應改變,才考慮常數。

self::static::$this-> 怎麼選?

這三種寫法常常一起出現,但角色不同:

  • $this->property:存取目前物件的實例屬性。
  • self::$property:存取目前宣告這段程式碼的類別之靜態屬性。
  • static::$property:使用晚期靜態繫結,依照實際呼叫的子類別解析。
  • ClassName::CONSTANT:從類別外存取類別常數。

例如:

<?php

class ParentConfig
{
	public const NAME = 'parent';

	public static function withSelf(): string
	{
		return self::NAME;
	}

	public static function withStatic(): string
	{
		return static::NAME;
	}
}

class ChildConfig extends ParentConfig
{
	public const NAME = 'child';
}

echo ChildConfig::withSelf();   // parent
echo ChildConfig::withStatic(); // child

self:: 指向方法宣告所在的類別;static:: 則會考慮最後實際呼叫的類別。只有在需要支援繼承與晚期靜態繫結時,才使用 static::,不要看到 static 就全部套上去——PHP 不是調味料,什麼都加一匙不會比較香。

staticconst 的差異

比較項目staticconst
主要用途共用狀態、靜態方法、函式內保留狀態宣告不能重新指定的類別常數
是否能重新指定靜態屬性可以;靜態方法不是值不可以
是否需要物件靜態屬性與方法不需要不需要
存取方式ClassName::$propertyClassName::method()ClassName::CONSTANT
是否有 $this靜態方法沒有 $this常數本身沒有方法內容
常見風險共用狀態造成隱藏的相依性把其實會變動的設定錯當成固定值

這裡不列「誰比較快」的排行榜。除非你有實際的效能測量,否則直接宣稱某一種語法一定比較快,通常只是把猜測穿上工程師外套。選擇時先看語意與可維護性;真的遇到效能問題,再用符合實際情境的基準測試驗證。

實際上該怎麼選?

可以用下面幾個問題快速判斷:

  1. 這個值從設計上就不應改變嗎?選類別常數 const
  2. 多個物件需要共用,而且狀態會更新嗎?才考慮靜態屬性 static
  3. 方法完全不需要物件資料嗎?可以考慮靜態方法,但也要衡量測試與相依性是否會變複雜。
  4. 只是想在函式多次呼叫時記住上一次結果嗎?可以使用函式內的 static,但要清楚記錄這份隱藏狀態。
  5. 這其實是某個物件的資料嗎?使用一般屬性與實例方法,通常更容易理解與測試。

一般來說,版本號、固定的狀態碼、固定的限制值,很適合用 const。需要累計的計數器、共用快取或類別層級狀態,才可能使用 static。至於 Singleton,雖然可以用靜態屬性實作,但它會引入全域存取與隱藏相依性,不是看到「只要一個實例」就必須立刻拿出的萬用扳手。

總結

記住這個簡化版:

  • const:這個值是規則,不能重新指定。
  • static:這個成員不綁定單一物件,可以在類別層級使用;若是屬性,值仍然可能改變。
  • $this->:這個資料屬於目前這個物件。

語法記熟只是起點,真正重要的是讓程式碼把「固定規則」和「可變狀態」分清楚。分清楚之後,未來閱讀 PHP 類別時,就不會看到兩個冒號便開始冒冷汗。

參考資料