Skip to content

Commit fc69b52

Browse files
committed
Add DbConnection adapters
1 parent 3070890 commit fc69b52

File tree

11 files changed

+160
-76
lines changed

11 files changed

+160
-76
lines changed

.github/workflows/php.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88

99
steps:
1010
- name: Checkout code
11-
uses: actions/checkout@v2
11+
uses: actions/checkout@v4
1212

1313
- name: Docker compose up
1414
run: docker compose up -d

composer.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@
3434
"ext-pdo": "*"
3535
},
3636
"require-dev": {
37-
"phpunit/phpunit": "^10.0"
37+
"doctrine/dbal": "4.*",
38+
"phpstan/phpstan": "2.1.22",
39+
"phpunit/phpunit": "^10.3"
40+
},
41+
"conflict": {
42+
"doctrine/dbal": "<4.0 || >=5.0"
3843
},
3944
"autoload": {
4045
"psr-4": {

phpstan.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
level: 10
3+
checkUninitializedProperties: true
4+
paths:
5+
- src
6+
excludePaths:
7+
- vendor

src/DbConnection/DoctrineDbConnection.php

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/DbConnection/DbConnectionInterface.php renamed to src/DbConnectionAdapter/DbConnectionAdapterInterface.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@
22

33
declare(strict_types=1);
44

5-
namespace Cog\DbLocker\DbConnection;
5+
namespace Cog\DbLocker\DbConnectionAdapter;
66

7-
interface DbConnectionInterface
7+
interface DbConnectionAdapterInterface
88
{
9+
public const PLATFORM_MYSQL = 'mysql';
10+
public const PLATFORM_POSTGRESQL = 'postgresql';
11+
912
/**
1013
* Execute a SQL query and return the first column of the first row.
1114
*
1215
* @param string $sql The SQL query to execute
1316
* @param array<string, mixed> $parameters Parameters to bind to the query
1417
* @return mixed The value from the first column of the first row
1518
*/
16-
public function executeAndFetchColumn(string $sql, array $parameters = []): mixed;
19+
public function executeAndFetchColumn(
20+
string $sql,
21+
array $parameters = [],
22+
): mixed;
1723

1824
/**
1925
* Check if the connection is currently inside a transaction.
2026
*
2127
* @return bool True if inside a transaction, false otherwise
2228
*/
2329
public function isInTransaction(): bool;
30+
31+
public function getPlatformName(): string;
2432
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cog\DbLocker\DbConnectionAdapter;
6+
7+
use Cog\DbLocker\DbConnectionAdapter\Exception\UncheckedDoctrineDbalException;
8+
use Doctrine\DBAL\Connection;
9+
use Doctrine\DBAL\Exception;
10+
use Doctrine\DBAL\Exception as DbalException;
11+
use Doctrine\DBAL\Platforms\MySQLPlatform;
12+
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
13+
use RuntimeException;
14+
15+
/**
16+
* Doctrine DBAL adapter for unified database connection interface.
17+
*
18+
* This adapter wraps a Doctrine DBAL Connection instance and provides
19+
* a consistent interface for database operations across different database drivers.
20+
*/
21+
final class DoctrineDbConnectionAdapter implements
22+
DbConnectionAdapterInterface
23+
{
24+
public function __construct(
25+
private readonly Connection $dbConnection,
26+
) {}
27+
28+
/**
29+
* @inheritDoc
30+
*/
31+
public function executeAndFetchColumn(
32+
string $sql,
33+
array $parameters = [],
34+
): mixed {
35+
try {
36+
return $this->dbConnection->fetchOne($sql, $parameters);
37+
} catch (Exception $e) {
38+
throw new RuntimeException(
39+
sprintf(
40+
'Doctrine DBAL error while executing SQL query: %s. Error: %s',
41+
$sql,
42+
$e->getMessage(),
43+
),
44+
previous: $e,
45+
);
46+
}
47+
}
48+
49+
/**
50+
* @inheritDoc
51+
*/
52+
public function isInTransaction(): bool
53+
{
54+
return $this->dbConnection->isTransactionActive();
55+
}
56+
57+
public function getPlatformName(): string
58+
{
59+
try {
60+
return match (true) {
61+
$this->dbConnection->getDatabasePlatform() instanceof MySqlPlatform => self::PLATFORM_MYSQL,
62+
$this->dbConnection->getDatabasePlatform() instanceof PostgreSqlPlatform => self::PLATFORM_POSTGRESQL,
63+
default => gettype($this->dbConnection->getDatabasePlatform()),
64+
};
65+
} catch (DbalException $exception) {
66+
throw UncheckedDoctrineDbalException::ofDbalException($exception);
67+
}
68+
}
69+
}

src/DbConnection/EloquentDbConnection.php renamed to src/DbConnectionAdapter/EloquentDbConnectionAdapter.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
declare(strict_types=1);
44

5-
namespace Cog\DbLocker\DbConnection;
5+
namespace Cog\DbLocker\DbConnectionAdapter;
66

77
use Illuminate\Database\Connection;
8+
use Illuminate\Database\MySqlConnection;
9+
use Illuminate\Database\PostgresConnection;
810
use Illuminate\Database\QueryException;
911
use RuntimeException;
1012

@@ -14,23 +16,25 @@
1416
* This adapter wraps an Illuminate\Database\Connection instance and provides
1517
* a consistent interface for database operations across different database drivers.
1618
*/
17-
final class EloquentDbConnection implements
18-
DbConnectionInterface
19+
final class EloquentDbConnectionAdapter implements
20+
DbConnectionAdapterInterface
1921
{
2022
public function __construct(
21-
private Connection $connection,
23+
private readonly Connection $dbConnection,
2224
) {}
2325

2426
/**
2527
* @inheritDoc
2628
*/
27-
public function executeAndFetchColumn(string $sql, array $parameters = []): mixed
28-
{
29+
public function executeAndFetchColumn(
30+
string $sql,
31+
array $parameters = [],
32+
): mixed {
2933
try {
3034
// Convert named parameters to positional parameters for Eloquent
3135
$bindings = array_values($parameters);
3236

33-
$result = $this->connection->selectOne($sql, $bindings);
37+
$result = $this->dbConnection->selectOne($sql, $bindings);
3438

3539
if ($result === null) {
3640
return null;
@@ -57,6 +61,15 @@ public function executeAndFetchColumn(string $sql, array $parameters = []): mixe
5761
*/
5862
public function isInTransaction(): bool
5963
{
60-
return $this->connection->transactionLevel() > 0;
64+
return $this->dbConnection->transactionLevel() > 0;
65+
}
66+
67+
public function getPlatformName(): string
68+
{
69+
return match (true) {
70+
$this->dbConnection instanceof MySqlConnection => self::PLATFORM_MYSQL,
71+
$this->dbConnection instanceof PostgresConnection => self::PLATFORM_POSTGRESQL,
72+
default => gettype($this->dbConnection),
73+
};
6174
}
6275
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cog\DbLocker\DbConnectionAdapter\Exception;
6+
7+
use Doctrine\DBAL\Exception as DoctrineDbalException;
8+
use RuntimeException;
9+
10+
final class UncheckedDoctrineDbalException extends RuntimeException
11+
{
12+
public static function ofDbalException(
13+
DoctrineDbalException $exception,
14+
): self {
15+
return new self(
16+
$exception->getMessage(),
17+
$exception->getCode(),
18+
$exception,
19+
);
20+
}
21+
}

src/DbConnection/PdoDbConnection.php renamed to src/DbConnectionAdapter/PdoDbConnectionAdapter.php

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace Cog\DbLocker\DbConnection;
5+
namespace Cog\DbLocker\DbConnectionAdapter;
66

77
use PDO;
88
use PDOStatement;
@@ -14,18 +14,20 @@
1414
* This adapter wraps a PDO instance and provides a consistent interface
1515
* for database operations across different database drivers.
1616
*/
17-
final class PdoDbConnection implements
18-
DbConnectionInterface
17+
final class PdoDbConnectionAdapter implements
18+
DbConnectionAdapterInterface
1919
{
2020
public function __construct(
21-
private PDO $pdo,
21+
private readonly PDO $pdo,
2222
) {}
2323

2424
/**
2525
* @inheritDoc
2626
*/
27-
public function executeAndFetchColumn(string $sql, array $parameters = []): mixed
28-
{
27+
public function executeAndFetchColumn(
28+
string $sql,
29+
array $parameters = [],
30+
): mixed {
2931
$statement = $this->prepareAndExecute($sql, $parameters);
3032

3133
$result = $statement->fetchColumn(0);
@@ -96,4 +98,13 @@ private function prepareAndExecute(string $sql, array $parameters): PDOStatement
9698
);
9799
}
98100
}
101+
102+
public function getPlatformName(): string
103+
{
104+
return match ($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) {
105+
'mysql' => self::PLATFORM_MYSQL,
106+
'pgsql' => self::PLATFORM_POSTGRESQL,
107+
default => $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME),
108+
};
109+
}
99110
}

src/Postgres/PostgresLockKey.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ classId: self::convertStringToSignedInt32($namespace),
4848
objectId: self::convertStringToSignedInt32($value),
4949
// TODO: Do we need to sanitize it?
5050
// TODO: Do we need to omit ":" on end if no value is passed
51-
humanReadableValue: "$namespace:$value",
51+
humanReadableValue: "$namespace|$value",
5252
);
5353
}
5454

0 commit comments

Comments
 (0)