1212use CalebDW \SqlEntities \Grammars \PostgresGrammar ;
1313use CalebDW \SqlEntities \Grammars \SQLiteGrammar ;
1414use CalebDW \SqlEntities \Grammars \SqlServerGrammar ;
15+ use Closure ;
1516use Illuminate \Database \Connection ;
1617use Illuminate \Database \DatabaseManager ;
18+ use Illuminate \Support \Arr ;
1719use Illuminate \Support \Collection ;
1820use Illuminate \Support \ItemNotFoundException ;
1921use InvalidArgumentException ;
2022
23+ /**
24+ * @phpstan-type TEntities Collection<class-string<SqlEntity>, SqlEntity>
25+ */
2126class SqlEntityManager
2227{
2328 use SortsTopologically;
2429
25- /** @var Collection<class-string<SqlEntity>, SqlEntity> */
30+ /**
31+ * The active connection instances.
32+ *
33+ * @var array<string, Connection>
34+ */
35+ protected array $ connections = [];
36+
37+ /** @var TEntities */
2638 public Collection $ entities ;
2739
2840 /**
@@ -32,7 +44,7 @@ class SqlEntityManager
3244 */
3345 protected array $ grammars = [];
3446
35- /** @param Collection<int , SqlEntity> $entities */
47+ /** @param Collection<array-key , SqlEntity> $entities */
3648 public function __construct (
3749 Collection $ entities ,
3850 protected DatabaseManager $ db ,
@@ -51,15 +63,15 @@ public function __construct(
5163 /**
5264 * Get the entity by class.
5365 *
54- * @param class-string<SqlEntity> $name
66+ * @param class-string<SqlEntity> $class
5567 * @throws ItemNotFoundException
5668 */
57- public function get (string $ name ): SqlEntity
69+ public function get (string $ class ): SqlEntity
5870 {
59- $ entity = $ this ->entities ->get ($ name );
71+ $ entity = $ this ->entities ->get ($ class );
6072
6173 if ($ entity === null ) {
62- throw new ItemNotFoundException ("Entity [ {$ name }] not found. " );
74+ throw new ItemNotFoundException ("Entity [ {$ class }] not found. " );
6375 }
6476
6577 return $ entity ;
@@ -77,7 +89,7 @@ public function create(SqlEntity|string $entity): void
7789 $ entity = $ this ->get ($ entity );
7890 }
7991
80- $ connection = $ this ->connection ($ entity );
92+ $ connection = $ this ->connection ($ entity-> connectionName () );
8193
8294 if (! $ entity ->creating ($ connection )) {
8395 return ;
@@ -101,7 +113,7 @@ public function drop(SqlEntity|string $entity): void
101113 $ entity = $ this ->get ($ entity );
102114 }
103115
104- $ connection = $ this ->connection ($ entity );
116+ $ connection = $ this ->connection ($ entity-> connectionName () );
105117
106118 if (! $ entity ->dropping ($ connection )) {
107119 return ;
@@ -113,32 +125,129 @@ public function drop(SqlEntity|string $entity): void
113125 $ entity ->dropped ($ connection );
114126 }
115127
116- /** @param class-string<SqlEntity>|null $type */
117- public function createAll (?string $ type = null , ?string $ connection = null ): void
118- {
128+ /**
129+ * Create all entities.
130+ *
131+ * @param array<int, class-string<SqlEntity>>|class-string<SqlEntity>|null $types
132+ * @param array<int, string>|string|null $connections
133+ */
134+ public function createAll (
135+ array |string |null $ types = null ,
136+ array |string |null $ connections = null ,
137+ ): void {
119138 $ this ->entities
120- ->when ($ connection , fn ($ c ) => $ c ->filter (
121- fn ($ e ) => $ e ->connectionName () === $ connection ,
122- ))
123- ->when ($ type , fn ($ c , $ t ) => $ c ->filter (fn ($ e ) => is_a ($ e , $ t )))
124- ->each (fn ($ e ) => $ this ->create ($ e ));
139+ ->when ($ connections , $ this ->filterByConnections (...))
140+ ->when ($ types , $ this ->filterByTypes (...))
141+ ->each ($ this ->create (...));
125142 }
126143
127- /** @param class-string<SqlEntity>|null $type */
128- public function dropAll (?string $ type = null , ?string $ connection = null ): void
129- {
144+ /**
145+ * Drop all entities.
146+ *
147+ * @param array<int, class-string<SqlEntity>>|class-string<SqlEntity>|null $types
148+ * @param array<int, string>|string|null $connections
149+ */
150+ public function dropAll (
151+ array |string |null $ types = null ,
152+ array |string |null $ connections = null ,
153+ ): void {
130154 $ this ->entities
131155 ->reverse ()
132- ->when ($ connection , fn ($ c ) => $ c ->filter (
133- fn ($ e ) => $ e ->connectionName () === $ connection ,
134- ))
135- ->when ($ type , fn ($ c , $ t ) => $ c ->filter (fn ($ e ) => is_a ($ e , $ t )))
136- ->each (fn ($ e ) => $ this ->drop ($ e ));
156+ ->when ($ connections , $ this ->filterByConnections (...))
157+ ->when ($ types , $ this ->filterByTypes (...))
158+ ->each ($ this ->drop (...));
137159 }
138160
139- protected function connection (SqlEntity $ entity ): Connection
161+ /**
162+ * Execute a callback (in a transaction, if supported) without the specified entities.
163+ *
164+ * @param Closure(Connection): mixed $callback
165+ * @param array<int, class-string<SqlEntity>>|class-string<SqlEntity>|null $types
166+ * @param array<int, string>|string|null $connections
167+ */
168+ public function withoutEntities (
169+ Closure $ callback ,
170+ array |string |null $ types = null ,
171+ array |string |null $ connections = null ,
172+ ): void {
173+ $ defaultConnection = $ this ->db ->getDefaultConnection ();
174+
175+ $ groups = $ this ->entities
176+ ->when ($ connections , $ this ->filterByConnections (...))
177+ ->when ($ types , $ this ->filterByTypes (...))
178+ ->groupBy (fn ($ e ) => $ e ->connectionName () ?? $ defaultConnection );
179+
180+ foreach ($ groups as $ connectionName => $ entities ) {
181+ $ connection = $ this ->connection ($ connectionName );
182+
183+ $ execute = function () use ($ connection , $ entities , $ callback ) {
184+ $ entities
185+ ->reverse ()
186+ ->each ($ this ->drop (...));
187+
188+ $ callback ($ connection );
189+
190+ $ entities ->each ($ this ->create (...));
191+ };
192+
193+ /** @phpstan-ignore identical.alwaysFalse (bad phpdocs) */
194+ if ($ connection ->getSchemaGrammar () === null ) {
195+ $ connection ->useDefaultSchemaGrammar ();
196+ }
197+
198+ $ connection ->getSchemaGrammar ()->supportsSchemaTransactions ()
199+ ? $ connection ->transaction ($ execute )
200+ : $ execute ();
201+ }
202+ }
203+
204+ /**
205+ * Filter entities by connection.
206+ *
207+ * @param TEntities $entities
208+ * @param array<int, class-string<SqlEntity>>|class-string<SqlEntity> $types
209+ * @return TEntities
210+ */
211+ protected function filterByTypes (
212+ Collection $ entities ,
213+ array |string $ types ,
214+ ): Collection {
215+ return $ entities ->filter (function ($ entity ) use ($ types ) {
216+ foreach (Arr::wrap ($ types ) as $ type ) {
217+ if (is_a ($ entity , $ type , allow_string: false )) {
218+ return true ;
219+ }
220+ }
221+
222+ return false ;
223+ });
224+ }
225+
226+ /**
227+ * Filter entities by connection.
228+ *
229+ * @param TEntities $entities
230+ * @param array<int, string>|string $connections
231+ * @return TEntities
232+ */
233+ protected function filterByConnections (
234+ Collection $ entities ,
235+ array |string $ connections ,
236+ ): Collection {
237+ $ default = $ this ->db ->getDefaultConnection ();
238+
239+ return $ entities ->filter (function ($ entity ) use ($ connections , $ default ) {
240+ $ name = $ entity ->connectionName () ?? $ default ;
241+
242+ return in_array ($ name , Arr::wrap ($ connections ), strict: true );
243+ });
244+ }
245+
246+ protected function connection (?string $ name ): Connection
140247 {
141- return $ this ->db ->connection ($ entity ->connectionName ());
248+ $ name ??= $ this ->db ->getDefaultConnection ();
249+
250+ return $ this ->connections [$ name ] ??= $ this ->db ->connection ($ name );
142251 }
143252
144253 protected function grammar (Connection $ connection ): Grammar
0 commit comments