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 的单例模式提供了一种确保类只有一个实例的方式。通过合理使用单例模式,可以管理共享资源、配置和状态。但应谨慎使用,避免过度依赖全局状态。