1
+ <?php
2
+
3
+ namespace Rcalicdan \FiberAsync \PostgreSQL ;
4
+
5
+ use Rcalicdan \FiberAsync \Api \AsyncPostgreSQL ;
6
+ use Rcalicdan \FiberAsync \Config \PostgresConfigLoader ;
7
+ use Rcalicdan \FiberAsync \Promise \Interfaces \PromiseInterface ;
8
+ use Rcalicdan \FiberAsync \QueryBuilder \PostgresQueryBuilder ;
9
+
10
+ /**
11
+ * DB API - Main entry point for auto-configured async database operations using AsyncPostgreSQL under the hood
12
+ * with asynchronous query builder support.
13
+ *
14
+ * This API automatically loads configuration from .env and config/pgsql/config.php
15
+ * the first time it is used, providing a zero-setup experience for the developer.
16
+ */
17
+ class DB
18
+ {
19
+ private static bool $ isInitialized = false ;
20
+ private static bool $ hasValidationError = false ;
21
+
22
+ /**
23
+ * The core of the new design: A private, self-configuring initializer.
24
+ * This method is called by every public method to ensure the system is ready.
25
+ * Validates only once if successful, but re-validates if there were previous errors.
26
+ */
27
+ private static function initializeIfNeeded (): void
28
+ {
29
+ if (self ::$ isInitialized && !self ::$ hasValidationError ) {
30
+ return ;
31
+ }
32
+
33
+ self ::$ hasValidationError = false ;
34
+
35
+ try {
36
+ $ configLoader = PostgresConfigLoader::getInstance ();
37
+ $ dbConfig = $ configLoader ->get ('config ' );
38
+
39
+ if (!is_array ($ dbConfig )) {
40
+ throw new \RuntimeException ("Postgres configuration not found. Ensure 'config/pgsql/config.php' exists in your project root. " );
41
+ }
42
+
43
+ $ connectionConfig = $ dbConfig ['connection ' ] ?? null ;
44
+ if (!is_array ($ connectionConfig )) {
45
+ throw new \RuntimeException ('Postgres connection configuration must be an array. ' );
46
+ }
47
+
48
+ $ required = ['host ' , 'database ' , 'username ' ];
49
+ foreach ($ required as $ key ) {
50
+ if (!isset ($ connectionConfig [$ key ])) {
51
+ throw new \RuntimeException ("Missing required Postgres connection parameter: {$ key }" );
52
+ }
53
+ }
54
+
55
+ /** @var array<string, mixed> $validatedConfig */
56
+ $ validatedConfig = [];
57
+ foreach ($ connectionConfig as $ key => $ value ) {
58
+ if (!is_string ($ key )) {
59
+ throw new \RuntimeException ('Postgres connection configuration must have string keys only. ' );
60
+ }
61
+ $ validatedConfig [$ key ] = $ value ;
62
+ }
63
+
64
+ $ poolSize = $ dbConfig ['pool_size ' ] ?? 10 ;
65
+ if (!is_int ($ poolSize ) || $ poolSize < 1 ) {
66
+ throw new \RuntimeException ('Postgres pool size must be a positive integer. ' );
67
+ }
68
+
69
+ AsyncPostgreSQL::init ($ validatedConfig , $ poolSize );
70
+ self ::$ isInitialized = true ;
71
+
72
+ } catch (\Exception $ e ) {
73
+ self ::$ hasValidationError = true ;
74
+ self ::$ isInitialized = false ;
75
+
76
+ throw $ e ;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Resets the entire database system. Crucial for isolated testing.
82
+ */
83
+ public static function reset (): void
84
+ {
85
+ AsyncPostgreSQL::reset ();
86
+ PostgresConfigLoader::reset ();
87
+ self ::$ isInitialized = false ;
88
+ self ::$ hasValidationError = false ;
89
+ }
90
+
91
+ /**
92
+ * Start a new query builder instance for the given table.
93
+ */
94
+ public static function table (string $ table ): PostgresQueryBuilder
95
+ {
96
+ self ::initializeIfNeeded ();
97
+
98
+ return new PostgresQueryBuilder ($ table );
99
+ }
100
+
101
+ /**
102
+ * Execute a raw query.
103
+ *
104
+ * @param array<string, mixed> $bindings
105
+ * @return PromiseInterface<array<int, array<string, mixed>>>
106
+ */
107
+ public static function raw (string $ sql , array $ bindings = []): PromiseInterface
108
+ {
109
+ self ::initializeIfNeeded ();
110
+
111
+ return AsyncPostgreSQL::query ($ sql , $ bindings );
112
+ }
113
+
114
+ /**
115
+ * Execute a raw query and return the first result.
116
+ *
117
+ * @param array<string, mixed> $bindings
118
+ * @return PromiseInterface<array<string, mixed>|false>
119
+ */
120
+ public static function rawFirst (string $ sql , array $ bindings = []): PromiseInterface
121
+ {
122
+ self ::initializeIfNeeded ();
123
+
124
+ return AsyncPostgreSQL::fetchOne ($ sql , $ bindings );
125
+ }
126
+
127
+ /**
128
+ * Execute a raw query and return a single scalar value.
129
+ *
130
+ * @param array<string, mixed> $bindings
131
+ * @return PromiseInterface<mixed>
132
+ */
133
+ public static function rawValue (string $ sql , array $ bindings = []): PromiseInterface
134
+ {
135
+ self ::initializeIfNeeded ();
136
+
137
+ return AsyncPostgreSQL::fetchValue ($ sql , $ bindings );
138
+ }
139
+
140
+ /**
141
+ * Execute a raw statement (INSERT, UPDATE, DELETE).
142
+ *
143
+ * @param array<string, mixed> $bindings
144
+ * @return PromiseInterface<int>
145
+ */
146
+ public static function rawExecute (string $ sql , array $ bindings = []): PromiseInterface
147
+ {
148
+ self ::initializeIfNeeded ();
149
+
150
+ return AsyncPostgreSQL::execute ($ sql , $ bindings );
151
+ }
152
+
153
+ /**
154
+ * Run a database transaction.
155
+ *
156
+ * @return PromiseInterface<mixed>
157
+ */
158
+ public static function transaction (callable $ callback ): PromiseInterface
159
+ {
160
+ self ::initializeIfNeeded ();
161
+
162
+ return AsyncPostgreSQL::transaction ($ callback );
163
+ }
164
+ }
0 commit comments