From e9708de5de115288c0d248f664875aa469ab021e Mon Sep 17 00:00:00 2001
From: Sergei Tigrov <rrr-r@ya.ru>
Date: Thu, 4 Jul 2024 09:42:51 +0700
Subject: [PATCH] Using Dependency Injection With Active Record (#370)

---
 README.md                                     |   3 +-
 composer-require-checker.json                 |   3 +-
 composer.json                                 |   2 +
 docs/create-model.md                          |   4 +
 docs/using-di.md                              |  96 ++++++++++++++++
 src/Trait/FactoryTrait.php                    |  59 ++++++++++
 tests/ActiveRecordTest.php                    | 104 ++++++++++++++++++
 tests/Driver/Mssql/ActiveRecordTest.php       |   6 +
 tests/Driver/Mysql/ActiveRecordTest.php       |   6 +
 tests/Driver/Oracle/ActiveRecordTest.php      |   6 +
 tests/Driver/Pgsql/ActiveRecordTest.php       |   6 +
 tests/Driver/Sqlite/ActiveRecordTest.php      |   6 +
 .../ActiveRecord/CustomerWithFactory.php      |  34 ++++++
 tests/Stubs/ActiveRecord/OrderWithFactory.php |  44 ++++++++
 tests/Support/ConnectionHelper.php            |  10 +-
 15 files changed, 378 insertions(+), 11 deletions(-)
 create mode 100644 docs/using-di.md
 create mode 100644 src/Trait/FactoryTrait.php
 create mode 100644 tests/Stubs/ActiveRecord/CustomerWithFactory.php
 create mode 100644 tests/Stubs/ActiveRecord/OrderWithFactory.php

diff --git a/README.md b/README.md
index 208ac78de..ffcea217c 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,8 @@ return [
 ];
 ```
 
-_For more information about how to configure middleware, follow [Middleware Documentation](https://github.com/yiisoft/docs/blob/master/guide/en/structure/middleware.md)_
+_For more information about how to configure middleware, follow 
+[Middleware Documentation](https://github.com/yiisoft/docs/blob/master/guide/en/structure/middleware.md)_
 
 Now you can use the Active Record in the action:
 
diff --git a/composer-require-checker.json b/composer-require-checker.json
index c17694e98..97c5b755f 100644
--- a/composer-require-checker.json
+++ b/composer-require-checker.json
@@ -3,6 +3,7 @@
         "Psr\\Http\\Message\\ResponseInterface",
         "Psr\\Http\\Message\\ServerRequestInterface",
         "Psr\\Http\\Server\\MiddlewareInterface",
-        "Psr\\Http\\Server\\RequestHandlerInterface"
+        "Psr\\Http\\Server\\RequestHandlerInterface",
+        "Yiisoft\\Factory\\Factory"
     ]
 }
diff --git a/composer.json b/composer.json
index dfd088270..8d8629f41 100644
--- a/composer.json
+++ b/composer.json
@@ -45,6 +45,7 @@
         "yiisoft/cache": "^3.0",
         "yiisoft/db-sqlite": "dev-master",
         "yiisoft/di": "^1.0",
+        "yiisoft/factory": "^1.2",
         "yiisoft/json": "^1.0",
         "yiisoft/middleware-dispatcher": "^5.2"
     },
@@ -54,6 +55,7 @@
         "yiisoft/db-pgsql": "For PostgreSQL database support",
         "yiisoft/db-mssql": "For MSSQL database support",
         "yiisoft/db-oracle": "For Oracle database support",
+        "yiisoft/factory": "For factory support",
         "yiisoft/middleware-dispatcher": "For middleware support"
     },
     "autoload": {
diff --git a/docs/create-model.md b/docs/create-model.md
index 791bea951..1c6158377 100644
--- a/docs/create-model.md
+++ b/docs/create-model.md
@@ -289,3 +289,7 @@ $user = $userQuery->where(['id' => 1])->onePopulate();
 $profile = $user->getProfile();
 $orders = $user->getOrders();
 ```
+
+Also see [Using Dependency Injection With Active Record Model](docs/using-di.md).
+
+Back to [README](../README.md)
diff --git a/docs/using-di.md b/docs/using-di.md
new file mode 100644
index 000000000..8837c9447
--- /dev/null
+++ b/docs/using-di.md
@@ -0,0 +1,96 @@
+# Using Dependency Injection With Active Record
+
+Using [dependency injection](https://github.com/yiisoft/di) in the Active Record model allows to inject dependencies 
+into the model and use them in the model methods.
+
+To create an Active Record model with dependency injection, you need to use 
+a [factory](https://github.com/yiisoft/factory) that will create an instance of the model and inject the dependencies 
+into it.
+
+## Define The Active Record Model
+
+Yii Active Record provides a `FactoryTrait` trait that allows to use the factory with the Active Record class.
+
+```php
+use Yiisoft\ActiveRecord\ActiveQueryInterface;
+use Yiisoft\ActiveRecord\ActiveRecord;
+use Yiisoft\ActiveRecord\Trait\FactoryTrait;
+
+#[\AllowDynamicProperties]
+final class User extends ActiveRecord
+{
+    use FactoryTrait;
+    
+    public function __construct(private MyService $myService)
+    {
+    }
+
+    public function getTableName(): string
+    {
+        return '{{%user}}';
+    }
+    
+    public function relationQuery(string $name): ActiveQueryInterface
+    {
+        return match ($name) {
+            'profile' => $this->hasOne(Profile::class, ['id' => 'profile_id']),
+            'orders' => $this->hasMany(Order::class, ['user_id' => 'id']),
+            default => parent::relationQuery($name),
+        };
+    }
+    
+    public function getProfile(): Profile|null
+    {
+        return $this->relation('profile');
+    }
+    
+    /** @return Order[] */
+    public function getOrders(): array
+    {
+        return $this->relation('orders');
+    }
+}
+```
+
+When you use dependency injection in the Active Record model, you need to create the Active Record instance using 
+the factory.
+
+```php
+/** @var \Yiisoft\Factory\Factory $factory */
+$user = $factory->create(User::class);
+```
+
+To create `ActiveQuery` instance you also need to use the factory to create the Active Record model.
+
+```php
+$userQuery = new ActiveQuery($factory->create(User::class)->withFactory($factory));
+```
+
+## Factory Parameter In The Constructor
+
+Optionally, you can define the factory parameter in the constructor of the Active Record class.
+
+```php
+use Yiisoft\ActiveRecord\ActiveQueryInterface;
+use Yiisoft\ActiveRecord\ActiveRecord;
+use Yiisoft\ActiveRecord\Trait\FactoryTrait;
+
+#[\AllowDynamicProperties]
+final class User extends ActiveRecord
+{
+    use FactoryTrait;
+    
+    public function __construct(Factory $factory, private MyService $myService)
+    {
+        $this->factory = $factory;
+    }
+}
+```
+
+This will allow to create the `ActiveQuery` instance without calling `ActiveRecord::withFactory()` method.
+
+```php
+$userQuery = new ActiveQuery($factory->create(User::class));
+```
+
+Back to [Create Active Record Model](docs/create-model.md)
diff --git a/src/Trait/FactoryTrait.php b/src/Trait/FactoryTrait.php
new file mode 100644
index 000000000..ec2e736e4
--- /dev/null
+++ b/src/Trait/FactoryTrait.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Yiisoft\ActiveRecord\Trait;
+
+use Closure;
+use Yiisoft\ActiveRecord\ActiveQuery;
+use Yiisoft\ActiveRecord\ActiveQueryInterface;
+use Yiisoft\ActiveRecord\ActiveRecordInterface;
+use Yiisoft\Factory\Factory;
+
+use function is_string;
+use function method_exists;
+
+/**
+ * Trait to add factory support to ActiveRecord.
+ *
+ * @see AbstractActiveRecord::instantiateQuery()
+ */
+trait FactoryTrait
+{
+    private Factory $factory;
+
+    /**
+     * Set the factory to use for creating new instances.
+     */
+    public function withFactory(Factory $factory): static
+    {
+        $new = clone $this;
+        $new->factory = $factory;
+        return $new;
+    }
+
+    public function instantiateQuery(string|ActiveRecordInterface|Closure $arClass): ActiveQueryInterface
+    {
+        if (!isset($this->factory)) {
+            return new ActiveQuery($arClass);
+        }
+
+        if (is_string($arClass)) {
+            if (method_exists($arClass, 'withFactory')) {
+                return new ActiveQuery(
+                    fn (): ActiveRecordInterface => $this->factory->create($arClass)->withFactory($this->factory)
+                );
+            }
+
+            return new ActiveQuery(fn (): ActiveRecordInterface => $this->factory->create($arClass));
+        }
+
+        if ($arClass instanceof ActiveRecordInterface && method_exists($arClass, 'withFactory')) {
+            return new ActiveQuery(
+                $arClass->withFactory($this->factory)
+            );
+        }
+
+        return new ActiveQuery($arClass);
+    }
+}
diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php
index 54126b82a..ca9e246df 100644
--- a/tests/ActiveRecordTest.php
+++ b/tests/ActiveRecordTest.php
@@ -4,6 +4,7 @@
 
 namespace Yiisoft\ActiveRecord\Tests;
 
+use ArgumentCountError;
 use DivisionByZeroError;
 use ReflectionException;
 use Yiisoft\ActiveRecord\ActiveQuery;
@@ -14,6 +15,7 @@
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerClosureField;
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerForArrayable;
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerWithAlias;
+use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerWithFactory;
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\CustomerWithCustomConnection;
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Dog;
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Item;
@@ -22,6 +24,7 @@
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Order;
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItem;
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderItemWithNullFK;
+use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\OrderWithFactory;
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Type;
 use Yiisoft\ActiveRecord\Tests\Support\Assert;
 use Yiisoft\Db\Exception\Exception;
@@ -30,9 +33,12 @@
 use Yiisoft\Db\Exception\InvalidConfigException;
 use Yiisoft\Db\Exception\UnknownPropertyException;
 use Yiisoft\Db\Query\Query;
+use Yiisoft\Factory\Factory;
 
 abstract class ActiveRecordTest extends TestCase
 {
+    abstract protected function createFactory(): Factory;
+
     public function testStoreNull(): void
     {
         $this->checkFixture($this->db(), 'null_values', true);
@@ -985,4 +991,102 @@ public function testWithCustomConnection(): void
 
         ConnectionProvider::remove('custom');
     }
+
+    public function testWithFactory(): void
+    {
+        $this->checkFixture($this->db(), 'order');
+
+        $factory = $this->createFactory();
+
+        $orderQuery = new ActiveQuery($factory->create(OrderWithFactory::class)->withFactory($factory));
+        $order = $orderQuery->with('customerWithFactory')->findOne(2);
+
+        $this->assertInstanceOf(OrderWithFactory::class, $order);
+        $this->assertTrue($order->isRelationPopulated('customerWithFactory'));
+        $this->assertInstanceOf(CustomerWithFactory::class, $order->getCustomerWithFactory());
+    }
+
+    public function testWithFactoryClosureRelation(): void
+    {
+        $this->checkFixture($this->db(), 'order');
+
+        $factory = $this->createFactory();
+
+        $orderQuery = new ActiveQuery($factory->create(OrderWithFactory::class)->withFactory($factory));
+        $order = $orderQuery->findOne(2);
+
+        $this->assertInstanceOf(OrderWithFactory::class, $order);
+        $this->assertInstanceOf(CustomerWithFactory::class, $order->getCustomerWithFactoryClosure());
+    }
+
+    public function testWithFactoryInstanceRelation(): void
+    {
+        $this->checkFixture($this->db(), 'order');
+
+        $factory = $this->createFactory();
+
+        $orderQuery = new ActiveQuery($factory->create(OrderWithFactory::class)->withFactory($factory));
+        $order = $orderQuery->findOne(2);
+
+        $this->assertInstanceOf(OrderWithFactory::class, $order);
+        $this->assertInstanceOf(CustomerWithFactory::class, $order->getCustomerWithFactoryInstance());
+    }
+
+    public function testWithFactoryRelationWithoutFactory(): void
+    {
+        $this->checkFixture($this->db(), 'order');
+
+        $factory = $this->createFactory();
+
+        $orderQuery = new ActiveQuery($factory->create(OrderWithFactory::class)->withFactory($factory));
+        $order = $orderQuery->findOne(2);
+
+        $this->assertInstanceOf(OrderWithFactory::class, $order);
+        $this->assertInstanceOf(Customer::class, $order->getCustomer());
+    }
+
+    public function testWithFactoryLazyRelation(): void
+    {
+        $this->checkFixture($this->db(), 'order');
+
+        $factory = $this->createFactory();
+
+        $orderQuery = new ActiveQuery($factory->create(OrderWithFactory::class)->withFactory($factory));
+        $order = $orderQuery->findOne(2);
+
+        $this->assertInstanceOf(OrderWithFactory::class, $order);
+        $this->assertFalse($order->isRelationPopulated('customerWithFactory'));
+        $this->assertInstanceOf(CustomerWithFactory::class, $order->getCustomerWithFactory());
+    }
+
+    public function testWithFactoryWithConstructor(): void
+    {
+        $this->checkFixture($this->db(), 'order');
+
+        $factory = $this->createFactory();
+
+        $customerQuery = new ActiveQuery($factory->create(CustomerWithFactory::class));
+        $customer = $customerQuery->findOne(2);
+
+        $this->assertInstanceOf(CustomerWithFactory::class, $customer);
+        $this->assertFalse($customer->isRelationPopulated('ordersWithFactory'));
+        $this->assertInstanceOf(OrderWithFactory::class, $customer->getOrdersWithFactory()[0]);
+    }
+
+    public function testWithFactoryNonInitiated(): void
+    {
+        $this->checkFixture($this->db(), 'order');
+
+        $orderQuery = new ActiveQuery(OrderWithFactory::class);
+        $order = $orderQuery->findOne(2);
+
+        $customer = $order->getCustomer();
+
+        $this->assertInstanceOf(Customer::class, $customer);
+
+        $this->expectException(ArgumentCountError::class);
+        $this->expectExceptionMessage('Too few arguments to function');
+
+        $customer = $order->getCustomerWithFactory();
+    }
 }
diff --git a/tests/Driver/Mssql/ActiveRecordTest.php b/tests/Driver/Mssql/ActiveRecordTest.php
index 39ad1fa00..a3d738ca8 100644
--- a/tests/Driver/Mssql/ActiveRecordTest.php
+++ b/tests/Driver/Mssql/ActiveRecordTest.php
@@ -9,6 +9,7 @@
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\TestTriggerAlert;
 use Yiisoft\ActiveRecord\Tests\Support\MssqlHelper;
 use Yiisoft\Db\Connection\ConnectionInterface;
+use Yiisoft\Factory\Factory;
 
 final class ActiveRecordTest extends \Yiisoft\ActiveRecord\Tests\ActiveRecordTest
 {
@@ -17,6 +18,11 @@ protected function createConnection(): ConnectionInterface
         return (new MssqlHelper())->createConnection();
     }
 
+    protected function createFactory(): Factory
+    {
+        return (new MssqlHelper())->createFactory($this->db());
+    }
+
     public function testSaveWithTrigger(): void
     {
         $this->checkFixture($this->db(), 'test_trigger');
diff --git a/tests/Driver/Mysql/ActiveRecordTest.php b/tests/Driver/Mysql/ActiveRecordTest.php
index c4ecfdd21..308e6ea6a 100644
--- a/tests/Driver/Mysql/ActiveRecordTest.php
+++ b/tests/Driver/Mysql/ActiveRecordTest.php
@@ -10,6 +10,7 @@
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer;
 use Yiisoft\ActiveRecord\Tests\Support\MysqlHelper;
 use Yiisoft\Db\Connection\ConnectionInterface;
+use Yiisoft\Factory\Factory;
 
 final class ActiveRecordTest extends \Yiisoft\ActiveRecord\Tests\ActiveRecordTest
 {
@@ -18,6 +19,11 @@ protected function createConnection(): ConnectionInterface
         return (new MysqlHelper())->createConnection();
     }
 
+    protected function createFactory(): Factory
+    {
+        return (new MysqlHelper())->createFactory($this->db());
+    }
+
     public function testCastValues(): void
     {
         $this->checkFixture($this->db(), 'type');
diff --git a/tests/Driver/Oracle/ActiveRecordTest.php b/tests/Driver/Oracle/ActiveRecordTest.php
index 4f82fc7ef..e8595a342 100644
--- a/tests/Driver/Oracle/ActiveRecordTest.php
+++ b/tests/Driver/Oracle/ActiveRecordTest.php
@@ -10,6 +10,7 @@
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Type;
 use Yiisoft\ActiveRecord\Tests\Support\OracleHelper;
 use Yiisoft\Db\Connection\ConnectionInterface;
+use Yiisoft\Factory\Factory;
 
 final class ActiveRecordTest extends \Yiisoft\ActiveRecord\Tests\ActiveRecordTest
 {
@@ -18,6 +19,11 @@ protected function createConnection(): ConnectionInterface
         return (new OracleHelper())->createConnection();
     }
 
+    protected function createFactory(): Factory
+    {
+        return (new OracleHelper())->createFactory($this->db());
+    }
+
     public function testCastValues(): void
     {
         $this->markTestSkipped('Cant bind floats without support from a custom PDO driver.');
diff --git a/tests/Driver/Pgsql/ActiveRecordTest.php b/tests/Driver/Pgsql/ActiveRecordTest.php
index df91df2c9..c6f1f37da 100644
--- a/tests/Driver/Pgsql/ActiveRecordTest.php
+++ b/tests/Driver/Pgsql/ActiveRecordTest.php
@@ -21,6 +21,7 @@
 use Yiisoft\Db\Expression\Expression;
 use Yiisoft\Db\Expression\JsonExpression;
 use Yiisoft\Db\Pgsql\Schema as SchemaPgsql;
+use Yiisoft\Factory\Factory;
 
 final class ActiveRecordTest extends \Yiisoft\ActiveRecord\Tests\ActiveRecordTest
 {
@@ -29,6 +30,11 @@ protected function createConnection(): ConnectionInterface
         return (new PgsqlHelper())->createConnection();
     }
 
+    protected function createFactory(): Factory
+    {
+        return (new PgsqlHelper())->createFactory($this->db());
+    }
+
     public function testDefaultValues(): void
     {
         $this->checkFixture($this->db(), 'type');
diff --git a/tests/Driver/Sqlite/ActiveRecordTest.php b/tests/Driver/Sqlite/ActiveRecordTest.php
index f9da98db3..d47fc396a 100644
--- a/tests/Driver/Sqlite/ActiveRecordTest.php
+++ b/tests/Driver/Sqlite/ActiveRecordTest.php
@@ -9,6 +9,7 @@
 use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer;
 use Yiisoft\ActiveRecord\Tests\Support\SqliteHelper;
 use Yiisoft\Db\Connection\ConnectionInterface;
+use Yiisoft\Factory\Factory;
 
 final class ActiveRecordTest extends \Yiisoft\ActiveRecord\Tests\ActiveRecordTest
 {
@@ -17,6 +18,11 @@ protected function createConnection(): ConnectionInterface
         return (new SqliteHelper())->createConnection();
     }
 
+    protected function createFactory(): Factory
+    {
+        return (new SqliteHelper())->createFactory($this->db());
+    }
+
     public function testExplicitPkOnAutoIncrement(): void
     {
         $this->checkFixture($this->db(), 'customer', true);
diff --git a/tests/Stubs/ActiveRecord/CustomerWithFactory.php b/tests/Stubs/ActiveRecord/CustomerWithFactory.php
new file mode 100644
index 000000000..1c2b46c86
--- /dev/null
+++ b/tests/Stubs/ActiveRecord/CustomerWithFactory.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord;
+
+use Yiisoft\ActiveRecord\ActiveQueryInterface;
+use Yiisoft\ActiveRecord\Trait\FactoryTrait;
+use Yiisoft\Aliases\Aliases;
+use Yiisoft\Factory\Factory;
+
+final class CustomerWithFactory extends Customer
+{
+    use FactoryTrait;
+
+    public function __construct(Factory $factory, private Aliases $aliases)
+    {
+        $this->factory = $factory;
+    }
+
+    public function relationQuery(string $name): ActiveQueryInterface
+    {
+        return match ($name) {
+            'ordersWithFactory' => $this->hasMany(OrderWithFactory::class, ['customer_id' => 'id']),
+            default => parent::relationQuery($name),
+        };
+    }
+
+    /** @return OrderWithFactory[] */
+    public function getOrdersWithFactory(): array
+    {
+        return $this->relation('ordersWithFactory');
+    }
+}
diff --git a/tests/Stubs/ActiveRecord/OrderWithFactory.php b/tests/Stubs/ActiveRecord/OrderWithFactory.php
new file mode 100644
index 000000000..d0a8b9282
--- /dev/null
+++ b/tests/Stubs/ActiveRecord/OrderWithFactory.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord;
+
+use Yiisoft\ActiveRecord\ActiveQueryInterface;
+use Yiisoft\ActiveRecord\Trait\FactoryTrait;
+
+final class OrderWithFactory extends Order
+{
+    use FactoryTrait;
+
+    public function relationQuery(string $name): ActiveQueryInterface
+    {
+        return match ($name) {
+            'customerWithFactory' => $this->hasOne(CustomerWithFactory::class, ['id' => 'customer_id']),
+            'customerWithFactoryClosure' => $this->hasOne(
+                fn () => $this->factory->create(CustomerWithFactory::class),
+                ['id' => 'customer_id']
+            ),
+            'customerWithFactoryInstance' => $this->hasOne(
+                $this->factory->create(CustomerWithFactory::class),
+                ['id' => 'customer_id']
+            ),
+            default => parent::relationQuery($name),
+        };
+    }
+
+    public function getCustomerWithFactory(): CustomerWithFactory|null
+    {
+        return $this->relation('customerWithFactory');
+    }
+
+    public function getCustomerWithFactoryClosure(): CustomerWithFactory|null
+    {
+        return $this->relation('customerWithFactoryClosure');
+    }
+
+    public function getCustomerWithFactoryInstance(): CustomerWithFactory|null
+    {
+        return $this->relation('customerWithFactoryInstance');
+    }
+}
diff --git a/tests/Support/ConnectionHelper.php b/tests/Support/ConnectionHelper.php
index 2b9a07dd5..14bde41a5 100644
--- a/tests/Support/ConnectionHelper.php
+++ b/tests/Support/ConnectionHelper.php
@@ -4,7 +4,6 @@
 
 namespace Yiisoft\ActiveRecord\Tests\Support;
 
-use Yiisoft\ActiveRecord\ActiveRecordFactory;
 use Yiisoft\Cache\ArrayCache;
 use Yiisoft\Db\Cache\SchemaCache;
 use Yiisoft\Db\Connection\ConnectionInterface;
@@ -14,19 +13,12 @@
 
 abstract class ConnectionHelper
 {
-    protected Factory $factory;
-
-    public function createARFactory(ConnectionInterface $db): ActiveRecordFactory
-    {
-        return new ActiveRecordFactory($this->createFactory($db));
-    }
-
     protected function createSchemaCache(): SchemaCache
     {
         return new SchemaCache(new ArrayCache());
     }
 
-    private function createFactory(ConnectionInterface $db): Factory
+    public function createFactory(ConnectionInterface $db): Factory
     {
         $container = new Container(ContainerConfig::create()->withDefinitions([ConnectionInterface::class => $db]));
         return new Factory($container, [ConnectionInterface::class => $db]);