From 2bde7d59123383a904459a74a7a7a0fc8393b20f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= <me@jiripudil.cz>
Date: Thu, 13 Feb 2025 13:40:19 +0100
Subject: [PATCH] add generated TableFactory that can create Tables with custom
 Connection or TableManager

---
 src/Scaffolding/Definitions.php               | 18 +++++
 .../TableFactoryImplementation.php            | 73 +++++++++++++++++++
 src/Scaffolding/TablesDefinitions.php         |  6 +-
 src/TableFactory.php                          | 14 ++++
 tests/Fixtures/ConfigTableFactory.php         | 42 +++++++++++
 tests/Fixtures/GeneratedTableFactory.php      | 42 +++++++++++
 tests/Fixtures/PackagesTableFactory.php       | 42 +++++++++++
 tests/Fixtures/TestsTableFactory.php          | 42 +++++++++++
 tests/Scaffolding/ScaffoldingTest.phpt        |  2 +-
 9 files changed, 278 insertions(+), 3 deletions(-)
 create mode 100644 src/Scaffolding/TableFactoryImplementation.php
 create mode 100644 src/TableFactory.php
 create mode 100644 tests/Fixtures/ConfigTableFactory.php
 create mode 100644 tests/Fixtures/GeneratedTableFactory.php
 create mode 100644 tests/Fixtures/PackagesTableFactory.php
 create mode 100644 tests/Fixtures/TestsTableFactory.php

diff --git a/src/Scaffolding/Definitions.php b/src/Scaffolding/Definitions.php
index 5fa50fa..35f5239 100644
--- a/src/Scaffolding/Definitions.php
+++ b/src/Scaffolding/Definitions.php
@@ -16,6 +16,7 @@ final class Definitions implements \IteratorAggregate
 		private ClassDefinition $modificationsClass,
 		private ?ClassDefinition $primaryKeyClass,
 		private ClassDefinition $tableClass,
+		private ?ClassDefinition $factoryClass,
 	) {}
 
 	public static function from(
@@ -23,6 +24,7 @@ final class Definitions implements \IteratorAggregate
 		ClassDefinition $modificationsClass,
 		ClassDefinition $primaryKeyClass,
 		ClassDefinition $tableClass,
+		ClassDefinition $factoryClass,
 	): self
 	{
 		return new self(
@@ -30,6 +32,7 @@ final class Definitions implements \IteratorAggregate
 			$modificationsClass,
 			$primaryKeyClass,
 			$tableClass,
+			$factoryClass,
 		);
 	}
 
@@ -63,6 +66,18 @@ final class Definitions implements \IteratorAggregate
 		return $this;
 	}
 
+	public function factoryClassWith(Capability $capability, Capability ...$capabilities): self
+	{
+		$this->factoryClass = $this->factoryClass?->with($capability, ...$capabilities);
+		return $this;
+	}
+
+	public function withoutFactory(): self
+	{
+		$this->factoryClass = null;
+		return $this;
+	}
+
 
 	public function getIterator(): \Traversable
 	{
@@ -72,6 +87,9 @@ final class Definitions implements \IteratorAggregate
 		if ($this->primaryKeyClass !== null) {
 			yield $this->primaryKeyClass;
 		}
+		if ($this->factoryClass !== null) {
+			yield $this->factoryClass;
+		}
 	}
 
 }
