diff --git a/lib/Alchemy/Phrasea/Helper/User/Manage.php b/lib/Alchemy/Phrasea/Helper/User/Manage.php index 6719a9884e..5976f91b5b 100644 --- a/lib/Alchemy/Phrasea/Helper/User/Manage.php +++ b/lib/Alchemy/Phrasea/Helper/User/Manage.php @@ -163,6 +163,7 @@ class Manage extends Helper $stmt = $conn->prepare($sql); $stmt->execute(array(':email' => $email)); $row = $stmt->fetch(\PDO::FETCH_ASSOC); + $stmt->closeCursor(); $count = count($row); if (!is_array($row) || $count == 0) { diff --git a/lib/classes/ACL.php b/lib/classes/ACL.php index 8cc43770d3..c5ee99efd0 100644 --- a/lib/classes/ACL.php +++ b/lib/classes/ACL.php @@ -1130,6 +1130,7 @@ class ACL implements cache_cacheableInterface throw new Exception('Error while deleteing some rights'); } } + $stmt_del->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_BAS); return $this; @@ -1188,6 +1189,7 @@ class ACL implements cache_cacheableInterface if (!$this->has_access_to_sbas($sbas_id)) $stmt_ins->execute(array(':sbas_id' => $sbas_id, ':usr_id' => $usr_id)); } + $stmt_ins->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_SBAS); return $this; @@ -1305,6 +1307,7 @@ class ACL implements cache_cacheableInterface if (!$stmt_up->execute($params)) { throw new Exception('Error while updating some rights'); } + $stmt_up->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_SBAS); return $this; diff --git a/lib/classes/User/Adapter.php b/lib/classes/User/Adapter.php index decfdd9cb0..93bfbbf439 100644 --- a/lib/classes/User/Adapter.php +++ b/lib/classes/User/Adapter.php @@ -1420,6 +1420,7 @@ class User_Adapter implements User_Interface, cache_cacheableInterface ':prop' => $prop, ':value' => $value )); + $stmt->closeCursor(); $this->delete_data_from_cache(); } catch (\Exception $e) { diff --git a/lib/classes/collection.php b/lib/classes/collection.php index 910e771e41..a44f01677a 100644 --- a/lib/classes/collection.php +++ b/lib/classes/collection.php @@ -81,6 +81,7 @@ class collection implements cache_cacheableInterface $stmt = $connbas->prepare($sql); $stmt->execute(array(':coll_id' => $this->coll_id)); $row = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt->closeCursor(); if ( ! $row) throw new Exception('Unknown collection ' . $this->coll_id . ' on ' . $this->databox->get_dbname()); diff --git a/lib/classes/connection.php b/lib/classes/connection.php index cd7effcc3e..4e0bb707d4 100644 --- a/lib/classes/connection.php +++ b/lib/classes/connection.php @@ -167,6 +167,7 @@ class connection public static function close_PDO_connection($name) { if (isset(self::$_PDO_instance[$name])) { + self::$_PDO_instance[$name]->disconnect(); self::$_PDO_instance[$name] = null; unset(self::$_PDO_instance[$name]); } diff --git a/lib/classes/connection/abstract.php b/lib/classes/connection/abstract.php index 7500626642..9af46eb344 100644 --- a/lib/classes/connection/abstract.php +++ b/lib/classes/connection/abstract.php @@ -15,11 +15,12 @@ * @license http://opensource.org/licenses/gpl-3.0 GPLv3 * @link www.phraseanet.com */ -abstract class connection_abstract extends PDO +abstract class connection_abstract { protected $name; protected $credentials = array(); protected $multi_db = true; + protected $connection; public function get_credentials() { @@ -42,8 +43,12 @@ abstract class connection_abstract extends PDO public function ping() { + if (null === $this->connection) { + $this->initConn(); + } + try { - $this->query('SELECT 1'); + $this->connection->query('SELECT 1'); } catch (PDOException $e) { return false; } @@ -51,41 +56,16 @@ abstract class connection_abstract extends PDO return true; } - /** - * - * @param string $statement - * @param array $driver_options - * @return PDOStatement - */ - public function prepare($statement, $driver_options = array()) - { - return parent::prepare($statement, $driver_options); - } - - /** - * - * @return boolean - */ - public function beginTransaction() - { - return parent::beginTransaction(); - } - - /** - * - * @return boolean - */ - public function commit() - { - return parent::commit(); - } - /** * * @return string */ public function server_info() { - return parent::getAttribute(constant("PDO::ATTR_SERVER_VERSION")); + if (null === $this->connection) { + $this->initConn(); + } + + return $this->connection->getAttribute(constant("PDO::ATTR_SERVER_VERSION")); } } diff --git a/lib/classes/connection/interface.php b/lib/classes/connection/interface.php index 58b322e4ca..d30a407bc5 100644 --- a/lib/classes/connection/interface.php +++ b/lib/classes/connection/interface.php @@ -17,7 +17,6 @@ */ interface connection_interface { - public function ping(); public function get_name(); @@ -28,11 +27,5 @@ interface connection_interface public function close(); - public function prepare($statement, $driver_options = array()); - - public function beginTransaction(); - - public function commit(); - public function server_info(); } diff --git a/lib/classes/connection/pdo.php b/lib/classes/connection/pdo.php index f12ee10bac..e4651fc053 100644 --- a/lib/classes/connection/pdo.php +++ b/lib/classes/connection/pdo.php @@ -36,42 +36,45 @@ class connection_pdo extends connection_abstract implements connection_interface { $this->debug = $debug; $this->name = $name; - if ($dbname) - $dsn = 'mysql:dbname=' . $dbname . ';host=' . $hostname . ';port=' . $port . ';'; - else - $dsn = 'mysql:host=' . $hostname . ';port=' . $port . ';'; $this->credentials['hostname'] = $hostname; $this->credentials['port'] = $port; $this->credentials['user'] = $user; $this->credentials['password'] = $passwd; + if ($dbname) $this->credentials['dbname'] = $dbname; - parent::__construct($dsn, $user, $passwd, $options); - - $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->query(" - SET character_set_results = 'utf8', character_set_client = 'utf8', - character_set_connection = 'utf8', character_set_database = 'utf8', - character_set_server = 'utf8'"); + $this->initConn(); return $this; } + protected function initConn() + { + $this->connection = null; + + if (isset($this->credentials['dbname'])) + $dsn = 'mysql:dbname=' . $this->credentials['dbname'] . ';host=' . $this->credentials['hostname'] . ';port=' . $this->credentials['port'] . ';'; + else + $dsn = 'mysql:host=' . $this->credentials['hostname'] . ';port=' . $this->credentials['port'] . ';'; + + $this->connection = new \PDO($dsn, $this->credentials['user'], $this->credentials['password'], array()); + + $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->connection->exec(" + SET character_set_results = 'utf8', character_set_client = 'utf8', + character_set_connection = 'utf8', character_set_database = 'utf8', + character_set_server = 'utf8'"); + } + /** * - * @param type $statement - * @param type $driver_options - * @return PDOStatement + * @return void */ - public function prepare($statement, $driver_options = array()) + public function disconnect() { - if ($this->debug) { - return new connection_pdoStatementDebugger(parent::prepare($statement, $driver_options)); - } else { - return parent::prepare($statement, $driver_options); - } + $this->connection = null; } /** @@ -83,6 +86,48 @@ class connection_pdo extends connection_abstract implements connection_interface connection::close_PDO_connection($this->name); } + public function __call($method, $args) + { + if (null === $this->connection) { + $this->initConn(); + } + + if (!method_exists($this->connection, $method)) { + throw new \BadMethodCallException(sprintf('Method %s does not exist', $method)); + } + + $tries = 0; + + do { + $tries++; + try { + set_error_handler(function ($errno, $errstr) { + if (false !== strpos($errstr, 'Error while sending QUERY packet')) { + throw new \Exception('MySQL server has gone away'); + } + throw new \Exception($errstr); + }); + + if ('prepare' === $method && $this->debug) { + $ret = new connection_pdoStatementDebugger(call_user_func_array(array($this->connection, $method), $args)); + } else { + $ret = call_user_func_array(array($this->connection, $method), $args); + } + restore_error_handler(); + + return $ret; + } catch (\Exception $e) { + restore_error_handler(); + + $found = (false !== strpos($e->getMessage(), 'MySQL server has gone away')) || (false !== strpos($e->getMessage(), 'errno=32 Broken pipe')); + if ($tries >= 2 || !$found) { + throw $e; + } + $this->initConn(); + } + } while (true); + } + /** * * @param string $message diff --git a/lib/classes/databox.php b/lib/classes/databox.php index 5a8a10b58a..1369b4768b 100644 --- a/lib/classes/databox.php +++ b/lib/classes/databox.php @@ -1009,6 +1009,7 @@ class databox extends base unset($e); } } + $stmt->closeCursor(); $user->ACL()->give_access_to_base($base_ids); foreach ($base_ids as $base_id) { @@ -1022,8 +1023,6 @@ class databox extends base ); } - $stmt->closeCursor(); - return $this; } diff --git a/lib/classes/databox/field.php b/lib/classes/databox/field.php index ef2f729ef7..04e241201a 100644 --- a/lib/classes/databox/field.php +++ b/lib/classes/databox/field.php @@ -413,6 +413,7 @@ class databox_field implements cache_cacheableInterface $stmt = $connbas->prepare($sql); $stmt->execute($params); + $stmt->closeCursor(); if ($this->renamed) { caption_field::rename_all_metadatas($this); diff --git a/lib/classes/patch/370alpha7a.php b/lib/classes/patch/370alpha7a.php index 7e36fe1629..c94c77af45 100644 --- a/lib/classes/patch/370alpha7a.php +++ b/lib/classes/patch/370alpha7a.php @@ -65,6 +65,7 @@ class patch_370alpha7a implements patchInterface $stmt = $conn->prepare($sql); $stmt->execute(); $rs = $stmt->fetchAll(); + $stmt->closeCursor(); } catch (\PDOException $e) { // table not found if ($e->getCode() == '42S02') { diff --git a/lib/classes/patch/370alpha8a.php b/lib/classes/patch/370alpha8a.php index 25ced1877a..60b3a260af 100644 --- a/lib/classes/patch/370alpha8a.php +++ b/lib/classes/patch/370alpha8a.php @@ -72,8 +72,8 @@ class patch_370alpha8a implements patchInterface if (($stmt = $conn->prepare($sql)) !== FALSE) { $stmt->execute(); $ttasks = $row = $stmt->fetchAll(); - $stmt->closeCursor(); } + $stmt->closeCursor(); $tdom = array(); // key = period $taskstodel = array(); diff --git a/lib/classes/patchthesaurus/204.php b/lib/classes/patchthesaurus/204.php index 0318ccc822..79c32f57af 100644 --- a/lib/classes/patchthesaurus/204.php +++ b/lib/classes/patchthesaurus/204.php @@ -74,7 +74,7 @@ class patchthesaurus_204 $sql2 = "UPDATE record SET status=((status | 15) & ~2)"; - $stmt = $connbas->prepare($sql); + $stmt = $connbas->prepare($sql2); $stmt->execute(); $stmt->closeCursor(); } diff --git a/lib/classes/record/adapter.php b/lib/classes/record/adapter.php index 2563c29e98..9f705ab399 100644 --- a/lib/classes/record/adapter.php +++ b/lib/classes/record/adapter.php @@ -1055,6 +1055,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface ':record_id' => $this->record_id ) ); + $stmt->closeCursor(); $this->reindex(); @@ -1181,6 +1182,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface $sql = 'UPDATE record SET jeton=(jeton | ' . JETON_MAKE_SUBDEF . ') WHERE record_id = :record_id'; $stmt = $connbas->prepare($sql); $stmt->execute(array(':record_id' => $this->get_record_id())); + $stmt->closeCursor(); return $this; } @@ -1197,6 +1199,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface WHERE record_id= :record_id'; $stmt = $connbas->prepare($sql); $stmt->execute(array(':record_id' => $this->record_id)); + $stmt->closeCursor(); return $this; } @@ -1264,6 +1267,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface ':originalname' => null, ':mime' => null, )); + $stmt->closeCursor(); $story_id = $databox->get_connection()->lastInsertId(); @@ -1319,6 +1323,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface ':originalname' => $file->getOriginalName(), ':mime' => $file->getFile()->getMimeType(), )); + $stmt->closeCursor(); $record_id = $databox->get_connection()->lastInsertId(); diff --git a/lib/classes/task/period/RecordMover.php b/lib/classes/task/period/RecordMover.php index 841205d96b..73f78e0b03 100644 --- a/lib/classes/task/period/RecordMover.php +++ b/lib/classes/task/period/RecordMover.php @@ -761,10 +761,10 @@ class task_period_RecordMover extends task_appboxAbstract while (($row = $stmt->fetch(PDO::FETCH_ASSOC))) { $result['rids'][] = $row['record_id']; } - $stmt->closeCursor(); } else { $result['err'] = $connbas->last_error(); } + $stmt->closeCursor(); return $result; } diff --git a/tests/Alchemy/Tests/Phrasea/Application/OAuth2Test.php b/tests/Alchemy/Tests/Phrasea/Application/OAuth2Test.php index 93cacaa99d..8427cd95b6 100644 --- a/tests/Alchemy/Tests/Phrasea/Application/OAuth2Test.php +++ b/tests/Alchemy/Tests/Phrasea/Application/OAuth2Test.php @@ -70,6 +70,7 @@ class oauthv2_application_test extends \PhraseanetWebTestCaseAuthenticatedAbstra $t = array(':id' => $app->get_id()); $stmt = $conn->prepare($sql); $stmt->execute($t); + $stmt->closeCursor(); $sql = ' DELETE FROM api_accounts WHERE api_account_id = :id @@ -78,6 +79,7 @@ class oauthv2_application_test extends \PhraseanetWebTestCaseAuthenticatedAbstra $t = array(':id' => $acc->get_id()); $stmt = $conn->prepare($sql); $stmt->execute($t); + $stmt->closeCursor(); } /** @@ -119,6 +121,7 @@ class oauthv2_application_test extends \PhraseanetWebTestCaseAuthenticatedAbstra $stmt = $conn->prepare($sql); $stmt->execute($t); $result = $stmt->fetch(\PDO::FETCH_ASSOC); + $stmt->closeCursor(); return $result; } @@ -131,6 +134,7 @@ class oauthv2_application_test extends \PhraseanetWebTestCaseAuthenticatedAbstra $stmt = $conn->prepare($sql); $stmt->execute($t); $row = $stmt->fetch(\PDO::FETCH_ASSOC); + $stmt->closeCursor(); return new \API_OAuth2_Account(self::$DI['app'], $row["api_account_id"]); } diff --git a/tests/Alchemy/Tests/Phrasea/Authentication/Phrasea/NativeAuthenticationTest.php b/tests/Alchemy/Tests/Phrasea/Authentication/Phrasea/NativeAuthenticationTest.php index 9b3665ba53..0b1dc17066 100644 --- a/tests/Alchemy/Tests/Phrasea/Authentication/Phrasea/NativeAuthenticationTest.php +++ b/tests/Alchemy/Tests/Phrasea/Authentication/Phrasea/NativeAuthenticationTest.php @@ -5,7 +5,7 @@ namespace Alchemy\Tests\Phrasea\Authentication\Phrasea; use Alchemy\Phrasea\Authentication\Phrasea\NativeAuthentication; use Alchemy\Phrasea\Authentication\Exception\AccountLockedException; -class NativeAuthenticationTest extends \PHPUnit_Framework_TestCase +class NativeAuthenticationTest extends \PhraseanetPHPUnitAbstract { /** * @dataProvider provideReservedUsernames @@ -16,7 +16,7 @@ class NativeAuthenticationTest extends \PHPUnit_Framework_TestCase $encoder = $this->getEncoderMock(); $oldEncoder = $this->getOldEncoderMock(); - $conn = $this->getMock('connection_interface'); + $conn = $this->createConnectionMock(); $request = $this->getRequestMock(); $auth = new NativeAuthentication($encoder, $oldEncoder, $conn); @@ -180,7 +180,7 @@ class NativeAuthenticationTest extends \PHPUnit_Framework_TestCase $encoder = $this->getEncoderMock(); $oldEncoder = $this->getOldEncoderMock(); - $conn = $this->getMock('connection_interface'); + $conn = $this->createConnectionMock(); $statement = $this->getMock('PDOStatement'); $statement @@ -248,7 +248,7 @@ class NativeAuthenticationTest extends \PHPUnit_Framework_TestCase private function getConnectionMock($username, $row = null) { - $conn = $this->getMock('connection_interface'); + $conn = $this->createConnectionMock(); $statement = $this->getMock('PDOStatement'); diff --git a/tests/Alchemy/Tests/Phrasea/SearchEngine/PhraseaEngineTest.php b/tests/Alchemy/Tests/Phrasea/SearchEngine/PhraseaEngineTest.php index 526b88d3fc..024250c6f1 100644 --- a/tests/Alchemy/Tests/Phrasea/SearchEngine/PhraseaEngineTest.php +++ b/tests/Alchemy/Tests/Phrasea/SearchEngine/PhraseaEngineTest.php @@ -90,6 +90,7 @@ class PhraseaEngineTest extends SearchEngineAbstractTest break; } + $stmt->closeCursor(); $date = new \DateTime('-1 months'); diff --git a/tests/classes/PhraseanetPHPUnitAbstract.php b/tests/classes/PhraseanetPHPUnitAbstract.php index 2742a90043..17742b07e2 100644 --- a/tests/classes/PhraseanetPHPUnitAbstract.php +++ b/tests/classes/PhraseanetPHPUnitAbstract.php @@ -107,6 +107,8 @@ abstract class PhraseanetPHPUnitAbstract extends WebTestCase parent::setUp(); + connection::close_connections(); + \PHPUnit_Framework_Error_Warning::$enabled = true; \PHPUnit_Framework_Error_Notice::$enabled = true; @@ -989,4 +991,9 @@ abstract class PhraseanetPHPUnitAbstract extends WebTestCase ->disableOriginalConstructor() ->getMock(); } + + public function createConnectionMock() + { + return $this->getMock('connection_interface', array('close', 'get_credentials', 'server_info', 'prepare', 'beginTransaction', 'commit', 'ping', 'get_name', 'is_multi_db')); + } } diff --git a/tests/classes/PhraseanetWebTestCaseAuthenticatedAbstract.php b/tests/classes/PhraseanetWebTestCaseAuthenticatedAbstract.php index 8bfdf66233..36dfccd92f 100644 --- a/tests/classes/PhraseanetWebTestCaseAuthenticatedAbstract.php +++ b/tests/classes/PhraseanetWebTestCaseAuthenticatedAbstract.php @@ -136,10 +136,12 @@ abstract class PhraseanetWebTestCaseAuthenticatedAbstract extends PhraseanetPHPU ->get_connection() ->prepare('DROP DATABASE IF EXISTS `unit_test_db`'); $stmt->execute(); + $stmt->closeCursor(); $stmt = self::$DI['app']['phraseanet.appbox'] ->get_connection() ->prepare('DELETE FROM sbas WHERE dbname = "unit_test_db"'); $stmt->execute(); + $stmt->closeCursor(); } protected function createDatabase() diff --git a/tests/classes/Session/Session_LoggerTest.php b/tests/classes/Session/Session_LoggerTest.php index 48b6213f54..6738042674 100644 --- a/tests/classes/Session/Session_LoggerTest.php +++ b/tests/classes/Session/Session_LoggerTest.php @@ -47,6 +47,7 @@ class Session_LoggerTest extends PhraseanetPHPUnitAbstract $stmt->execute($params); $this->assertEquals(1, $stmt->rowCount()); $row = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt->closeCursor(); $this->assertEquals($this->object->get_id(), $row['id']); $log_id = $this->object->get_id(); $ses_id = self::$DI['app']['session']->get('session_id'); @@ -67,5 +68,6 @@ class Session_LoggerTest extends PhraseanetPHPUnitAbstract $this->assertEquals(1, $stmt->rowCount()); $row = $stmt->fetch(PDO::FETCH_ASSOC); $this->assertEquals($log_id, $row['id']); + $stmt->closeCursor(); } } diff --git a/tests/classes/connectionTest.php b/tests/classes/connectionTest.php new file mode 100644 index 0000000000..1c4cf74e5d --- /dev/null +++ b/tests/classes/connectionTest.php @@ -0,0 +1,13 @@ +exec('SET @@local.wait_timeout= 1'); + usleep(1200000); + $conn->exec('SHOW DATABASES'); + connection::close_connections(); + } +}