I have a very weird behaviour with PDO. I won’t go into much details as it would take up way too much time but basically what I observed is that when I re-use a \PDOStatement that performs a simple INSERT I sistematically get a wrong value when invoking PDO::lastInsertId().
The first time I execute the statement it works fine and I get back the right id. Subsequent executions will instead always return ‘0’. This is even more weird because it happens only between tests (PHPUnit ones). So say I execute the insert using the prepared statement in test1 (working), in test2 it will fail miserably.
When executing multiple times the prepared statement in a non unit-testing environment (in a simple php file fro instance) it all works fine and the last inserted ids are always accurate. Very weird indeed.
Here’s the test (note that PersistencyManagerInstance is just a plain intsance of PersistencyManager):
<?php
class PersistencyManagerTest extends PHPUnit_Framework_TestCase {
const DELETE_ALL = "TRUNCATE user";
const ADD_USER = "INSERT INTO user values(null, :username, :password)";
const CHECK_USER_EXISTENCE = "SELECT * FROM user WHERE username = :username AND password = :password";
const DELETE_USER_BY_ID = "DELETE FROM user WHERE id = ?";
protected $manager = null;
public function __construct() {
$this->manager = new PersistencyManagerInstance(PDOFactory::build());
}
public function setUp() {
$this->manager->exec(self::DELETE_ALL);
}
public function tearDown() {
$this->manager->exec(self::DELETE_ALL);
}
public function testInsert() {
$user = new User("laurent", "password");
$id = $this->manager->insert(self::ADD_USER, $user->export());
$this->assertEquals("1", $id);
}
public function testInsertAgain() {
$user1 = new User("laurent1", "password1");
$id = $this->manager->insert(self::ADD_USER, $user1->export());
$this->assertEquals("1", $id);
}
public function testQuery() {
$user = new User("laurent", "password");
$this->manager->insert(self::ADD_USER, $user->export());
$results = $this->manager->query(self::CHECK_USER_EXISTENCE, $user->export());
$this->assertEquals(1, count($results));
}
public function testExec() {
$user = new User("laurent", "password---");
$id = $this->manager->insert(self::ADD_USER, $user->export());
$affected = $this->manager->exec(self::DELETE_USER_BY_ID, array($id));
$this->assertEquals(1, $affected);
}
}
testInsert works while testInsertAgain does not.
and here’s the class:
<?php
namespace memory\manager;
use \PDO;
abstract class PersistencyManager {
/**
* @var array An array of \PDOStatement objects
*/
protected static $ps = array();
/**
* @var \PDO
*/
protected $connection = null;
protected function prepareStmt($sql) {
// return $this->connection->prepare($sql);
$key = md5($sql);
if (!isset(self::$ps[$key])) {
self::$ps[$key] = $this->connection->prepare($sql);
}
return self::$ps[$key];
}
public function __construct(PDO $connection) {
$this->connection = $connection;
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function __destruct() {
$this->connection = null;
}
/**
* Good for SELECT operations. By default it fetches using arrays.
* @param string $sql
* @param array $values
* @param integer $fetchStyle
* @return array A list of matching elements (The elements' type depends on $fetchStyle)
*/
public function query($sql, array $values = array(), $fetchStyle = PDO::FETCH_ASSOC) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$prepared->setFetchMode($fetchStyle);
$all = $prepared->fetchAll();
$prepared->closeCursor();
return $all;
}
/**
* Good for INSERT operations.
* @param string $sql
* @param array $values
* @return string Last inserted element's id in string format
*/
public function insert($sql, array $values = array()) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$prepared->closeCursor();
return $this->connection->lastInsertId();
}
/**
* Good for all the remaining routines.
* @param string $sql
* @param array $values
* @return integer The number of effected rows
*/
public function exec($sql, array $values = array()) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$count = $prepared->rowCount();
$prepared->closeCursor();
return $count;
}
}
Any idea?
Cheers
guys I was starting a new connection at every test. That was the reason.