diff --git a/src/Scaffolding/TableFactoryImplementation.php b/src/Scaffolding/TableFactoryImplementation.php
new file mode 100644
index 0000000..81bbede
--- /dev/null
+++ b/src/Scaffolding/TableFactoryImplementation.php
@@ -0,0 +1,73 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Grifart\Tables\Scaffolding;
+
+use Dibi\IConnection;
+use Grifart\ClassScaffolder\Capabilities\Capability;
+use Grifart\ClassScaffolder\ClassInNamespace;
+use Grifart\ClassScaffolder\Definition\ClassDefinition;
+use Grifart\Tables\SingleConnectionTableManager;
+use Grifart\Tables\TableFactory;
+use Grifart\Tables\TableManager;
+use Grifart\Tables\TypeResolver;
+use Nette\PhpGenerator\Literal;
+use Nette\PhpGenerator\Parameter;
+use Nette\PhpGenerator\PromotedParameter;
+
+final readonly class TableFactoryImplementation implements Capability
+{
+	public function __construct(
+		private string $tableClass,
+	) {}
+
+	public function applyTo(
+		ClassDefinition $definition,
+		ClassInNamespace $draft,
+		?ClassInNamespace $current,
+	): void
+	{
+		$namespace = $draft->getNamespace();
+		$classType = $draft->getClassType();
+
+		$classType->addImplement(TableFactory::class);
+		$classType->setReadOnly();
+
+		$namespace->addUse(TypeResolver::class);
+		$namespace->addUse(TableManager::class);
+
+		$classType->addMethod('__construct')
+			->setPublic()
+			->setParameters([
+				(new PromotedParameter('tableManager'))
+					->setPrivate()
+					->setType(TableManager::class),
+				(new PromotedParameter('typeResolver'))
+					->setPrivate()
+					->setType(TypeResolver::class),
+			]);
+
+		$namespace->addUse($this->tableClass);
+
+		$classType->addMethod('create')
+			->setPublic()
+			->setReturnType($this->tableClass)
+			->setBody('return new ?($this->tableManager, $this->typeResolver);', [new Literal($namespace->simplifyName($this->tableClass))]);
+
+		$classType->addMethod('withTableManager')
+			->setPublic()
+			->setReturnType($this->tableClass)
+			->setParameters([(new Parameter('tableManager'))->setType(TableManager::class)])
+			->setBody('return new ?($tableManager, $this->typeResolver);', [new Literal($namespace->simplifyName($this->tableClass))]);
+
+		$namespace->addUse(IConnection::class);
+		$namespace->addUse(SingleConnectionTableManager::class);
+		$classType->addMethod('withConnection')
+			->setPublic()
+			->setReturnType($this->tableClass)
+			->setParameters([(new Parameter('connection'))->setType(IConnection::class)])
+			->addBody('$tableManager = new ?($connection);', [new Literal($namespace->simplifyName(SingleConnectionTableManager::class))])
+			->addBody('return new ?($tableManager, $this->typeResolver);', [new Literal($namespace->simplifyName($this->tableClass))]);
+	}
+}
diff --git a/src/Scaffolding/TablesDefinitions.php b/src/Scaffolding/TablesDefinitions.php
index 0a5b629..805ac51 100644
--- a/src/Scaffolding/TablesDefinitions.php
+++ b/src/Scaffolding/TablesDefinitions.php
@@ -17,7 +17,6 @@ use function Grifart\ClassScaffolder\Capabilities\implementedInterface;
 use function Grifart\ClassScaffolder\Capabilities\namedConstructor;
 use function Grifart\ClassScaffolder\Capabilities\privatizedConstructor;
 use function Grifart\ClassScaffolder\Definition\Types\nullable;
-use function Phun\map;
 use function Phun\mapWithKeys;
 
 final class TablesDefinitions
@@ -101,6 +100,9 @@ final class TablesDefinitions
 				$columnPhpTypes,
 			));
 
-		return Definitions::from($rowClass, $modificationsClass, $primaryKeyClass, $tableClass);
+		$factoryClass = (new ClassDefinition($tableClassName . 'Factory'))
+			->with(new TableFactoryImplementation($tableClassName));
+
+		return Definitions::from($rowClass, $modificationsClass, $primaryKeyClass, $tableClass, $factoryClass);
 	}
 }
