در برنامهنویسی شیگرا معمولاً هر بار که از new استفاده میکنیم، یک Object جدید ایجاد میشود.
مثلاً:
$config1 = new Config();
$config2 = new Config();
در این حالت:
var_dump($config1 === $config2);
نتیجه:
false
چون دو Object متفاوت ساختهایم.
اما گاهی در طراحی یک برنامه میخواهیم تنها یک Instance از یک Class در کل برنامه وجود داشته باشد و تمام بخشهای برنامه از همان Instance استفاده کنند.
برای چنین مسئلهای میتوان از Singleton Pattern استفاده کرد.
Singleton یکی از الگوهای طراحی Creational است که هدف آن کنترل فرآیند ایجاد Object و اطمینان از وجود یک Instance واحد از یک Class است.
ساختار کلی آن را میتوان اینگونه تصور کرد:
Singleton
│
┌───────┼───────┐
↓ ↓ ↓
Request Service Request
│ │ │
└───────┼───────┘
↓
Same Instance
# چرا Singleton Pattern ایجاد شد؟
فرض کنید یک Class داریم که وضعیت یا منبع مشترکی را مدیریت میکند.
برای مثال:
- Configuration
- یک Registry
- یک Resource خاص
- یک Connection مدیریتشده
- Logger
- Cache Manager
اگر در قسمتهای مختلف برنامه Objectهای جداگانه ایجاد کنیم، ممکن است وضعیت داخلی آنها با یکدیگر متفاوت شود.
مثلاً:
$logger1 = new Logger();
$logger2 = new Logger();
$logger3 = new Logger();
حالا سه Instance مختلف داریم.
Singleton میگوید:
> برای این Class فقط یک Instance ایجاد کن و همان Instance را در اختیار بخشهای مختلف قرار بده.
# ساختار Singleton در PHP
یک Singleton ساده معمولاً سه ویژگی مهم دارد:
- Constructor خصوصی است.
- یک Property استاتیک برای نگهداری Instance داریم.
- یک متد عمومی برای دریافت Instance داریم.
مثال:
class Config
{
private static ?Config $instance = null;
private function __construct()
{
}
public static function getInstance(): Config
{
if (self::$instance === null) {
self::$instance = new Config();
}
return self::$instance;
}
}
حالا:
$config1 = Config::getInstance();
$config2 = Config::getInstance();
بررسی:
var_dump($config1 === $config2);
نتیجه:
true
هر دو متغیر به همان Instance اشاره میکنند.
# چرا Constructor در Singleton خصوصی است؟
اگر Constructor عمومی باشد، هر کسی میتواند مستقیماً Object جدید بسازد:
$config = new Config();
در نتیجه هدف Singleton از بین میرود.
به همین دلیل Constructor را private میکنیم:
private function __construct()
{
}
حالا این کد:
$config = new Config();
با خطا مواجه میشود.
تنها راه دسترسی به Instance این است:
Config::getInstance();
# Singleton چگونه از ایجاد چند Object جلوگیری میکند؟
بخش مهم Singleton این قسمت است:
if (self::$instance === null) {
self::$instance = new Config();
}
در اولین فراخوانی:
Config::getInstance();
چون Instance وجود ندارد:
$instance === null
بنابراین Object ساخته میشود.
در فراخوانیهای بعدی، Instance قبلاً وجود دارد و دیگر Object جدیدی ساخته نمیشود.
در نتیجه:
Call 1
↓
Instance وجود ندارد
↓
Create Instance
↓
Return Instance
Call 2
↓
Instance وجود دارد
↓
Return همان Instance
# یک مثال کاملتر از Singleton در PHP
فرض کنید میخواهیم یک Logger ساده داشته باشیم:
class Logger
{
private static ?Logger $instance = null;
private function __construct()
{
}
public static function getInstance(): Logger
{
if (self::$instance === null) {
self::$instance = new Logger();
}
return self::$instance;
}
public function log(string $message): void
{
file_put_contents(
'app.log',
$message . PHP_EOL,
FILE_APPEND
);
}
}
حالا:
$logger = Logger::getInstance();
$logger->log('User logged in');
$logger->log('Order created');
از هر جای دیگری نیز میتوان:
Logger::getInstance()->log('Payment completed');
استفاده کرد.
# مزایای Singleton Pattern
Singleton میتواند در شرایط خاص مزایایی داشته باشد.
1. جلوگیری از ایجاد چند Instance
اگر واقعاً فقط یک Instance لازم باشد، Singleton این محدودیت را اعمال میکند.
2. دسترسی به Instance مشترک
تمام قسمتهای برنامه میتوانند Instance مشترک را دریافت کنند.
3. کنترل فرآیند ایجاد Object
فرآیند ایجاد Object داخل خود Class کنترل میشود.
4. جلوگیری از ایجاد منابع تکراری
اگر Object ایجادشده مدیریت یک Resource خاص را بر عهده داشته باشد، Singleton میتواند از ایجاد Instanceهای غیرضروری جلوگیری کند.
اما این مزایا به این معنی نیستند که Singleton همیشه انتخاب مناسبی است.
# معایب Singleton Pattern چیست؟
Singleton یکی از Patternهایی است که در پروژههای مدرن باید با احتیاط استفاده شود.
مهمترین دلیل، ایجاد وابستگیهای پنهان و نزدیک شدن به Global State است.
# مشکل Global State در Singleton
فرض کنید در یک پروژه چنین کدی داشته باشیم:
Logger::getInstance()->log('Something happened');
از روی امضای متد مشخص نیست که کلاس فعلی به Logger وابسته است.
مثلاً:
class OrderService
{
public function create(): void
{
// Create order
Logger::getInstance()->log(
'Order created'
);
}
}
اگر Constructor کلاس را نگاه کنیم:
public function __construct()
{
}
هیچ وابستگیای دیده نمیشود.
در حالی که OrderService عملاً به Logger وابسته است.
این موضوع میتواند نگهداری و تست کد را دشوارتر کند.
# مشکل Singleton در Unit Test
فرض کنید:
class OrderService
{
public function create(): void
{
Logger::getInstance()->log(
'Order created'
);
}
}
در زمان تست ممکن است بخواهیم Logger واقعی را با یک Mock جایگزین کنیم.
اما چون OrderService مستقیماً Singleton را صدا میزند، جایگزین کردن Dependency دشوارتر میشود.
در مقابل، با Dependency Injection:
class OrderService
{
public function __construct(
private Logger $logger
) {
}
public function create(): void
{
$this->logger->log(
'Order created'
);
}
}
در تست میتوانیم Logger مناسب را به کلاس تزریق کنیم.
# Singleton در مقابل Dependency Injection
این یکی از مهمترین مقایسهها در طراحی PHP است.
Singleton
کلاس خودش تصمیم میگیرد Instance را از کجا دریافت کند:
$logger = Logger::getInstance();
Dependency Injection
Dependency از بیرون وارد Class میشود:
class OrderService
{
public function __construct(
private Logger $logger
) {
}
}
سپس:
$service = new OrderService($logger);
در این حالت وابستگی صریح است.
یعنی با مشاهده Constructor میفهمیم:
OrderService
↓
Logger
# چرا Dependency Injection معمولاً انتخاب بهتری است؟
Dependency Injection چند مزیت مهم دارد:
- وابستگیها واضح هستند.
- تست کردن سادهتر میشود.
- Mock کردن Dependencyها راحتتر است.
- Coupling کمتر میشود.
- تغییر Implementation سادهتر میشود.
- Container میتواند Lifecycle Object را مدیریت کند.
این موضوع دقیقاً با مقاله قبلی ما درباره Dependency Injection در PHP ارتباط دارد.
# آیا Dependency Injection یعنی هر بار Object جدید بسازیم؟
خیر.
این تصور اشتباه است.
Dependency Injection فقط درباره نحوه دریافت Dependency است.
اینکه یک Dependency:
- Singleton باشد،
- یک Instance در هر Request باشد،
- یا هر بار Instance جدید داشته باشد،
به نحوه مدیریت Lifecycle آن بستگی دارد.
Frameworkهایی مانند Laravel میتوانند این Lifecycle را از طریق Service Container مدیریت کنند.
# Singleton و Interface
میتوان Singleton را با Interface نیز ترکیب کرد.
مثلاً:
interface CacheInterface
{
public function get(string $key): mixed;
public function set(
string $key,
mixed $value
): void;
}
پیادهسازی:
class CacheManager implements CacheInterface
{
private static ?CacheManager $instance = null;
private function __construct()
{
}
public static function getInstance(): CacheManager
{
if (self::$instance === null) {
self::$instance = new CacheManager();
}
return self::$instance;
}
public function get(string $key): mixed
{
return null;
}
public function set(
string $key,
mixed $value
): void {
// Store value
}
}
اما حتی در چنین حالتی نیز باید بررسی کنیم که آیا واقعاً Singleton لازم است یا Dependency Injection گزینه مناسبتری است.
# Singleton با Static Method چه تفاوتی دارد؟
این دو مفهوم یکی نیستند.
مثلاً:
Config::get('app.name');
یک Static Method است.
اما Singleton یک الگوی طراحی برای کنترل Instance است.
در Singleton ممکن است متد Static داشته باشیم:
Config::getInstance();
که Instance را برمیگرداند.
پس:
Static Method
≠
Singleton Pattern
هر Static Class یا Static Method یک Singleton نیست.
# Singleton و Thread Safety
در PHP معمولی وب، بسیاری از درخواستها Lifecycle جداگانه دارند. بنابراین نباید Singleton PHP را با یک Object واحد که در تمام کاربران و تمام درخواستهای یک برنامه همیشه مشترک است اشتباه بگیریم.
برای مثال:
Config::getInstance();
به معنی این نیست که تمام کاربران سایت در تمام درخواستها الزاماً همان Object داخل حافظه را دارند.
نحوه اجرای PHP، نوع Runtime، Process Model و معماری برنامه روی Lifecycle حافظه تأثیر میگذارد.
بنابراین Singleton یک مفهوم مربوط به Scope و Lifecycle اجرای برنامه است، نه الزاماً یک Object جهانی در تمام سرور.
# Singleton در پروژههای PHP با اجرای طولانی
در Runtimeهای سنتی PHP، معمولاً وضعیت حافظه بین Requestها مانند یک برنامه دائماً در حال اجرا باقی نمیماند.
اما در محیطهای Long-Running مانند برخی کاربردهای:
- PHP Workers
- Server Workers
- Application Servers
- Laravel Octane
موضوع Lifecycle اهمیت بیشتری پیدا میکند.
در چنین محیطهایی نگهداری State داخل Singleton میتواند باعث رفتارهای ناخواسته شود، اگر تصور کنیم Object در هر Request از ابتدا ساخته میشود.
بنابراین در محیطهای Long-Running باید به State و Lifecycle Objectها دقت بیشتری داشت.
# Singleton در Laravel
در Laravel معمولاً لازم نیست برای مدیریت یک Instance مشترک، خودمان Singleton Pattern را از صفر پیادهسازی کنیم.
Laravel دارای Service Container است که امکان مدیریت Dependencyها و Bindingها را فراهم میکند.
مثلاً میتوانیم یک Interface داشته باشیم:
interface PaymentGateway
{
public function pay(int $amount): void;
}
و آن را در Container ثبت کنیم.
برای مثال در یک Service Provider:
$this->app->singleton(
PaymentGateway::class,
function () {
return new ZarinpalGateway();
}
);
در این حالت Laravel مدیریت Instance را بر عهده میگیرد.
سپس میتوانیم Dependency را تزریق کنیم:
class OrderService
{
public function __construct(
private PaymentGateway $gateway
) {
}
}
این روش معمولاً از پیادهسازی دستی Singleton در تمام بخشهای پروژه تمیزتر است.
# تفاوت Singleton دستی و Singleton در Service Container
در Singleton دستی:
Logger::getInstance();
خود Class مسئول Lifecycle خودش است.
اما با Container:
Application
↓
Service Container
↓
Logger
Container مسئول مدیریت Dependency و Lifecycle آن است.
این جداسازی مسئولیت یکی از دلایلی است که Container در Frameworkهای مدرن اهمیت زیادی دارد.
# آیا Singleton در Laravel بد است؟
نمیتوان گفت Singleton همیشه بد است.
مثلاً اگر واقعاً یک Service باید در Scope مشخصی فقط یک Instance داشته باشد، Binding از نوع Singleton در Container میتواند منطقی باشد.
مسئله اصلی، استفاده بیدلیل از Singleton به عنوان یک Global Access Point است.
به طور مثال این:
Logger::getInstance()->log('...');
از نظر معماری معمولاً وابستگی پنهانتری نسبت به این دارد:
class OrderService
{
public function __construct(
private Logger $logger
) {
}
}
# چه زمانی استفاده از Singleton منطقی است؟
Singleton میتواند زمانی مناسب باشد که:
- واقعاً فقط یک Instance در Scope مورد نظر نیاز دارید.
- Lifecycle Object باید کنترل شود.
- ایجاد چند Instance منطقی نیست.
- Object دارای State نامناسب برای چند Instance است.
- استفاده از Container یا روش مناسب دیگری برای مدیریت Lifecycle در دسترس نیست.
اما قبل از آن باید بپرسید:
> آیا واقعاً Singleton لازم دارم یا فقط دنبال دسترسی آسان به یک Service هستم؟
اگر دلیل فقط «دسترسی آسان» باشد، احتمالاً Singleton انتخاب خوبی نیست.
# چه زمانی نباید از Singleton استفاده کنیم؟
اگر هدف شما فقط این است که:
> از هر جای پروژه بتوانم به این کلاس دسترسی داشته باشم.
Singleton احتمالاً راهکار مناسبی نیست.
در چنین شرایطی بهتر است از:
- Dependency Injection
- Service Container
- Interface
- Service Provider
- معماری مناسب Application
استفاده کنید.
# Singleton و SOLID
استفاده بیش از حد از Singleton میتواند با برخی اصول SOLID در تضاد قرار بگیرد.
بهخصوص وقتی یک کلاس Singleton:
- Dependencyهای زیادی را پنهان میکند.
- مسئولیتهای متعددی دارد.
- به Global State تبدیل میشود.
از طرف دیگر، اگر Singleton صرفاً برای مدیریت Lifecycle یک Resource خاص و در Scope مناسب استفاده شود، لزوماً به معنی طراحی بد نیست.
اصل مهم این است که Pattern باید در خدمت معماری باشد، نه اینکه معماری را مجبور به استفاده از Pattern کنیم.
# Singleton Pattern و Design Pattern
Singleton در دسته Creational Design Patterns قرار میگیرد.
چون هدف اصلی آن کنترل فرآیند ایجاد و دسترسی به Instance است.
در کنار Singleton، Patternهای دیگری مانند:
- Factory
- Builder
- Prototype
- Abstract Factory
نیز در دسته Creational قرار میگیرند.
در مقاله جامع Design Pattern در PHP درباره دستهبندی Patternها و تفاوت آنها صحبت کردیم.
# مقایسه Singleton و Factory
Singleton و Factory هر دو در دسته Creational قرار دارند، اما مسئله متفاوتی را حل میکنند.
| ویژگی | Singleton | Factory |
|---|---|---|
| هدف | یک Instance | ایجاد Object مناسب |
| تمرکز | Lifecycle | Creation |
| انتخاب بین چند Implementation | معمولاً خیر | بله |
| مناسب چند نوع Object | معمولاً خیر | بله |
| استفاده از Interface | ممکن است | بسیار رایج |
| ریسک Global State | نسبتاً بالا | کمتر |
مثلاً:
Singleton
↓
One Logger Instance
در مقابل:
Factory
↓
┌───────┬───────┐
↓ ↓ ↓
SMS Email Push
# Singleton یا Factory؛ کدام را انتخاب کنیم؟
اگر سؤال شما این است:
> چطور Object مناسب را ایجاد کنم؟
به Factory فکر کنید.
اگر سؤال شما این است:
> چطور Lifecycle یک Instance مشخص را مدیریت کنم؟
Singleton یا مدیریت Lifecycle توسط Container ممکن است راهکار باشد.
اما در پروژههای مدرن PHP بهتر است ابتدا Dependency Injection و Container را بررسی کنید.
# یک مثال واقعی: سیستم تنظیمات
فرض کنید برنامه تنظیمات زیادی دارد:
class Config
{
private static ?Config $instance = null;
private array $items = [];
private function __construct()
{
$this->items = [
'app_name' => 'CMS Nevis',
'timezone' => 'Asia/Tehran',
];
}
public static function getInstance(): Config
{
if (self::$instance === null) {
self::$instance = new Config();
}
return self::$instance;
}
public function get(string $key): mixed
{
return $this->items[$key] ?? null;
}
}
استفاده:
$config = Config::getInstance();
echo $config->get('app_name');
این مثال مفهوم Singleton را به خوبی نشان میدهد، اما در یک پروژه واقعی معمولاً بهتر است مدیریت Configuration را به ابزارها و معماری مناسب خود Framework واگذار کنیم.
# چگونه Singleton را تست کنیم؟
اول میتوانیم بررسی کنیم که دو فراخوانی یک Instance برمیگردانند:
$config1 = Config::getInstance();
$config2 = Config::getInstance();
assert($config1 === $config2);
این تست نشان میدهد که Singleton به هدف اصلی خود عمل میکند.
اما تست رفتار کلاس Singleton میتواند دشوارتر باشد، مخصوصاً اگر سایر Classها مستقیماً آن را فراخوانی کنند.
به همین دلیل Dependency Injection معمولاً تستپذیری بهتری ایجاد میکند.
# اشتباهات رایج در Singleton
1. استفاده از Singleton برای همه چیز
هر Class نیازی به Singleton ندارد.
2. استفاده از Singleton به عنوان Global Variable
اگر همه قسمتهای برنامه از طریق Singleton به State مشترک دسترسی داشته باشند، معماری به سمت Global State میرود.
3. مخفی کردن Dependencyها
این کد:
Logger::getInstance();
وابستگی را از Constructor خارج میکند و آن را پنهان میکند.
4. نادیده گرفتن Lifecycle
در محیطهای Long-Running باید بدانیم Instance چه مدت در حافظه باقی میماند.
5. استفاده از Singleton فقط برای راحتی
«راحتتر بودن دسترسی» دلیل کافی برای استفاده از Singleton نیست.
# آیا Singleton باعث افزایش Performance میشود؟
نباید Singleton را صرفاً به عنوان یک تکنیک Performance در نظر گرفت.
Singleton میتواند از ایجاد Instanceهای تکراری جلوگیری کند، اما این موضوع لزوماً به معنی سریعتر شدن کل برنامه نیست.
در بسیاری از پروژهها، پیچیدگی معماری و مدیریت State مهمتر از صرفهجویی احتمالی در ایجاد یک Object است.
بنابراین ابتدا باید مسئله واقعی را مشخص کنیم و بعد Pattern را انتخاب کنیم.
# جمعبندی Singleton Pattern در PHP
Singleton Pattern یکی از معروفترین الگوهای طراحی Creational است که برای محدود کردن ایجاد یک Class به یک Instance در Scope مشخص استفاده میشود.
پیادهسازی ساده آن شامل:
private static ?ClassName $instance = null;
و:
private function __construct()
{
}
و متدی مانند:
public static function getInstance()
{
// ...
}
است.
اما مهمتر از نحوه پیادهسازی، دانستن زمان مناسب استفاده از Singleton است.
Singleton میتواند در بعضی شرایط مفید باشد، اما استفاده بیش از حد از آن ممکن است:
- Global State ایجاد کند.
- Dependencyها را پنهان کند.
- Unit Test را سختتر کند.
- Coupling را افزایش دهد.
- مدیریت Lifecycle را پیچیده کند.
در پروژههای مدرن PHP، مخصوصاً Laravel، معمولاً بهتر است ابتدا Dependency Injection و Service Container را بررسی کنیم و مدیریت Lifecycle را تا حد امکان به Container بسپاریم.
بنابراین یک قانون ساده را به خاطر داشته باشید:
> Singleton را فقط به دلیل اینکه میتوانید پیادهسازی کنید، استفاده نکنید؛ زمانی از آن استفاده کنید که محدودیت یک Instance واقعاً بخشی از نیاز طراحی سیستم باشد.
# سوالات متداول Singleton Pattern در PHP
Singleton Pattern در PHP چیست؟
Singleton یک Design Pattern از دسته Creational است که برای محدود کردن ایجاد یک Class به یک Instance در Scope مشخص استفاده میشود.
چگونه Singleton را در PHP پیادهسازی کنیم؟
معمولاً Constructor را private میکنیم، یک Property استاتیک برای نگهداری Instance قرار میدهیم و یک متد Static برای دریافت Instance ایجاد میکنیم.
آیا Singleton در PHP بد است؟
خیر، اما استفاده نادرست از آن میتواند باعث Global State، وابستگی پنهان و کاهش تستپذیری شود.
آیا Singleton بهتر از Dependency Injection است؟
این دو دقیقاً جایگزین یکدیگر نیستند. Dependency Injection روش بهتری برای بیان و مدیریت وابستگیهاست و در بسیاری از پروژههای مدرن گزینه مناسبتری نسبت به دسترسی مستقیم به Singleton است.
آیا Laravel از Singleton استفاده میکند؟
Laravel Service Container امکان ثبت Bindingهای Singleton را فراهم میکند و میتواند Lifecycle یک Service را مدیریت کند.
آیا Singleton در تمام درخواستهای PHP مشترک است؟
خیر. Singleton به Scope و Lifecycle محیط اجرای PHP وابسته است و نباید آن را به معنی یک Object مشترک دائمی بین تمام کاربران و تمام درخواستها در نظر گرفت.
آیا Singleton باعث افزایش سرعت PHP میشود؟
هدف اصلی Singleton مدیریت Creation و Lifecycle Object است، نه افزایش مستقیم Performance. استفاده نادرست از آن حتی میتواند پیچیدگی برنامه را افزایش دهد.
تفاوت Singleton و Factory چیست؟
Singleton روی داشتن یک Instance تمرکز دارد، در حالی که Factory روی انتخاب و ایجاد Object مناسب تمرکز میکند.





