diff --git a/src/Tempest/Database/src/Connection.php b/src/Tempest/Database/src/Connection.php new file mode 100644 index 000000000..ddf6cc416 --- /dev/null +++ b/src/Tempest/Database/src/Connection.php @@ -0,0 +1,24 @@ +get(AppConfig::class)->environment->isTesting()) { + return self::$instance; + } + + $databaseConfig = $container->get(DatabaseConfig::class); + + $connection = new PDOConnection($databaseConfig->connection()); + $connection->connect(); + + self::$instance = $connection; + + return $connection; + } +} diff --git a/src/Tempest/Database/src/DatabaseConnection.php b/src/Tempest/Database/src/Connections/DatabaseConnection.php similarity index 81% rename from src/Tempest/Database/src/DatabaseConnection.php rename to src/Tempest/Database/src/Connections/DatabaseConnection.php index 5333167d4..ac16ebfcb 100644 --- a/src/Tempest/Database/src/DatabaseConnection.php +++ b/src/Tempest/Database/src/Connections/DatabaseConnection.php @@ -2,8 +2,9 @@ declare(strict_types=1); -namespace Tempest\Database; +namespace Tempest\Database\Connections; +use Tempest\Database\DatabaseDialect; use Tempest\Database\Tables\NamingStrategy; interface DatabaseConnection diff --git a/src/Tempest/Database/src/DatabaseConnectionInitializer.php b/src/Tempest/Database/src/Connections/DatabaseConnectionInitializer.php similarity index 83% rename from src/Tempest/Database/src/DatabaseConnectionInitializer.php rename to src/Tempest/Database/src/Connections/DatabaseConnectionInitializer.php index 0b02fe22f..424ff9da7 100644 --- a/src/Tempest/Database/src/DatabaseConnectionInitializer.php +++ b/src/Tempest/Database/src/Connections/DatabaseConnectionInitializer.php @@ -2,11 +2,12 @@ declare(strict_types=1); -namespace Tempest\Database; +namespace Tempest\Database\Connections; use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; +use Tempest\Database\DatabaseConfig; final class DatabaseConnectionInitializer implements Initializer { diff --git a/src/Tempest/Database/src/Connections/MySqlConnection.php b/src/Tempest/Database/src/Connections/MySqlConnection.php index 9418928eb..4ae0ffb21 100644 --- a/src/Tempest/Database/src/Connections/MySqlConnection.php +++ b/src/Tempest/Database/src/Connections/MySqlConnection.php @@ -5,7 +5,6 @@ namespace Tempest\Database\Connections; use SensitiveParameter; -use Tempest\Database\DatabaseConnection; use Tempest\Database\DatabaseDialect; use Tempest\Database\Tables\NamingStrategy; use Tempest\Database\Tables\PluralizedSnakeCaseStrategy; diff --git a/src/Tempest/Database/src/Connections/PostgresConnection.php b/src/Tempest/Database/src/Connections/PostgresConnection.php index 8d49bbdfc..19bd7df55 100644 --- a/src/Tempest/Database/src/Connections/PostgresConnection.php +++ b/src/Tempest/Database/src/Connections/PostgresConnection.php @@ -5,7 +5,6 @@ namespace Tempest\Database\Connections; use SensitiveParameter; -use Tempest\Database\DatabaseConnection; use Tempest\Database\DatabaseDialect; use Tempest\Database\Tables\NamingStrategy; use Tempest\Database\Tables\PluralizedSnakeCaseStrategy; diff --git a/src/Tempest/Database/src/Connections/SQLiteConnection.php b/src/Tempest/Database/src/Connections/SQLiteConnection.php index f20f06922..243bc7a5a 100644 --- a/src/Tempest/Database/src/Connections/SQLiteConnection.php +++ b/src/Tempest/Database/src/Connections/SQLiteConnection.php @@ -5,7 +5,6 @@ namespace Tempest\Database\Connections; use SensitiveParameter; -use Tempest\Database\DatabaseConnection; use Tempest\Database\DatabaseDialect; use Tempest\Database\Tables\NamingStrategy; use Tempest\Database\Tables\PluralizedSnakeCaseStrategy; diff --git a/src/Tempest/Database/src/DatabaseConfig.php b/src/Tempest/Database/src/DatabaseConfig.php index 8df6cb957..cc8aeae76 100644 --- a/src/Tempest/Database/src/DatabaseConfig.php +++ b/src/Tempest/Database/src/DatabaseConfig.php @@ -4,6 +4,8 @@ namespace Tempest\Database; +use Tempest\Database\Connections\DatabaseConnection; + final class DatabaseConfig { private array $migrations = []; diff --git a/src/Tempest/Database/src/DatabaseDialectInitializer.php b/src/Tempest/Database/src/DatabaseDialectInitializer.php index e462085b3..527490607 100644 --- a/src/Tempest/Database/src/DatabaseDialectInitializer.php +++ b/src/Tempest/Database/src/DatabaseDialectInitializer.php @@ -6,6 +6,7 @@ use Tempest\Container\Container; use Tempest\Container\Initializer; +use Tempest\Database\Connections\DatabaseConnection; final readonly class DatabaseDialectInitializer implements Initializer { diff --git a/src/Tempest/Database/src/DatabaseInitializer.php b/src/Tempest/Database/src/DatabaseInitializer.php index a37763c81..3a5831a34 100644 --- a/src/Tempest/Database/src/DatabaseInitializer.php +++ b/src/Tempest/Database/src/DatabaseInitializer.php @@ -4,7 +4,6 @@ namespace Tempest\Database; -use PDO; use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; @@ -16,7 +15,7 @@ public function initialize(Container $container): Database { return new GenericDatabase( - $container->get(PDO::class), + $container->get(Connection::class), $container->get(TransactionManager::class), ); } diff --git a/src/Tempest/Database/src/Exceptions/ConnectionClosed.php b/src/Tempest/Database/src/Exceptions/ConnectionClosed.php new file mode 100644 index 000000000..7752d6cae --- /dev/null +++ b/src/Tempest/Database/src/Exceptions/ConnectionClosed.php @@ -0,0 +1,13 @@ +resolveBindings($query); try { - $this->pdo + $this->connection ->prepare($query->getSql()) ->execute($bindings); } catch (PDOException $pdoException) { @@ -35,12 +35,12 @@ public function execute(Query $query): void public function getLastInsertId(): Id { - return new Id($this->pdo->lastInsertId()); + return new Id($this->connection->lastInsertId()); } public function fetch(Query $query): array { - $pdoQuery = $this->pdo->prepare($query->getSql()); + $pdoQuery = $this->connection->prepare($query->getSql()); $pdoQuery->execute($this->resolveBindings($query)); diff --git a/src/Tempest/Database/src/PDOConnection.php b/src/Tempest/Database/src/PDOConnection.php new file mode 100644 index 000000000..15394179e --- /dev/null +++ b/src/Tempest/Database/src/PDOConnection.php @@ -0,0 +1,82 @@ +pdo === null) { + throw new ConnectionClosed(); + } + + return $this->pdo->beginTransaction(); + } + + public function commit(): bool + { + if ($this->pdo === null) { + throw new ConnectionClosed(); + } + + return $this->pdo->commit(); + } + + public function rollback(): bool + { + if ($this->pdo === null) { + throw new ConnectionClosed(); + } + + return $this->pdo->rollBack(); + } + + public function lastInsertId(): false|string + { + if ($this->pdo === null) { + throw new ConnectionClosed(); + } + + return $this->pdo->lastInsertId(); + } + + public function prepare(string $sql): false|PDOStatement + { + if ($this->pdo === null) { + throw new ConnectionClosed(); + } + + return $this->pdo->prepare($sql); + } + + public function close(): void + { + $this->pdo = null; + } + + public function connect(): void + { + if ($this->pdo !== null) { + return; + } + + $this->pdo = new PDO( + $this->connection->getDsn(), + $this->connection->getUsername(), + $this->connection->getPassword(), + ); + } +} diff --git a/src/Tempest/Database/src/PDOInitializer.php b/src/Tempest/Database/src/PDOInitializer.php deleted file mode 100644 index 3d854f9e0..000000000 --- a/src/Tempest/Database/src/PDOInitializer.php +++ /dev/null @@ -1,35 +0,0 @@ -get(DatabaseConfig::class); - - $connection = $databaseConfig->connection(); - - self::$pdo = new PDO( - $connection->getDsn(), - $connection->getUsername(), - $connection->getPassword(), - ); - } - - return self::$pdo; - } -} diff --git a/src/Tempest/Database/src/Transactions/GenericTransactionManager.php b/src/Tempest/Database/src/Transactions/GenericTransactionManager.php index a6196da51..29a5928ef 100644 --- a/src/Tempest/Database/src/Transactions/GenericTransactionManager.php +++ b/src/Tempest/Database/src/Transactions/GenericTransactionManager.php @@ -4,20 +4,20 @@ namespace Tempest\Database\Transactions; -use PDO; +use Tempest\Database\Connection; use Tempest\Database\Exceptions\CouldNotBeginTransaction; use Tempest\Database\Exceptions\CouldNotCommitTransaction; use Tempest\Database\Exceptions\CouldNotRollbackTransaction; final class GenericTransactionManager implements TransactionManager { - public function __construct(private PDO $pdo) + public function __construct(private Connection $connection) { } public function begin(): void { - $transactionBegun = $this->pdo->beginTransaction(); + $transactionBegun = $this->connection->beginTransaction(); if (! $transactionBegun) { throw new CouldNotBeginTransaction(); @@ -26,7 +26,7 @@ public function begin(): void public function commit(): void { - $transactionCommitted = $this->pdo->commit(); + $transactionCommitted = $this->connection->commit(); if (! $transactionCommitted) { throw new CouldNotCommitTransaction(); @@ -35,7 +35,7 @@ public function commit(): void public function rollback(): void { - $transactionRolledBack = $this->pdo->rollBack(); + $transactionRolledBack = $this->connection->rollBack(); if (! $transactionRolledBack) { throw new CouldNotRollbackTransaction(); diff --git a/src/Tempest/Database/src/Transactions/TransactionManagerInitializer.php b/src/Tempest/Database/src/Transactions/TransactionManagerInitializer.php index 8cb5fbd1b..bf9d0cd81 100644 --- a/src/Tempest/Database/src/Transactions/TransactionManagerInitializer.php +++ b/src/Tempest/Database/src/Transactions/TransactionManagerInitializer.php @@ -4,16 +4,16 @@ namespace Tempest\Database\Transactions; -use PDO; use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; +use Tempest\Database\Connection; final readonly class TransactionManagerInitializer implements Initializer { #[Singleton] public function initialize(Container $container): TransactionManager { - return new GenericTransactionManager($container->get(PDO::class)); + return new GenericTransactionManager($container->get(Connection::class)); } } diff --git a/src/Tempest/Database/tests/DatabaseDriverTest.php b/src/Tempest/Database/tests/DatabaseDriverTest.php index 551464ab6..d41b2a644 100644 --- a/src/Tempest/Database/tests/DatabaseDriverTest.php +++ b/src/Tempest/Database/tests/DatabaseDriverTest.php @@ -8,10 +8,10 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +use Tempest\Database\Connections\DatabaseConnection; use Tempest\Database\Connections\MySqlConnection; use Tempest\Database\Connections\PostgresConnection; use Tempest\Database\Connections\SQLiteConnection; -use Tempest\Database\DatabaseConnection; /** * @internal diff --git a/src/Tempest/Database/tests/DatabaseQueryStatementTest.php b/src/Tempest/Database/tests/DatabaseQueryStatementTest.php index de0d50bc7..76a55ef08 100644 --- a/src/Tempest/Database/tests/DatabaseQueryStatementTest.php +++ b/src/Tempest/Database/tests/DatabaseQueryStatementTest.php @@ -8,10 +8,10 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +use Tempest\Database\Connections\DatabaseConnection; use Tempest\Database\Connections\MySqlConnection; use Tempest\Database\Connections\PostgresConnection; use Tempest\Database\Connections\SQLiteConnection; -use Tempest\Database\DatabaseConnection; use Tempest\Database\QueryStatements\CreateTableStatement; use Tempest\Database\QueryStatements\OnDelete; use Tempest\Database\QueryStatements\PrimaryKeyStatement; diff --git a/src/Tempest/Database/tests/GenericDatabaseTest.php b/src/Tempest/Database/tests/GenericDatabaseTest.php index 7a942004d..559f714aa 100644 --- a/src/Tempest/Database/tests/GenericDatabaseTest.php +++ b/src/Tempest/Database/tests/GenericDatabaseTest.php @@ -5,8 +5,8 @@ namespace Tempest\Database\Tests; use Exception; -use PDO; use PHPUnit\Framework\TestCase; +use Tempest\Database\Connection; use Tempest\Database\GenericDatabase; use Tempest\Database\Transactions\GenericTransactionManager; @@ -17,19 +17,19 @@ final class GenericDatabaseTest extends TestCase { public function test_it_executes_transactions(): void { - $pdo = $this->createMock(PDO::class); - $pdo->expects($this->once()) + $connection = $this->createMock(Connection::class); + $connection->expects($this->once()) ->method('beginTransaction') ->withAnyParameters() ->willReturn(true); - $pdo->expects($this->once()) + $connection->expects($this->once()) ->method('commit') ->withAnyParameters() ->willReturn(true); $database = new GenericDatabase( - $pdo, - new GenericTransactionManager($pdo), + $connection, + new GenericTransactionManager($connection), ); $result = $database->withinTransaction(function () { @@ -41,19 +41,19 @@ public function test_it_executes_transactions(): void public function test_it_rolls_back_transactions_on_failure(): void { - $pdo = $this->createMock(PDO::class); - $pdo->expects($this->once()) + $connection = $this->createMock(Connection::class); + $connection->expects($this->once()) ->method('beginTransaction') ->withAnyParameters() ->willReturn(true); - $pdo->expects($this->once()) + $connection->expects($this->once()) ->method('rollback') ->withAnyParameters() ->willReturn(true); $database = new GenericDatabase( - $pdo, - new GenericTransactionManager($pdo), + $connection, + new GenericTransactionManager($connection), ); $result = $database->withinTransaction(function (): never { diff --git a/src/Tempest/Database/tests/GenericTransactionManagerTest.php b/src/Tempest/Database/tests/GenericTransactionManagerTest.php index b04b7ba52..86ea64511 100644 --- a/src/Tempest/Database/tests/GenericTransactionManagerTest.php +++ b/src/Tempest/Database/tests/GenericTransactionManagerTest.php @@ -4,8 +4,8 @@ namespace Tempest\Database\Tests; -use PDO; use PHPUnit\Framework\TestCase; +use Tempest\Database\Connection; use Tempest\Database\Exceptions\CouldNotBeginTransaction; use Tempest\Database\Exceptions\CouldNotCommitTransaction; use Tempest\Database\Exceptions\CouldNotRollbackTransaction; @@ -18,84 +18,84 @@ final class GenericTransactionManagerTest extends TestCase { public function test_it_calls_being_transactions(): void { - $pdo = $this->createMock(PDO::class); - $pdo->expects($this->once()) + $connection = $this->createMock(Connection::class); + $connection->expects($this->once()) ->method('beginTransaction') ->withAnyParameters() ->willReturn(true); - $manager = new GenericTransactionManager($pdo); + $manager = new GenericTransactionManager($connection); $manager->begin(); } public function test_it_throws_an_exception_when_transaction_cannot_begin(): void { - $pdo = $this->createMock(PDO::class); - $pdo->expects($this->once()) + $connection = $this->createMock(Connection::class); + $connection->expects($this->once()) ->method('beginTransaction') ->withAnyParameters() ->willReturn(false); $this->expectException(CouldNotBeginTransaction::class); - $manager = new GenericTransactionManager($pdo); + $manager = new GenericTransactionManager($connection); $manager->begin(); } public function test_it_calls_commit_transactions(): void { - $pdo = $this->createMock(PDO::class); - $pdo->expects($this->once()) + $connection = $this->createMock(Connection::class); + $connection->expects($this->once()) ->method('commit') ->withAnyParameters() ->willReturn(true); - $manager = new GenericTransactionManager($pdo); + $manager = new GenericTransactionManager($connection); $manager->commit(); } public function test_it_throws_an_exception_when_transaction_cannot_commit(): void { - $pdo = $this->createMock(PDO::class); - $pdo->expects($this->once()) + $connection = $this->createMock(Connection::class); + $connection->expects($this->once()) ->method('commit') ->withAnyParameters() ->willReturn(false); $this->expectException(CouldNotCommitTransaction::class); - $manager = new GenericTransactionManager($pdo); + $manager = new GenericTransactionManager($connection); $manager->commit(); } public function test_it_calls_rollback_transactions(): void { - $pdo = $this->createMock(PDO::class); - $pdo->expects($this->once()) + $connection = $this->createMock(Connection::class); + $connection->expects($this->once()) ->method('rollBack') ->withAnyParameters() ->willReturn(true); - $manager = new GenericTransactionManager($pdo); + $manager = new GenericTransactionManager($connection); $manager->rollback(); } public function test_it_throws_an_exception_when_transaction_cannot_rollback(): void { - $pdo = $this->createMock(PDO::class); - $pdo->expects($this->once()) + $connection = $this->createMock(Connection::class); + $connection->expects($this->once()) ->method('rollBack') ->withAnyParameters() ->willReturn(false); $this->expectException(CouldNotRollbackTransaction::class); - $manager = new GenericTransactionManager($pdo); + $manager = new GenericTransactionManager($connection); $manager->rollback(); } diff --git a/src/Tempest/Database/tests/PDOConnectionTest.php b/src/Tempest/Database/tests/PDOConnectionTest.php new file mode 100644 index 000000000..9902521a6 --- /dev/null +++ b/src/Tempest/Database/tests/PDOConnectionTest.php @@ -0,0 +1,85 @@ +expectException(ConnectionClosed::class); + + $connection = new PDOConnection(new SQLiteConnection(self::PATH)); + + $connection->$method(...$params); + } + + #[DataProvider('provideQueryMethods')] + public function test_close_must_be_open(string $method, array $params): void + { + $this->expectException(ConnectionClosed::class); + + $connection = new PDOConnection(new SQLiteConnection(self::PATH)); + $connection->connect(); + $connection->close(); + + $connection->$method(...$params); + } + + public static function provideQueryMethods(): Generator + { + yield 'lastInsertId' => ['lastInsertId', []]; + yield 'commit' => ['commit', []]; + yield 'rollback' => ['rollback', []]; + yield 'beginTransaction' => ['beginTransaction', []]; + yield 'prepare' => ['prepare', ['select 1']]; + } + + public function test_commit(): void + { + $connection = new PDOConnection(new SQLiteConnection(self::PATH)); + $connection->connect(); + + $this->assertTrue($connection->beginTransaction()); + $this->assertTrue($connection->commit()); + } + + public function test_rollback(): void + { + $connection = new PDOConnection(new SQLiteConnection(self::PATH)); + $connection->connect(); + + $this->assertTrue($connection->beginTransaction()); + $this->assertTrue($connection->rollback()); + } + + public function test_last_insert_id(): void + { + $connection = new PDOConnection(new SQLiteConnection(self::PATH)); + $connection->connect(); + + $this->assertSame('0', $connection->lastInsertId()); + } + + public function test_prepare(): void + { + $connection = new PDOConnection(new SQLiteConnection(self::PATH)); + $connection->connect(); + + $this->assertNotFalse($connection->prepare('select 1')); + } +}