diff --git a/src/TableFactory.php b/src/TableFactory.php
new file mode 100644
index 0000000..aa29745
--- /dev/null
+++ b/src/TableFactory.php
@@ -0,0 +1,14 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Grifart\Tables;
+
+use Dibi\IConnection;
+
+interface TableFactory
+{
+	public function create(): Table;
+	public function withTableManager(TableManager $tableManager): Table;
+	public function withConnection(IConnection $connection): Table;
+}
diff --git a/tests/Fixtures/ConfigTableFactory.php b/tests/Fixtures/ConfigTableFactory.php
new file mode 100644
index 0000000..de03067
--- /dev/null
+++ b/tests/Fixtures/ConfigTableFactory.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Do not edit. This is generated file. Modify definition file instead.
+ */
+
+declare(strict_types=1);
+
+namespace Grifart\Tables\Tests\Fixtures;
+
+use Dibi\IConnection;
+use Grifart\Tables\SingleConnectionTableManager;
+use Grifart\Tables\TableManager;
+use Grifart\Tables\TypeResolver;
+
+final readonly class ConfigTableFactory implements \Grifart\Tables\TableFactory
+{
+	public function __construct(
+		private TableManager $tableManager,
+		private TypeResolver $typeResolver,
+	) {
+	}
+
+
+	public function create(): ConfigTable
+	{
+		return new ConfigTable($this->tableManager, $this->typeResolver);
+	}
+
+
+	public function withTableManager(TableManager $tableManager): ConfigTable
+	{
+		return new ConfigTable($tableManager, $this->typeResolver);
+	}
+
+
+	public function withConnection(IConnection $connection): ConfigTable
+	{
+		$tableManager = new SingleConnectionTableManager($connection);
+		return new ConfigTable($tableManager, $this->typeResolver);
+	}
+}
diff --git a/tests/Fixtures/GeneratedTableFactory.php b/tests/Fixtures/GeneratedTableFactory.php
new file mode 100644
index 0000000..ac9413d
--- /dev/null
+++ b/tests/Fixtures/GeneratedTableFactory.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Do not edit. This is generated file. Modify definition file instead.
+ */
+
+declare(strict_types=1);
+
+namespace Grifart\Tables\Tests\Fixtures;
+
+use Dibi\IConnection;
+use Grifart\Tables\SingleConnectionTableManager;
+use Grifart\Tables\TableManager;
+use Grifart\Tables\TypeResolver;
+
+final readonly class GeneratedTableFactory implements \Grifart\Tables\TableFactory
+{
+	public function __construct(
+		private TableManager $tableManager,
+		private TypeResolver $typeResolver,
+	) {
+	}
+
+
+	public function create(): GeneratedTable
+	{
+		return new GeneratedTable($this->tableManager, $this->typeResolver);
+	}
+
+
+	public function withTableManager(TableManager $tableManager): GeneratedTable
+	{
+		return new GeneratedTable($tableManager, $this->typeResolver);
+	}
+
+
+	public function withConnection(IConnection $connection): GeneratedTable
+	{
+		$tableManager = new SingleConnectionTableManager($connection);
+		return new GeneratedTable($tableManager, $this->typeResolver);
+	}
+}
diff --git a/tests/Fixtures/PackagesTableFactory.php b/tests/Fixtures/PackagesTableFactory.php
new file mode 100644
index 0000000..72bfcce
--- /dev/null
+++ b/tests/Fixtures/PackagesTableFactory.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Do not edit. This is generated file. Modify definition file instead.
+ */
+
+declare(strict_types=1);
+
+namespace Grifart\Tables\Tests\Fixtures;
+
+use Dibi\IConnection;
+use Grifart\Tables\SingleConnectionTableManager;
+use Grifart\Tables\TableManager;
+use Grifart\Tables\TypeResolver;
+
+final readonly class PackagesTableFactory implements \Grifart\Tables\TableFactory
+{
+	public function __construct(
+		private TableManager $tableManager,
+		private TypeResolver $typeResolver,
+	) {
+	}
+
+
+	public function create(): PackagesTable
+	{
+		return new PackagesTable($this->tableManager, $this->typeResolver);
+	}
+
+
+	public function withTableManager(TableManager $tableManager): PackagesTable
+	{
+		return new PackagesTable($tableManager, $this->typeResolver);
+	}
+
+
+	public function withConnection(IConnection $connection): PackagesTable
+	{
+		$tableManager = new SingleConnectionTableManager($connection);
+		return new PackagesTable($tableManager, $this->typeResolver);
+	}
+}
diff --git a/tests/Fixtures/TestsTableFactory.php b/tests/Fixtures/TestsTableFactory.php
new file mode 100644
index 0000000..7b7bf9d
--- /dev/null
+++ b/tests/Fixtures/TestsTableFactory.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Do not edit. This is generated file. Modify definition file instead.
+ */
+
+declare(strict_types=1);
+
+namespace Grifart\Tables\Tests\Fixtures;
+
+use Dibi\IConnection;
+use Grifart\Tables\SingleConnectionTableManager;
+use Grifart\Tables\TableManager;
+use Grifart\Tables\TypeResolver;
+
+final readonly class TestsTableFactory implements \Grifart\Tables\TableFactory
+{
+	public function __construct(
+		private TableManager $tableManager,
+		private TypeResolver $typeResolver,
+	) {
+	}
+
+
+	public function create(): TestsTable
+	{
+		return new TestsTable($this->tableManager, $this->typeResolver);
+	}
+
+
+	public function withTableManager(TableManager $tableManager): TestsTable
+	{
+		return new TestsTable($tableManager, $this->typeResolver);
+	}
+
+
+	public function withConnection(IConnection $connection): TestsTable
+	{
+		$tableManager = new SingleConnectionTableManager($connection);
+		return new TestsTable($tableManager, $this->typeResolver);
+	}
+}
diff --git a/tests/Scaffolding/ScaffoldingTest.phpt b/tests/Scaffolding/ScaffoldingTest.phpt
index 148d0c5..a956c07 100644
--- a/tests/Scaffolding/ScaffoldingTest.phpt
+++ b/tests/Scaffolding/ScaffoldingTest.phpt
@@ -34,5 +34,5 @@ $results = $fileProcessor->processFile(
 	},
 );
 
-Assert::count(16, $results->getDefinitions());
+Assert::count(20, $results->getDefinitions());
 Assert::true($results->isSuccessful());
-- 
GitLab