15
15
class AsyncDB
16
16
{
17
17
private static bool $ isInitialized = false ;
18
+ private static bool $ hasValidationError = false ;
18
19
19
20
/**
20
21
* The core of the new design: A private, self-configuring initializer.
21
22
* This method is called by every public method to ensure the system is ready.
23
+ * Validates only once if successful, but re-validates if there were previous errors.
22
24
*/
23
25
private static function initializeIfNeeded (): void
24
26
{
25
- if (self ::$ isInitialized ) {
27
+ if (self ::$ isInitialized && ! self :: $ hasValidationError ) {
26
28
return ;
27
29
}
28
30
29
- $ configLoader = ConfigLoader::getInstance ();
30
-
31
- $ dbConfigAll = $ configLoader ->get ('database ' );
32
-
33
- if ($ dbConfigAll === null ) {
34
- throw new \RuntimeException ("Database configuration not found. Ensure 'config/database.php' exists in your project root. " );
35
- }
36
-
37
- $ defaultConnection = $ dbConfigAll ['default ' ];
38
- $ connectionConfig = $ dbConfigAll ['connections ' ][$ defaultConnection ] ?? null ;
39
- $ poolSize = $ dbConfigAll ['pool_size ' ] ?? 10 ;
40
-
41
- if ($ connectionConfig === null ) {
42
- throw new \RuntimeException ("Default database connection ' {$ defaultConnection }' not defined in your database config. " );
31
+ self ::$ hasValidationError = false ;
32
+
33
+ try {
34
+ $ configLoader = ConfigLoader::getInstance ();
35
+ $ dbConfigAll = $ configLoader ->get ('database ' );
36
+
37
+ if (!is_array ($ dbConfigAll )) {
38
+ throw new \RuntimeException ("Database configuration not found. Ensure 'config/database.php' exists in your project root. " );
39
+ }
40
+
41
+ $ defaultConnection = $ dbConfigAll ['default ' ] ?? null ;
42
+ if (!is_string ($ defaultConnection )) {
43
+ throw new \RuntimeException ("Default connection name must be a string in your database config. " );
44
+ }
45
+
46
+ $ connections = $ dbConfigAll ['connections ' ] ?? null ;
47
+ if (!is_array ($ connections )) {
48
+ throw new \RuntimeException ("Database connections configuration must be an array. " );
49
+ }
50
+
51
+ if (!isset ($ connections [$ defaultConnection ]) || !is_array ($ connections [$ defaultConnection ])) {
52
+ throw new \RuntimeException ("Default database connection ' {$ defaultConnection }' not defined in your database config. " );
53
+ }
54
+
55
+ $ connectionConfig = $ connections [$ defaultConnection ];
56
+
57
+ /** @var array<string, mixed> $validatedConfig */
58
+ $ validatedConfig = [];
59
+ foreach ($ connectionConfig as $ key => $ value ) {
60
+ if (!is_string ($ key )) {
61
+ throw new \RuntimeException ("Database connection configuration must have string keys only. " );
62
+ }
63
+ $ validatedConfig [$ key ] = $ value ;
64
+ }
65
+
66
+ $ poolSize = 10 ;
67
+ if (isset ($ dbConfigAll ['pool_size ' ])) {
68
+ if (!is_int ($ dbConfigAll ['pool_size ' ]) || $ dbConfigAll ['pool_size ' ] < 1 ) {
69
+ throw new \RuntimeException ("Database pool size must be a positive integer. " );
70
+ }
71
+ $ poolSize = $ dbConfigAll ['pool_size ' ];
72
+ }
73
+
74
+ AsyncPDO::init ($ validatedConfig , $ poolSize );
75
+ self ::$ isInitialized = true ;
76
+
77
+ } catch (\Exception $ e ) {
78
+ self ::$ hasValidationError = true ;
79
+ self ::$ isInitialized = false ;
80
+
81
+ throw $ e ;
43
82
}
44
-
45
- AsyncPDO::init ($ connectionConfig , $ poolSize );
46
- self ::$ isInitialized = true ;
47
83
}
48
84
49
85
/**
@@ -54,6 +90,7 @@ public static function reset(): void
54
90
AsyncPDO::reset ();
55
91
ConfigLoader::reset ();
56
92
self ::$ isInitialized = false ;
93
+ self ::$ hasValidationError = false ;
57
94
}
58
95
59
96
/**
@@ -62,57 +99,65 @@ public static function reset(): void
62
99
public static function table (string $ table ): AsyncQueryBuilder
63
100
{
64
101
self ::initializeIfNeeded ();
65
-
66
102
return new AsyncQueryBuilder ($ table );
67
103
}
68
104
69
105
/**
70
106
* Execute a raw query.
107
+ *
108
+ * @param array<string, mixed> $bindings
109
+ * @return PromiseInterface<array<int, array<string, mixed>>>
71
110
*/
72
111
public static function raw (string $ sql , array $ bindings = []): PromiseInterface
73
112
{
74
113
self ::initializeIfNeeded ();
75
-
76
114
return AsyncPDO::query ($ sql , $ bindings );
77
115
}
78
116
79
117
/**
80
118
* Execute a raw query and return the first result.
119
+ *
120
+ * @param array<string, mixed> $bindings
121
+ * @return PromiseInterface<array<string, mixed>|false>
81
122
*/
82
123
public static function rawFirst (string $ sql , array $ bindings = []): PromiseInterface
83
124
{
84
125
self ::initializeIfNeeded ();
85
-
86
126
return AsyncPDO::fetchOne ($ sql , $ bindings );
87
127
}
88
128
89
129
/**
90
130
* Execute a raw query and return a single scalar value.
131
+ *
132
+ * @param array<string, mixed> $bindings
133
+ * @return PromiseInterface<mixed>
91
134
*/
92
135
public static function rawValue (string $ sql , array $ bindings = []): PromiseInterface
93
136
{
94
137
self ::initializeIfNeeded ();
95
-
96
138
return AsyncPDO::fetchValue ($ sql , $ bindings );
97
139
}
98
140
99
141
/**
100
142
* Execute a raw statement (INSERT, UPDATE, DELETE).
143
+ *
144
+ * @param array<string, mixed> $bindings
145
+ * @return PromiseInterface<int>
101
146
*/
102
147
public static function rawExecute (string $ sql , array $ bindings = []): PromiseInterface
103
148
{
104
149
self ::initializeIfNeeded ();
105
-
106
150
return AsyncPDO::execute ($ sql , $ bindings );
107
151
}
108
152
109
153
/**
110
154
* Run a database transaction.
155
+ *
156
+ * @return PromiseInterface<mixed>
111
157
*/
112
158
public static function transaction (callable $ callback ): PromiseInterface
113
159
{
114
160
self ::initializeIfNeeded ();
115
-
116
161
return AsyncPDO::transaction ($ callback );
117
162
}
118
- }
163
+ }
0 commit comments