You are here

public function PdoSessionHandler::write in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php \Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler::write()

File

vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php, line 322

Class

PdoSessionHandler
Session handler using a PDO connection to read and write data.

Namespace

Symfony\Component\HttpFoundation\Session\Storage\Handler

Code

public function write($sessionId, $data) {
  $maxlifetime = (int) ini_get('session.gc_maxlifetime');
  try {

    // We use a single MERGE SQL query when supported by the database.
    $mergeSql = $this
      ->getMergeSql();
    if (null !== $mergeSql) {
      $mergeStmt = $this->pdo
        ->prepare($mergeSql);
      $mergeStmt
        ->bindParam(':id', $sessionId, \PDO::PARAM_STR);
      $mergeStmt
        ->bindParam(':data', $data, \PDO::PARAM_LOB);
      $mergeStmt
        ->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
      $mergeStmt
        ->bindValue(':time', time(), \PDO::PARAM_INT);
      $mergeStmt
        ->execute();
      return true;
    }
    $updateStmt = $this->pdo
      ->prepare("UPDATE {$this->table} SET {$this->dataCol} = :data, {$this->lifetimeCol} = :lifetime, {$this->timeCol} = :time WHERE {$this->idCol} = :id");
    $updateStmt
      ->bindParam(':id', $sessionId, \PDO::PARAM_STR);
    $updateStmt
      ->bindParam(':data', $data, \PDO::PARAM_LOB);
    $updateStmt
      ->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
    $updateStmt
      ->bindValue(':time', time(), \PDO::PARAM_INT);
    $updateStmt
      ->execute();

    // When MERGE is not supported, like in Postgres, we have to use this approach that can result in
    // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior).
    // We can just catch such an error and re-execute the update. This is similar to a serializable
    // transaction with retry logic on serialization failures but without the overhead and without possible
    // false positives due to longer gap locking.
    if (!$updateStmt
      ->rowCount()) {
      try {
        $insertStmt = $this->pdo
          ->prepare("INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :lifetime, :time)");
        $insertStmt
          ->bindParam(':id', $sessionId, \PDO::PARAM_STR);
        $insertStmt
          ->bindParam(':data', $data, \PDO::PARAM_LOB);
        $insertStmt
          ->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
        $insertStmt
          ->bindValue(':time', time(), \PDO::PARAM_INT);
        $insertStmt
          ->execute();
      } catch (\PDOException $e) {

        // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
        if (0 === strpos($e
          ->getCode(), '23')) {
          $updateStmt
            ->execute();
        }
        else {
          throw $e;
        }
      }
    }
  } catch (\PDOException $e) {
    $this
      ->rollback();
    throw $e;
  }
  return true;
}