---
# System prepended metadata

title: Singleton pattern (單例模式) for PHP
tags: [學習筆記, ' design pattern', ' PHP']

---

---
tags: 學習筆記, design pattern, PHP
---
Singleton pattern (單例模式) for PHP
===

什麼是單例模式
---
單例模式是一種 design pattern，要求一個 Class 只能有一個實例，通常會使用在系統中需要一個全局物件的情況。

在實作時的邏輯就是：「一個 Class 只能有一個儲存自身實例化的靜態變數 `$instance` 、和一個獲得這個變數的靜態方法 `getInstance()` 」
實際在使用時會 call `getInstance()` 這個靜態方法，他會檢查 `$instance` 是否為空，若不為空則回傳，若為空則實例化 Class 賦予 `$instance` 並回傳。

以PHP為例
---
```php=
<?php
final class Singleton
{
    private static $instance;
    
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new Singleton;
        }
        
        return self::$instance;
    }
    
    public $_somethingWeWantToKeep = null;
}
```
我們用 `final` 宣告 class 來確保不會被繼承，並如同上面所說的，建立 static function 來 return 一個存了 class 實體化後物件的 static 變數，最後在 class 中宣告一個變數 `$_somethingWeWantToKeep`。

實際上在使用的情況是這樣的：
```php=
Singleton::getInstance()->_somethingWeWantToKeep = 'I\'m smart!';
```
之後我們在任何地方都可以引用 `Singleton::getInstance()->_somethingWeWantToKeep` ，並且都會得到一樣的 `I'm smart!`

### 這樣就夠了嗎？
在上面的例子中， Singleton 大致上完成了，但還不夠嚴謹。
#### 防止被外部調用
舉一個簡單的例子：
```php=
Singleton::getInstance()->_somethingWeWantToKeep = 'I\'m smart!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep);    //I'm smart!

$stupidGuy = new Singleton;
$stupidGuy->getInstance()->_somethingWeWantToKeep = 'I\'m stupid!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep);    //I'm stupid!
```
上述的情況簡單來說就是在 Singleton 之後，被外部調用並重新賦值，所以我們可以在 class 中新增：
```php=
private function __construct() {}
```
覆寫 `__construct()` 來避免 Singleton 被外部調用。

#### 防止被 clone
還有可能出現另一種情況：
```php=
Singleton::getInstance()->_somethingWeWantToKeep = 'I\'m smart!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep);    //I'm smart!

$stupidGuy = clone Singleton::getInstance();
$stupidGuy->getInstance()->_somethingWeWantToKeep = 'I\'m stupid!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep);    //I'm stupid!
```

這個地方也是發生類似的事情，我們的 Singleton 被 clone 之後調用了 `getInstance()` ，同樣的我們
```php=
private function __clone() {}
```
覆寫 `__clone` 來避免 Singleton 被 clone 。

#### 防止被序列化
最後還有一個可能是序列化(serialize)
```php=
Singleton::getInstance()->_somethingWeWantToKeep = 'I\'m smart!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep);    //I'm smart!

$stupidGuy = serialize(Singleton::getInstance());
$stupidGuy = unserialize($stupidGuy);
$stupidGuy->getInstance()->_somethingWeWantToKeep = 'I\'m stupid!';
var_dump(Singleton::getInstance()->_somethingWeWantToKeep);    //I'm stupid!
```

相對應的防範措施是
```php=
private function __wakeup() {}
```
所以完整的 Singleton 如下：
```php=
<?php
final class Singleton
{
    private static $instance;
    
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new Singleton;
        }
        
        return self::$instance;
    }
    
    public $_somethingWeWantToKeep = null;
    
    private function __construct() {}

    private function __clone() {}

    private function __wakeup() {}
}
```

結語
---
總之我們做的事情就是防止這位 stupidGuy 對這個應該要是單例的 Class 建了物件，然後還呼叫 static function 來修改我們想要保持不變的東西，也就是**在其他開發者在不知情的情況下，建立了副本並修改時，噴 error 給他**。

事實上 Singleton pattern 因為隱藏依賴和難以測試，被廣泛地認為是一種 anti-pattern，可以參考[在 stackoverflow 的討論](https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons)。