Skip to content

Commit

Permalink
refactor(database): wrap PDO so it can be closed
Browse files Browse the repository at this point in the history
  • Loading branch information
innocenzi committed Dec 24, 2024
1 parent 44efff9 commit eedac03
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 45 deletions.
1 change: 0 additions & 1 deletion src/Tempest/Database/src/DatabaseInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Tempest\Database;

use PDO;
use Tempest\Container\Container;
use Tempest\Container\Initializer;
use Tempest\Container\Singleton;
Expand Down
9 changes: 9 additions & 0 deletions src/Tempest/Database/src/Exceptions/ConnectionClosed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Tempest\Database\Exceptions;

final class ConnectionClosed extends DatabaseException
{
}
9 changes: 5 additions & 4 deletions src/Tempest/Database/src/GenericDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use BackedEnum;
use DateTimeInterface;
use PDO;
use PDO as GlobalPDO;
use PDOException;
use Tempest\Database\Exceptions\QueryException;
use Tempest\Database\Transactions\TransactionManager;
Expand All @@ -26,6 +26,7 @@ public function execute(Query $query): void

try {
$this->pdo
->getPdo()
->prepare($query->getSql())
->execute($bindings);
} catch (PDOException $pdoException) {
Expand All @@ -35,16 +36,16 @@ public function execute(Query $query): void

public function getLastInsertId(): Id
{
return new Id($this->pdo->lastInsertId());
return new Id($this->pdo->getPdo()->lastInsertId());
}

public function fetch(Query $query): array
{
$pdoQuery = $this->pdo->prepare($query->getSql());
$pdoQuery = $this->pdo->getPdo()->prepare($query->getSql());

$pdoQuery->execute($this->resolveBindings($query));

return $pdoQuery->fetchAll(PDO::FETCH_NAMED);
return $pdoQuery->fetchAll(GlobalPDO::FETCH_NAMED);
}

public function fetchFirst(Query $query): ?array
Expand Down
35 changes: 35 additions & 0 deletions src/Tempest/Database/src/PDO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Tempest\Database;

use PDO as GlobalPDO;
use Tempest\Database\Exceptions\ConnectionClosed;

final class PDO
{
public function __construct(
private ?GlobalPDO $pdo,
) {
}

public static function create(string $dsn, ?string $username = null, ?string $password = null, ?array $options = []): self
{
return new self(new GlobalPDO($dsn, $username, $password, $options));
}

public function getPdo(): GlobalPDO
{
if ($this->pdo === null) {
throw new ConnectionClosed('The database connection is closed.');
}

return $this->pdo;
}

public function close(): void
{
$this->pdo = null;
}
}
25 changes: 10 additions & 15 deletions src/Tempest/Database/src/PDOInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,27 @@

namespace Tempest\Database;

use PDO;
use Tempest\Container\Container;
use Tempest\Container\Initializer;
use Tempest\Container\Singleton;

final class PDOInitializer implements Initializer
{
private static ?PDO $pdo = null;

#[Singleton]
public function initialize(Container $container): PDO
{
// Prevent multiple PDO connections to live on in memory while running tests
// TODO: need to improve
if (self::$pdo === null) {
$databaseConfig = $container->get(DatabaseConfig::class);
if ($container->has(PDO::class)) {
return $container->get(PDO::class);
}

$connection = $databaseConfig->connection();
$databaseConfig = $container->get(DatabaseConfig::class);

self::$pdo = new PDO(
$connection->getDsn(),
$connection->getUsername(),
$connection->getPassword(),
);
}
$connection = $databaseConfig->connection();

return self::$pdo;
return PDO::create(
$connection->getDsn(),
$connection->getUsername(),
$connection->getPassword(),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

namespace Tempest\Database\Transactions;

use PDO;
use Tempest\Database\Exceptions\CouldNotBeginTransaction;
use Tempest\Database\Exceptions\CouldNotCommitTransaction;
use Tempest\Database\Exceptions\CouldNotRollbackTransaction;
use Tempest\Database\PDO;

final class GenericTransactionManager implements TransactionManager
{
Expand All @@ -17,7 +17,7 @@ public function __construct(private PDO $pdo)

public function begin(): void
{
$transactionBegun = $this->pdo->beginTransaction();
$transactionBegun = $this->pdo->getPdo()->beginTransaction();

if (! $transactionBegun) {
throw new CouldNotBeginTransaction();
Expand All @@ -26,7 +26,7 @@ public function begin(): void

public function commit(): void
{
$transactionCommitted = $this->pdo->commit();
$transactionCommitted = $this->pdo->getPdo()->commit();

if (! $transactionCommitted) {
throw new CouldNotCommitTransaction();
Expand All @@ -35,7 +35,7 @@ public function commit(): void

public function rollback(): void
{
$transactionRolledBack = $this->pdo->rollBack();
$transactionRolledBack = $this->pdo->getPdo()->rollBack();

if (! $transactionRolledBack) {
throw new CouldNotRollbackTransaction();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

namespace Tempest\Database\Transactions;

use PDO;
use Tempest\Container\Container;
use Tempest\Container\Initializer;
use Tempest\Container\Singleton;
use Tempest\Database\PDO;

final readonly class TransactionManagerInitializer implements Initializer
{
Expand Down
19 changes: 12 additions & 7 deletions src/Tempest/Database/tests/GenericDatabaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
namespace Tempest\Database\Tests;

use Exception;
use PDO;
use PDO as GlobalPDO;
use PHPUnit\Framework\TestCase;
use Tempest\Database\GenericDatabase;
use Tempest\Database\PDO;
use Tempest\Database\Transactions\GenericTransactionManager;

/**
Expand All @@ -17,16 +18,18 @@ final class GenericDatabaseTest extends TestCase
{
public function test_it_executes_transactions(): void
{
$pdo = $this->createMock(PDO::class);
$pdo->expects($this->once())
$globalPdo = $this->createMock(GlobalPDO::class);
$globalPdo->expects($this->once())
->method('beginTransaction')
->withAnyParameters()
->willReturn(true);
$pdo->expects($this->once())
$globalPdo->expects($this->once())
->method('commit')
->withAnyParameters()
->willReturn(true);

$pdo = new PDO($globalPdo);

$database = new GenericDatabase(
$pdo,
new GenericTransactionManager($pdo),
Expand All @@ -41,16 +44,18 @@ 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())
$globalPdo = $this->createMock(GlobalPDO::class);
$globalPdo->expects($this->once())
->method('beginTransaction')
->withAnyParameters()
->willReturn(true);
$pdo->expects($this->once())
$globalPdo->expects($this->once())
->method('rollback')
->withAnyParameters()
->willReturn(true);

$pdo = new PDO($globalPdo);

$database = new GenericDatabase(
$pdo,
new GenericTransactionManager($pdo),
Expand Down
27 changes: 14 additions & 13 deletions src/Tempest/Database/tests/GenericTransactionManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace Tempest\Database\Tests;

use PDO;
use PDO as GlobalPDO;
use PHPUnit\Framework\TestCase;
use Tempest\Database\Exceptions\CouldNotBeginTransaction;
use Tempest\Database\Exceptions\CouldNotCommitTransaction;
use Tempest\Database\Exceptions\CouldNotRollbackTransaction;
use Tempest\Database\PDO;
use Tempest\Database\Transactions\GenericTransactionManager;

/**
Expand All @@ -18,84 +19,84 @@ final class GenericTransactionManagerTest extends TestCase
{
public function test_it_calls_being_transactions(): void
{
$pdo = $this->createMock(PDO::class);
$pdo = $this->createMock(GlobalPDO::class);
$pdo->expects($this->once())
->method('beginTransaction')
->withAnyParameters()
->willReturn(true);

$manager = new GenericTransactionManager($pdo);
$manager = new GenericTransactionManager(new PDO($pdo));

$manager->begin();
}

public function test_it_throws_an_exception_when_transaction_cannot_begin(): void
{
$pdo = $this->createMock(PDO::class);
$pdo = $this->createMock(GlobalPDO::class);
$pdo->expects($this->once())
->method('beginTransaction')
->withAnyParameters()
->willReturn(false);

$this->expectException(CouldNotBeginTransaction::class);

$manager = new GenericTransactionManager($pdo);
$manager = new GenericTransactionManager(new PDO($pdo));

$manager->begin();
}

public function test_it_calls_commit_transactions(): void
{
$pdo = $this->createMock(PDO::class);
$pdo = $this->createMock(GlobalPDO::class);
$pdo->expects($this->once())
->method('commit')
->withAnyParameters()
->willReturn(true);

$manager = new GenericTransactionManager($pdo);
$manager = new GenericTransactionManager(new PDO($pdo));

$manager->commit();
}

public function test_it_throws_an_exception_when_transaction_cannot_commit(): void
{
$pdo = $this->createMock(PDO::class);
$pdo = $this->createMock(GlobalPDO::class);
$pdo->expects($this->once())
->method('commit')
->withAnyParameters()
->willReturn(false);

$this->expectException(CouldNotCommitTransaction::class);

$manager = new GenericTransactionManager($pdo);
$manager = new GenericTransactionManager(new PDO($pdo));

$manager->commit();
}

public function test_it_calls_rollback_transactions(): void
{
$pdo = $this->createMock(PDO::class);
$pdo = $this->createMock(GlobalPDO::class);
$pdo->expects($this->once())
->method('rollBack')
->withAnyParameters()
->willReturn(true);

$manager = new GenericTransactionManager($pdo);
$manager = new GenericTransactionManager(new PDO($pdo));

$manager->rollback();
}

public function test_it_throws_an_exception_when_transaction_cannot_rollback(): void
{
$pdo = $this->createMock(PDO::class);
$pdo = $this->createMock(GlobalPDO::class);
$pdo->expects($this->once())
->method('rollBack')
->withAnyParameters()
->willReturn(false);

$this->expectException(CouldNotRollbackTransaction::class);

$manager = new GenericTransactionManager($pdo);
$manager = new GenericTransactionManager(new PDO($pdo));

$manager->rollback();
}
Expand Down
3 changes: 3 additions & 0 deletions tests/Integration/Console/Components/TaskComponentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Tempest\Console\Components\Interactive\TaskComponent;
use Tempest\Console\Console;
use Tempest\Console\Terminal\Terminal;
use Tempest\Database\PDO;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

/**
Expand All @@ -20,6 +21,8 @@ protected function setUp(): void
{
parent::setUp();

$this->container->get(PDO::class)->close();

if (PHP_OS_FAMILY === 'Windows') {
$this->markTestSkipped('These tests require the pcntl extension, which is not available on Windows.');
}
Expand Down
15 changes: 15 additions & 0 deletions tests/Integration/Database/GenericDatabaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
namespace Tests\Tempest\Integration\Database;

use Exception;
use PDO;
use Tempest\Database\Database;
use Tempest\Database\Exceptions\ConnectionClosed;
use Tempest\Database\Migrations\CreateMigrationsTable;
use Tempest\Database\Migrations\Migration;
use Tempest\Database\PDO as DatabasePDO;
use Tests\Tempest\Fixtures\Migrations\CreateAuthorTable;
use Tests\Tempest\Fixtures\Modules\Books\Models\Author;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
Expand Down Expand Up @@ -43,4 +46,16 @@ public function test_execute_with_fail_works_correctly(): void

$this->assertCount(0, Author::all());
}

public function test_pdo_close(): void
{
$this->expectException(ConnectionClosed::class);

$pdo = $this->container->get(DatabasePDO::class);

$this->assertInstanceOf(PDO::class, $pdo->getPdo());

$pdo->close();
$pdo->getPdo(); // this throws
}
}

0 comments on commit eedac03

Please sign in to comment.