8000 Merge branch '2.3' into 2.4 · sandermarechal/symfony@5535167 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5535167

Browse files
committed
Merge branch '2.3' into 2.4
* 2.3: [Config] Fix NodeBuilderTest::testNumericNodeCreation to use BaseNodeBuilder alias. [Profiler] Prevent throwing fatal errors when searching timestamps or invalid dates [HttpKernel] Added the resource ID when printing a resource in the DataCollector [HttpFoundation] use insert or replace for sqlite session handler [HttpFoundation] use MERGE SQL for MS SQL Server session storage [HttpFoundation] fix PDO session handler under high concurrency Conflicts: src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php
2 parents a19eb9b + e81c872 commit 5535167

File tree

5 files changed

+164
-123
lines changed

5 files changed

+164
-123
lines changed

src/Symfony/Component/Config/Tests/Definition/Builder/NodeBuilderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public function testNodeTypesAreNotCaseSensitive()
7979

8080
public function testNumericNodeCreation()
8181
{
82-
$builder = new NodeBuilder();
82+
$builder = new BaseNodeBuilder();
8383

8484
$node = $builder->integerNode('foo')->min(3)->max(5);
8585
$this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition', $node);

src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php

Lines changed: 94 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,34 @@
1616
*
1717
* @author Fabien Potencier <fabien@symfony.com>
1818
* @author Michael Williams <michael.williams@funsational.com>
19+
* @author Tobias Schultze <http://tobion.de>
1920
*/
2021
class PdoSessionHandler implements \SessionHandlerInterface
2122
{
2223
/**
23-
* @var \PDO PDO instance.
24+
* @var \PDO PDO instance
2425
*/
2526
private $pdo;
2627

2728
/**
28-
* @var array Database options.
29+
* @var string Table name
2930
*/
30-
private $dbOptions;
31+
private $table;
32+
33+
/**
34+
* @var string Column for session id
35+
*/
36+
private $idCol;
37+
38+
/**
39+
* @var string Column for session data
40+
*/
41+
private $dataCol;
42+
43+
/**
44+
* @var string Column for timestamp
45+
*/
46+
private $timeCol;
3147

3248
/**
3349
* Constructor.
@@ -52,11 +68,16 @@ public function __construct(\PDO $pdo, array $dbOptions = array())
5268
throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
5369
}
5470
$this->pdo = $pdo;
55-
$this->dbOptions = array_merge(array(
71+
$dbOptions = array_merge(array(
5672
'db_id_col' => 'sess_id',
5773
'db_data_col' => 'sess_data',
5874
'db_time_col' => 'sess_time',
5975
), $dbOptions);
76+
77+
$this->table = $dbOptions['db_table'];
78+
$this->idCol = $dbOptions['db_id_col'];
79+
$this->dataCol = $dbOptions['db_data_col'];
80+
$this->timeCol = $dbOptions['db_time_col'];
6081
}
6182

6283
/**
@@ -80,19 +101,15 @@ public function close()
80101
*/
81102
public function destroy($id)
82103
{
83-
// get table/column
84-
$dbTable = $this->dbOptions['db_table'];
85-
$dbIdCol = $this->dbOptions['db_id_col'];
86-
87104
// delete the record associated with this id
88-
$sql = "DELETE FROM $dbTable WHERE $dbIdCol = :id";
105+
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
89106

90107
try {
91108
$stmt = $this->pdo->prepare($sql);
92109
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
93110
$stmt->execute();
94111
} catch (\PDOException $e) {
95-
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
112+
throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete a session: %s', $e->getMessage()), 0, $e);
96113
}
97114

98115
return true;
@@ -103,19 +120,15 @@ public function destroy($id)
103120
*/
104121
public function gc($lifetime)
105122
{
106-
// get table/column
107-
$dbTable = $this->dbOptions['db_table'];
108-
$dbTimeCol = $this->dbOptions['db_time_col'];
109-
110123
// delete the session records that have expired
111-
$sql = "DELETE FROM $dbTable WHERE $dbTimeCol < :time";
124+
$sql = "DELETE FROM $this->table WHERE $this->timeCol < :time";
112125

113126
try {
114127
$stmt = $this->pdo->prepare($sql);
115128
$stmt->bindValue(':time', time() - $lifetime, \PDO::PARAM_INT);
116129
$stmt->execute();
117130
} catch (\PDOException $e) {
118-
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
131+
throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete expired sessions: %s', $e->getMessage()), 0, $e);
119132
}
120133

121134
return true;
@@ -126,29 +139,20 @@ public function gc($lifetime)
126139
*/
127140
public function read($id)
128141
{
129-
// get table/columns
130-
$dbTable = $this->dbOptions['db_table'];
131-
$dbDataCol = $this->dbOptions['db_data_col'];
132-
$dbIdCol = $this->dbOptions['db_id_col'];
142+
$sql = "SELECT $this->dataCol FROM $this->table WHERE $this->idCol = :id";
133143

134144
try {
135-
$sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id";
136-
137145
$stmt = $this->pdo->prepare($sql);
138146
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
139-
140147
$stmt->execute();
141-
// it is recommended to use fetchAll so that PDO can close the DB cursor
142-
// we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777
148+
149+
// We use fetchAll instead of fetchColumn to make sure the DB cursor gets closed
143150
$sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
144151

145-
if (count($sessionRows) == 1) {
152+
if ($sessionRows) {
146153
return base64_decode($sessionRows[0][0]);
147154
}
148155

149-
// session does not exist, create it
150-
$this->createNewSession($id);
151-
152156
return '';
153157
} catch (\PDOException $e) {
154158
throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e);
@@ -160,85 +164,83 @@ public function read($id)
160164
*/
161165
public function write($id, $data)
162166
{
163-
// get table/column
164-
$dbTable = $this->dbOptions['db_table'];
165-
$dbDataCol = $this->dbOptions['db_data_col'];
166-
$dbIdCol = $this->dbOptions['db_id_col'];
167-
$dbTimeCol = $this->dbOptions['db_time_col'];
168-
169-
//session data can contain non binary safe characters so we need to encode it
167+
// Session data can contain non binary safe characters so we need to encode it.
170168
$encoded = base64_encode($data);
171169

170+
// We use a MERGE SQL query when supported by the database.
171+
// Otherwise we have to use a transactional DELETE followed by INSERT to prevent duplicate entries under high concurrency.
172+
172173
try {
173-
$driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
174-
175-
if ('mysql' === $driver) {
176-
// MySQL would report $stmt->rowCount() = 0 on UPDATE when the data is left unchanged
177-
// it could result in calling createNewSession() whereas the session already exists in
178-
// the DB which would fail as the id is unique
179-
$stmt = $this->pdo->prepare(
180-
"INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time) " .
181-
"ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = VALUES($dbTimeCol)"
174+
$mergeSql = $this->getMergeSql();
175+
176+
if (null !== $mergeSql) {
177+
$mergeStmt = $this->pdo->prepare($mergeSql);
178+
$mergeStmt->bindParam(':id', $id, \PDO::PARAM_STR);
179+
$mergeStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
180+
$mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
181+
$mergeStmt->execute();
182+
183+
return true;
184+
}
185+
186+
$this->pdo->beginTransaction();
187+
188+
try {
189+
$deleteStmt = $this->pdo->prepare(
190+
"DELETE FROM $this->table WHERE $this->idCol = :id"
191+
);
192+
$deleteStmt->bindParam(':id', $id, \PDO::PARAM_STR);
193+
$deleteStmt->execute();
194+
195+
$insertStmt = $this->pdo->prepare(
196+
"INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)"
182197
);
183-
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
184-
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
185-
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
186-
$stmt->execute();
187-
} elseif ('oci' === $driver) {
188-
$stmt = $this->pdo->prepare("MERGE INTO $dbTable USING DUAL ON($dbIdCol = :id) ".
189-
"WHEN NOT MATCHED THEN INSERT ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, sysdate) " .
190-
"WHEN MATCHED THEN UPDATE SET $dbDataCol = :data WHERE $dbIdCol = :id");
191-
192-
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
193-
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
194-
$stmt->execute();
195-
} else {
196-
$stmt = $this->pdo->prepare("UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id");
197-
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
198-
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
199-
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
200-
$stmt->execute();
201-
202-
if (!$stmt->rowCount()) {
203-
// No session exists in the database to update. This happens when we have called
204-
// session_regenerate_id()
205-
$this->createNewSession($id, $data);
206-
}
198+
$insertStmt->bindParam(':id', $id, \PDO::PARAM_STR);
199+
$insertStmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
200+
$insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
201+
$insertStmt->execute();
202+
203+
$this->pdo->commit();
204+
} catch (\PDOException $e) {
205+
$this->pdo->rollback();
206+
207+
throw $e;
207208
}
208209
} catch (\PDOException $e) {
209-
throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
210+
throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
210211
}
211212

212213
return true;
213214
}
214215

215216
/**
216-
* Creates a new session with the given $id and $data
217+
* Returns a merge/upsert (i.e. insert or update) SQL query when supported by the database.
217218
*
218-
* @param string $id
219-
* @param string $data
220-
*
221-
* @return boolean True.
219+
* @return string|null The SQL string or null when not supported
222220
*/
223-
private function createNewSession($id, $data = '')
221+
private function getMergeSql()
224222
{
225-
// get table/column
226-
$dbTable = $this->dbOptions['db_table'];
227-
$dbDataCol = $this->dbOptions['db_data_col'];
228-
$dbIdCol = $this->dbOptions['db_id_col'];
229-
$dbTimeCol = $this->dbOptions['db_time_col'];
230-
231-
$sql = "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time)";
232-
233-
//session data can contain non binary safe characters so we need to encode it
234-
$encoded = base64_encode($data);
235-
$stmt = $this->pdo->prepare($sql);
236-
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
237-
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
238-
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
239-
$stmt->execute();
223+
$driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
224+
225+
switch ($driver) {
226+
case 'mysql':
227+
return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) " .
228+
"ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)";
229+
case 'oci':
230+
// DUAL is Oracle specific dummy table
231+
return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) " .
232+
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) " .
233+
"WHEN MATCHED THEN UPDATE SET $this->dataCol = :data";
234+
case 'sqlsrv':
235+
// MS SQL Server requires MERGE be terminated by semicolon
236+
return "MERGE INTO $this->table USING (SELECT 'x' AS dummy) AS src ON ($this->idCol = :id) " .
237+
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) " .
238+
"WHEN MATCHED THEN UPDATE SET $this->dataCol = :data;";
239+
case 'sqlite':
240+
return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)";
241+
}
240242

241-
return true;
243+
return null;
242244
}
243245

244246
/**

src/Symfony/Component/HttpKernel/DataCollector/Util/ValueExporter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function exportValue($value)
3939
}
4040

4141
if (is_resource($value)) {
42-
return sprintf('Resource(%s)', get_resource_type($value));
42+
return sprintf('Resource(%s#%d)', get_resource_type($value), $value);
4343
}
4444

4545
if (null === $value) {

src/Symfony/Component/HttpKernel/Profiler/Profiler.php

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -175,25 +175,11 @@ public function import($data)
175175
*
176176
* @return array An array of tokens
177177
*
178-
* @see http://fr2.php.net/manual/en/datetime.formats.php for the supported date/time formats
178+
* @see http://php.net/manual/en/datetime.formats.php for the supported date/time formats
179179
*/
180180
public function find($ip, $url, $limit, $method, $start, $end)
181181
{
182-
if ('' != $start && null !== $start) {
183-
$start = new \DateTime($start);
184-
$start = $start->getTimestamp();
185-
} else {
186-
$start = null;
187-
}
188-
189-
if ('' != $end && null !== $end) {
190-
$end = new \DateTime($end);
191-
$end = $end->getTimestamp();
192-
} else {
193-
$end = null;
194-
}
195-
196-
return $this->storage->find($ip, $url, $limit, $method, $start, $end);
182+
return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end));
197183
}
198184

199185
/**
@@ -291,4 +277,19 @@ public function get($name)
291277

292278
return $this->collectors[$name];
293279
}
280+
281+
private function getTimestamp($value)
282+
{
283+
if (null === $value || '' == $value) {
284+
return null;
285+
}
286+
287+
try {
288+
$value = new \DateTime(is_numeric($value) ? '@'.$value : $value);
289+
} catch (\Exception $e) {
290+
return null;
291+
}
292+
293+
return $value->getTimestamp();
294+
}
294295
}

0 commit comments

Comments
 (0)
0