Laravel 13 单例模式深度解析
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨 Laravel 13 中单例模式的高级用法。
单例模式基础
什么是单例模式
单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
namespace App\Singletons;
class Singleton { private static ?Singleton $instance = null;
private function __construct() { }
private function __clone(): void { }
public function __wakeup(): void { throw new \Exception('Cannot unserialize singleton'); }
public static function getInstance(): Singleton { if (self::$instance === null) { self::$instance = new self(); }
return self::$instance; } }
|
Laravel 服务容器单例
绑定单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider; use App\Services\ConfigurationService; use App\Services\CacheManager; use App\Services\LoggerService;
class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->singleton(ConfigurationService::class, function ($app) { return new ConfigurationService( config('app'), $app->make('cache') ); });
$this->app->singleton(CacheManager::class, function ($app) { return new CacheManager($app['config']); });
$this->app->singleton(LoggerService::class); } }
|
单例服务示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| <?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class ConfigurationService { protected array $config = []; protected bool $loaded = false;
public function __construct( protected array $appConfig, protected $cache ) { $this->load(); }
protected function load(): void { if ($this->loaded) { return; }
$cached = $this->cache->get('app:configuration');
if ($cached) { $this->config = $cached; } else { $this->config = $this->loadFromDatabase(); $this->cache->put('app:configuration', $this->config, 3600); }
$this->loaded = true; }
public function get(string $key, mixed $default = null): mixed { return $this->config[$key] ?? $default; }
public function set(string $key, mixed $value): void { $this->config[$key] = $value; $this->persist(); }
public function all(): array { return $this->config; }
public function refresh(): void { $this->cache->forget('app:configuration'); $this->loaded = false; $this->load(); }
protected function loadFromDatabase(): array { return \App\Models\Configuration::pluck('value', 'key')->toArray(); }
protected function persist(): void { $this->cache->put('app:configuration', $this->config, 3600); } }
|
连接池单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| <?php
namespace App\Services;
use Redis;
class RedisConnectionPool { protected static ?RedisConnectionPool $instance = null; protected array $connections = []; protected array $config;
private function __construct(array $config) { $this->config = $config; }
public static function getInstance(array $config = []): RedisConnectionPool { if (self::$instance === null) { self::$instance = new self($config); }
return self::$instance; }
public function getConnection(string $name = 'default'): Redis { if (!isset($this->connections[$name])) { $this->connections[$name] = $this->createConnection($name); }
return $this->connections[$name]; }
protected function createConnection(string $name): Redis { $config = $this->config[$name] ?? $this->config['default'] ?? [];
$redis = new Redis(); $redis->connect( $config['host'] ?? '127.0.0.1', $config['port'] ?? 6379 );
if (isset($config['password'])) { $redis->auth($config['password']); }
if (isset($config['database'])) { $redis->select($config['database']); }
return $redis; }
public function closeAll(): void { foreach ($this->connections as $connection) { $connection->close(); }
$this->connections = []; }
public function __destruct() { $this->closeAll(); } }
|
状态管理单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <?php
namespace App\Services;
class ApplicationState { protected array $state = []; protected array $history = []; protected int $maxHistory = 100;
public function get(string $key, mixed $default = null): mixed { return $this->state[$key] ?? $default; }
public function set(string $key, mixed $value): void { $oldValue = $this->state[$key] ?? null;
$this->history[] = [ 'action' => 'set', 'key' => $key, 'old_value' => $oldValue, 'new_value' => $value, 'timestamp' => now(), ];
$this->state[$key] = $value;
$this->trimHistory(); }
public function has(string $key): bool { return array_key_exists($key, $this->state); }
public function remove(string $key): void { if ($this->has($key)) { $this->history[] = [ 'action' => 'remove', 'key' => $key, 'old_value' => $this->state[$key], 'timestamp' => now(), ];
unset($this->state[$key]); } }
public function all(): array { return $this->state; }
public function clear(): void { $this->history[] = [ 'action' => 'clear', 'old_state' => $this->state, 'timestamp' => now(), ];
$this->state = []; }
public function getHistory(): array { return $this->history; }
protected function trimHistory(): void { if (count($this->history) > $this->maxHistory) { $this->history = array_slice($this->history, -$this->maxHistory); } } }
|
日志管理单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| <?php
namespace App\Services;
use Psr\Log\LoggerInterface; use Illuminate\Support\Facades\Log;
class LogManager { protected static ?LogManager $instance = null; protected array $channels = []; protected array $context = []; protected array $processors = [];
private function __construct() { }
public static function getInstance(): LogManager { if (self::$instance === null) { self::$instance = new self(); }
return self::$instance; }
public function channel(string $name = null): LoggerInterface { $name = $name ?? config('logging.default');
if (!isset($this->channels[$name])) { $this->channels[$name] = Log::channel($name); }
return $this->channels[$name]; }
public function addContext(string $key, mixed $value): self { $this->context[$key] = $value; return $this; }
public function addProcessor(callable $processor): self { $this->processors[] = $processor; return $this; }
public function log(string $level, string $message, array $context = []): void { $context = array_merge($this->context, $context);
foreach ($this->processors as $processor) { $context = $processor($context); }
$this->channel()->{$level}($message, $context); }
public function emergency(string $message, array $context = []): void { $this->log('emergency', $message, $context); }
public function error(string $message, array $context = []): void { $this->log('error', $message, $context); }
public function warning(string $message, array $context = []): void { $this->log('warning', $message, $context); }
public function info(string $message, array $context = []): void { $this->log('info', $message, $context); }
public function debug(string $message, array $context = []): void { $this->log('debug', $message, $context); } }
|
配置管理单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| <?php
namespace App\Services;
class FeatureFlagManager { protected static ?FeatureFlagManager $instance = null; protected array $flags = []; protected array $overrides = []; protected bool $loaded = false;
private function __construct() { $this->load(); }
public static function getInstance(): FeatureFlagManager { if (self::$instance === null) { self::$instance = new self(); }
return self::$instance; }
protected function load(): void { if ($this->loaded) { return; }
$this->flags = cache()->remember('feature_flags', 300, function () { return \App\Models\FeatureFlag::all() ->keyBy('key') ->map(fn($flag) => [ 'enabled' => $flag->enabled, 'rollout' => $flag->rollout_percentage, 'conditions' => $flag->conditions, ]) ->toArray(); });
$this->loaded = true; }
public function isEnabled(string $flag, $user = null): bool { if (isset($this->overrides[$flag])) { return $this->overrides[$flag]; }
if (!isset($this->flags[$flag])) { return false; }
$config = $this->flags[$flag];
if (!$config['enabled']) { return false; }
if ($user && $config['rollout'] < 100) { return $this->checkRollout($flag, $user, $config['rollout']); }
return true; }
public function override(string $flag, bool $enabled): void { $this->overrides[$flag] = $enabled; }
public function clearOverride(string $flag): void { unset($this->overrides[$flag]); }
public function refresh(): void { cache()->forget('feature_flags'); $this->loaded = false; $this->load(); }
protected function checkRollout(string $flag, $user, int $percentage): bool { $hash = crc32($flag . $user->id); return ($hash % 100) < $percentage; } }
|
性能监控单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| <?php
namespace App\Services;
class PerformanceMonitor { protected static ?PerformanceMonitor $instance = null; protected array $metrics = []; protected array $timers = []; protected int $maxMetrics = 1000;
private function __construct() { }
public static function getInstance(): PerformanceMonitor { if (self::$instance === null) { self::$instance = new self(); }
return self::$instance; }
public function startTimer(string $name): void { $this->timers[$name] = [ 'start' => microtime(true), 'memory' => memory_get_usage(), ]; }
public function stopTimer(string $name): ?float { if (!isset($this->timers[$name])) { return null; }
$timer = $this->timers[$name]; $end = microtime(true); $duration = ($end - $timer['start']) * 1000;
$this->recordMetric($name, [ 'duration_ms' => $duration, 'memory_used' => memory_get_usage() - $timer['memory'], 'timestamp' => now(), ]);
unset($this->timers[$name]);
return $duration; }
public function recordMetric(string $name, array $data): void { $this->metrics[$name][] = $data;
if (count($this->metrics[$name]) > $this->maxMetrics) { $this->metrics[$name] = array_slice($this->metrics[$name], -$this->maxMetrics); } }
public function getMetrics(string $name = null): array { if ($name) { return $this->metrics[$name] ?? []; }
return $this->metrics; }
public function getAverageDuration(string $name): ?float { $metrics = $this->getMetrics($name);
if (empty($metrics)) { return null; }
$durations = array_column($metrics, 'duration_ms');
return array_sum($durations) / count($durations); }
public function clearMetrics(): void { $this->metrics = []; }
public function getSummary(): array { $summary = [];
foreach ($this->metrics as $name => $data) { $durations = array_column($data, 'duration_ms');
$summary[$name] = [ 'count' => count($data), 'avg_duration' => array_sum($durations) / count($durations), 'min_duration' => min($durations), 'max_duration' => max($durations), ]; }
return $summary; } }
|
线程安全单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php
namespace App\Singletons;
class ThreadSafeSingleton { private static ?ThreadSafeSingleton $instance = null; private static bool $initialized = false;
private function __construct() { }
public static function getInstance(): ThreadSafeSingleton { if (self::$instance === null) { $lock = fopen(sys_get_temp_dir() . '/singleton.lock', 'w');
if (flock($lock, LOCK_EX)) { try { if (self::$instance === null) { self::$instance = new self(); self::$initialized = true; } } finally { flock($lock, LOCK_UN); fclose($lock); } } }
return self::$instance; } }
|
多例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
namespace App\Singletons;
class Multiton { private static array $instances = [];
private function __construct() { }
public static function getInstance(string $key): Multiton { if (!isset(self::$instances[$key])) { self::$instances[$key] = new self(); }
return self::$instances[$key]; }
public static function getInstances(): array { return self::$instances; }
public static function removeInstance(string $key): void { unset(self::$instances[$key]); } }
|
数据库连接多例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?php
namespace App\Services;
use PDO;
class DatabaseConnectionManager { private static array $connections = []; private static array $configs;
public static function configure(array $configs): void { self::$configs = $configs; }
public static function getConnection(string $name = 'default'): PDO { if (!isset(self::$connections[$name])) { self::$connections[$name] = self::createConnection($name); }
return self::$connections[$name]; }
protected static function createConnection(string $name): PDO { $config = self::$configs[$name] ?? self::$configs['default'];
$dsn = sprintf( '%s:host=%s;port=%s;dbname=%s;charset=%s', $config['driver'] ?? 'mysql', $config['host'] ?? 'localhost', $config['port'] ?? 3306, $config['database'], $config['charset'] ?? 'utf8mb4' );
$pdo = new PDO($dsn, $config['username'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]);
return $pdo; }
public static function closeConnection(string $name): void { unset(self::$connections[$name]); }
public static function closeAll(): void { self::$connections = []; } }
|
测试单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <?php
namespace Tests\Unit\Singletons;
use Tests\TestCase; use App\Services\ConfigurationService; use App\Services\FeatureFlagManager; use App\Services\PerformanceMonitor;
class SingletonTest extends TestCase { public function test_configuration_service_is_singleton(): void { $instance1 = app(ConfigurationService::class); $instance2 = app(ConfigurationService::class);
$this->assertSame($instance1, $instance2); }
public function test_feature_flag_manager_returns_same_instance(): void { $manager1 = FeatureFlagManager::getInstance(); $manager2 = FeatureFlagManager::getInstance();
$this->assertSame($manager1, $manager2); }
public function test_performance_monitor_tracks_metrics(): void { $monitor = PerformanceMonitor::getInstance();
$monitor->startTimer('test_operation'); usleep(10000); $duration = $monitor->stopTimer('test_operation');
$this->assertGreaterThan(0, $duration);
$metrics = $monitor->getMetrics('test_operation'); $this->assertCount(1, $metrics); }
public function test_multiton_creates_separate_instances(): void { $instance1 = \App\Singletons\Multiton::getInstance('first'); $instance2 = \App\Singletons\Multiton::getInstance('second'); $instance3 = \App\Singletons\Multiton::getInstance('first');
$this->assertNotSame($instance1, $instance2); $this->assertSame($instance1, $instance3); } }
|
最佳实践
1. 使用 Laravel 容器管理单例
1 2 3 4 5
| <?php
$this->app->singleton(MyService::class, function ($app) { return new MyService($app['config']); });
|
2. 避免全局状态污染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
namespace App\Services;
class GoodSingleton { protected array $data = [];
public function getData(string $key): mixed { return $this->data[$key] ?? null; }
public function setData(string $key, mixed $value): void { $this->data[$key] = $value; }
public function reset(): void { $this->data = []; } }
|
3. 考虑依赖注入替代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
namespace App\Services;
interface CacheInterface { public function get(string $key): mixed; public function set(string $key, mixed $value): void; }
class CacheService implements CacheInterface { protected array $cache = [];
public function get(string $key): mixed { return $this->cache[$key] ?? null; }
public function set(string $key, mixed $value): void { $this->cache[$key] = $value; } }
|
总结
Laravel 13 的单例模式提供了一种确保类只有一个实例的方式。通过合理使用单例模式,可以管理共享资源、配置和状态。但应谨慎使用,避免过度依赖全局状态。