Skip to content

Commit 39bcf0c

Browse files
committed
Lock PdoStore becomes a facade for DoctrineDbalStore
1 parent 54d687b commit 39bcf0c

File tree

5 files changed

+154
-117
lines changed

5 files changed

+154
-117
lines changed

src/Symfony/Component/Lock/Store/DbalStore.php renamed to src/Symfony/Component/Lock/Store/DoctrineDbalStore.php

+61-40
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
namespace Symfony\Component\Lock\Store;
1313

1414
use Doctrine\DBAL\Connection;
15+
use Doctrine\DBAL\DriverManager;
1516
use Doctrine\DBAL\Exception as DBALException;
1617
use Doctrine\DBAL\Schema\Schema;
1718
use Symfony\Component\Lock\Exception\InvalidArgumentException;
1819
use Symfony\Component\Lock\Exception\InvalidTtlException;
1920
use Symfony\Component\Lock\Exception\LockConflictedException;
20-
use Symfony\Component\Lock\Exception\NotSupportedException;
2121
use Symfony\Component\Lock\Key;
2222
use Symfony\Component\Lock\PersistingStoreInterface;
2323

@@ -34,11 +34,12 @@
3434
*
3535
* @author Jérémy Derussé <jeremy@derusse.com>
3636
*/
37-
class DbalStore implements PersistingStoreInterface
37+
class DoctrineDbalStore implements PersistingStoreInterface
3838
{
3939
use ExpiringStoreTrait;
4040

4141
private $conn;
42+
private $dsn;
4243
private $driver;
4344
private $table = 'lock_keys';
4445
private $idCol = 'key_id';
@@ -54,15 +55,15 @@ class DbalStore implements PersistingStoreInterface
5455
* * db_token_col: The column where to store the lock token [default: key_token]
5556
* * db_expiration_col: The column where to store the expiration [default: key_expiration]
5657
*
57-
* @param Connection $conn A DBAL Connection instance
58-
* @param array $options An associative array of options
59-
* @param float $gcProbability Probability expressed as floating number between 0 and 1 to clean old locks
60-
* @param int $initialTtl The expiration delay of locks in seconds
58+
* @param Connection|String $connOrDsn A DBAL Connection instance
59+
* @param array $options An associative array of options
60+
* @param float $gcProbability Probability expressed as floating number between 0 and 1 to clean old locks
61+
* @param int $initialTtl The expiration delay of locks in seconds
6162
*
6263
* @throws InvalidArgumentException When namespace contains invalid characters
6364
* @throws InvalidArgumentException When the initial ttl is not valid
6465
*/
65-
public function __construct(Connection $conn, array $options = [], float $gcProbability = 0.01, int $initialTtl = 300)
66+
public function __construct($connOrDsn, array $options = [], float $gcProbability = 0.01, int $initialTtl = 300)
6667
{
6768
if ($gcProbability < 0 || $gcProbability > 1) {
6869
throw new InvalidArgumentException(sprintf('"%s" requires gcProbability between 0 and 1, "%f" given.', __METHOD__, $gcProbability));
@@ -71,7 +72,13 @@ public function __construct(Connection $conn, array $options = [], float $gcProb
7172
throw new InvalidTtlException(sprintf('"%s()" expects a strictly positive TTL, "%d" given.', __METHOD__, $initialTtl));
7273
}
7374

74-
$this->conn = $conn;
75+
if ($connOrDsn instanceof Connection) {
76+
$this->conn = $connOrDsn;
77+
} elseif (\is_string($connOrDsn)) {
78+
$this->dsn = $connOrDsn;
79+
} else {
80+
throw new InvalidArgumentException(sprintf('"%s" requires Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, get_debug_type($connOrDsn)));
81+
}
7582

7683
$this->table = $options['db_table'] ?? $this->table;
7784
$this->idCol = $options['db_id_col'] ?? $this->idCol;
@@ -90,13 +97,12 @@ public function save(Key $key)
9097
$key->reduceLifetime($this->initialTtl);
9198

9299
$sql = "INSERT INTO $this->table ($this->idCol, $this->tokenCol, $this->expirationCol) VALUES (:id, :token, {$this->getCurrentTimestampStatement()} + $this->initialTtl)";
93-
$stmt = $this->getConnection()->prepare($sql);
94-
95-
$stmt->bindValue(':id', $this->getHashedKey($key));
96-
$stmt->bindValue(':token', $this->getUniqueToken($key));
97100

98101
try {
99-
$stmt->executeStatement();
102+
$this->getConnection()->executeStatement($sql, [
103+
'id' => $this->getHashedKey($key),
104+
'token' => $this->getUniqueToken($key),
105+
]);
100106
} catch (DBALException $e) {
101107
// the lock is already acquired. It could be us. Let's try to put off.
102108
$this->putOffExpiration($key, $this->initialTtl);
@@ -121,13 +127,13 @@ public function putOffExpiration(Key $key, $ttl)
121127
$key->reduceLifetime($ttl);
122128

123129
$sql = "UPDATE $this->table SET $this->expirationCol = {$this->getCurrentTimestampStatement()} + $ttl, $this->tokenCol = :token1 WHERE $this->idCol = :id AND ($this->tokenCol = :token2 OR $this->expirationCol <= {$this->getCurrentTimestampStatement()})";
124-
$stmt = $this->getConnection()->prepare($sql);
125-
126130
$uniqueToken = $this->getUniqueToken($key);
127-
$stmt->bindValue(':id', $this->getHashedKey($key));
128-
$stmt->bindValue(':token1', $uniqueToken);
129-
$stmt->bindValue(':token2', $uniqueToken);
130-
$result = $stmt->executeQuery();
131+
132+
$result = $this->getConnection()->executeQuery($sql, [
133+
'id' => $this->getHashedKey($key),
134+
'token1' => $uniqueToken,
135+
'token2' => $uniqueToken,
136+
]);
131137

132138
// If this method is called twice in the same second, the row wouldn't be updated. We have to call exists to know if we are the owner
133139
if (!$result->rowCount() && !$this->exists($key)) {
@@ -142,12 +148,10 @@ public function putOffExpiration(Key $key, $ttl)
142148
*/
143149
public function delete(Key $key)
144150
{
145-
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id AND $this->tokenCol = :token";
146-
$stmt = $this->getConnection()->prepare($sql);
147-
148-
$stmt->bindValue(':id', $this->getHashedKey($key));
149-
$stmt->bindValue(':token', $this->getUniqueToken($key));
150-
$stmt->executeStatement();
151+
$this->getConnection()->delete($this->table, [
152+
$this->idCol => $this->getHashedKey($key),
153+
$this->tokenCol => $this->getUniqueToken($key),
154+
]);
151155
}
152156

153157
/**
@@ -156,13 +160,12 @@ public function delete(Key $key)
156160
public function exists(Key $key)
157161
{
158162
$sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND $this->tokenCol = :token AND $this->expirationCol > {$this->getCurrentTimestampStatement()}";
159-
$stmt = $this->getConnection()->prepare($sql);
160-
161-
$stmt->bindValue(':id', $this->getHashedKey($key));
162-
$stmt->bindValue(':token', $this->getUniqueToken($key));
163-
$result = $stmt->executeQuery();
163+
$result = $this->getConnection()->fetchOne($sql, [
164+
'id' => $this->getHashedKey($key),
165+
'token' => $this->getUniqueToken($key),
166+
]);
164167

165-
return (bool) $result->fetchOne();
168+
return (bool) $result;
166169
}
167170

168171
/**
@@ -186,8 +189,15 @@ private function getUniqueToken(Key $key): string
186189
/**
187190
* @return Connection
188191
*/
189-
private function getConnection()
192+
private function getConnection(): object
190193
{
194+
if (null === $this->conn) {
195+
if (!class_exists(DriverManager::class)) {
196+
throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $this->dsn));
197+
}
198+
$this->conn = DriverManager::getConnection(['url' => $this->dsn]);
199+
}
200+
191201
return $this->conn;
192202
}
193203

@@ -198,29 +208,40 @@ private function getConnection()
198208
*/
199209
public function createTable(): void
200210
{
211+
$schema = new Schema();
212+
$this->configureSchema($schema);
213+
201214
$conn = $this->getConnection();
215+
foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
216+
$conn->executeStatement($sql);
217+
}
218+
}
219+
220+
/**
221+
* Adds the Table to the Schema if it doesn't exist.
222+
*/
223+
public function configureSchema(Schema $schema): void
224+
{
225+
if ($schema->hasTable($this->table)) {
226+
return;
227+
}
202228

203-
$schema = new Schema();
204229
$table = $schema->createTable($this->table);
205230
$table->addColumn($this->idCol, 'string', ['length' => 64]);
206231
$table->addColumn($this->tokenCol, 'string', ['length' => 44]);
207232
$table->addColumn($this->expirationCol, 'integer', ['unsigned' => true]);
208233
$table->setPrimaryKey([$this->idCol]);
209-
210-
foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
211-
$conn->executeStatement($sql);
212-
}
213234
}
214235

236+
215237
/**
216238
* Cleans up the table by removing all expired locks.
217239
*/
218240
private function prune(): void
219241
{
220242
$sql = "DELETE FROM $this->table WHERE $this->expirationCol <= {$this->getCurrentTimestampStatement()}";
221243

222-
$conn = $this->getConnection();
223-
$conn->executeStatement($sql);
244+
$this->getConnection()->executeStatement($sql);
224245
}
225246

226247
private function getDriver(): string
@@ -280,7 +301,7 @@ private function getCurrentTimestampStatement(): string
280301
case 'sqlsrv':
281302
return 'DATEDIFF(s, \'1970-01-01\', GETUTCDATE())';
282303
default:
283-
return time();
304+
return (string) time();
284305
}
285306
}
286307
}

0 commit comments

Comments
 (0)