From b034fa3625344ce86f44bf0a32f029c402544108 Mon Sep 17 00:00:00 2001 From: Dominique Fournier Date: Mon, 22 Sep 2014 10:52:53 +0000 Subject: [PATCH] dblayer : add more debug dblayer : add more unit tests (foreign keys) dblayer : add the same answer when updating a line with the same informations dblayer : better support of foreign keys git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@1842 bf3deb0d-5f1a-0410-827f-c0cc1f45334c --- Tests/dblayerComplet.php | 75 ++++++++++++++++++++---- dblayer.php | 123 ++++++++++++++++++++++++++++++--------- 2 files changed, 161 insertions(+), 37 deletions(-) diff --git a/Tests/dblayerComplet.php b/Tests/dblayerComplet.php index 49e8801..cf8e9d6 100644 --- a/Tests/dblayerComplet.php +++ b/Tests/dblayerComplet.php @@ -39,20 +39,22 @@ class test_dblayer_{ENGINE} extends PHPUnit_Framework_TestCase $dbconfig = $this->confs["{ENGINE}"]; $db = new dblayer ($dbconfig["dsn"], $dbconfig["username"], $dbconfig["password"], $dbconfig["driver_options"]); - $db->table = "grouped"; - try + foreach (array ("users", "grouped") as $table) { - $res = $db->dropTable(); - } - catch (Exception $e) - { - + $db->table = $table; + try + { + $res = $db->dropTable(); + } + catch (Exception $e) + { + } } // Never generate an error, just drop the table if it exists, and do noting // if it doesn't exists } - public function test_createTable () + public function test_createTable1 () { // Create a table named group $dbconfig = $this->confs["{ENGINE}"]; @@ -63,11 +65,13 @@ class test_dblayer_{ENGINE} extends PHPUnit_Framework_TestCase "object"=>array ("varchar", "255", "not null"), "where"=>array ("varchar", "255", "not null"), "with space"=>array ("varchar", "255", "not null")); + $db->unique = array (); + $db->primary = "group"; $res = $db->createTable (); $this->assertSame (0, $res); } - public function test_insert () + public function test_insert1 () { $dbconfig = $this->confs["{ENGINE}"]; $db = new dblayer ($dbconfig["dsn"], $dbconfig["username"], @@ -78,6 +82,7 @@ class test_dblayer_{ENGINE} extends PHPUnit_Framework_TestCase "where"=>array ("varchar", "255", "not null"), "with space"=>array ("varchar", "255", "not null")); $db->unique = array (); + $db->primary = "group"; $res = $db->insert (array ("group"=>"gr ou\"p", "object"=>"/éobj%", "where"=>"\$'\"", @@ -198,10 +203,60 @@ class test_dblayer_{ENGINE} extends PHPUnit_Framework_TestCase "with space"=>array ("varchar", "255", "not null")); $db->unique = array ("group"); $db->primary = "group"; - // Update primary key with primary key in unique + // Update primary key with primary key in unique with same values to test if + // the exception is NOT raised $res = $db->update ("NEW GROUP", array ("group"=>"NEW GROUP", "object"=>"%éàoppp", "with space"=>"WITH SPACE")); + // SQLite and PostgreSQL return 1 line modified + // MySQL return 0 by default because there is no modification on the line + // http://fr2.php.net/manual/en/pdostatement.rowcount.php#104930 + // Now, the MYSQL_ATTR_FOUND_ROWS is added to connection to be the same in + // all the engines $this->assertSame (1, $res); } + + // Part to test the foreign keys + public function test_createTable2 () + { + // Create a table named group + $dbconfig = $this->confs["{ENGINE}"]; + $db = new dblayer ($dbconfig["dsn"], $dbconfig["username"], + $dbconfig["password"], $dbconfig["driver_options"]); + $db->table = "users"; + $db->fields = array ("user"=>array ("varchar", "255", "not null"), + "groupmember"=>array ("varchar", "255", "not null"), + "where"=>array ("varchar", "255", "not null"), + "with space"=>array ("varchar", "255", "not null")); + $db->foreign = array ( + "groupmember"=>array ("grouped", "group", "ON UPDATE CASCADE ON DELETE CASCADE"), + ); + + $res = $db->createTable (); + $this->assertSame (0, $res); + } + + public function test_insert2 () + { + $dbconfig = $this->confs["{ENGINE}"]; + $db = new dblayer ($dbconfig["dsn"], $dbconfig["username"], + $dbconfig["password"], $dbconfig["driver_options"]); + $db->table = "users"; + $db->fields = array ("user"=>array ("varchar", "255", "not null"), + "groupmember"=>array ("varchar", "255", "not null"), + "where"=>array ("varchar", "255", "not null"), + "with space"=>array ("varchar", "255", "not null")); + $db->unique = array ("user"); + $db->primary = "user"; + $db->foreign = array ( + "groupmember"=>array ("grouped", "group", "ON UPDATE CASCADE ON DELETE CASCADE"), + ); + $res = $db->insert (array ("user"=>"Us ou\"r", + "groupmember"=>"NEW GROUP", + "where"=>"\$'\"", + "with space"=>"with space")); + // SQLite start at 1, MySQL start at 0... + $this->assertThat($res, $this->lessThanOrEqual(2)); + } + } diff --git a/dblayer.php b/dblayer.php index 15c3439..655f683 100644 --- a/dblayer.php +++ b/dblayer.php @@ -9,9 +9,9 @@ The dbLayer provide an abstraction layer on PDO to be easier on all the CRUD (Create, Read, Update, Delete) operations, accross all the databases engines. To use it, extends in your code this class, and define the attributes : -- protected $table : name of the table you want to use -- protected $fields : description of all the fields in the database like : - protected $fields = array ( +- public $table : name of the table you want to use +- public $fields : description of all the fields in the database like : + public $fields = array ( "id"=>array ("integer", "not null", "autoincrement"), "zone"=>array ("varchar", "255", "not null"), "viewname"=>array ("varchar", "255"), @@ -20,12 +20,17 @@ To use it, extends in your code this class, and define the attributes : "opendate"=>array ("datetime", "not null"), "closedate"=>array ("datetime"), ); -- protected $primary = "id" ; the primary key of the table (in text). Actually +- public $primary = "id" ; the primary key of the table (in text). Actually the dbLayer abstraction don't supports primary key on multiples columns -- protected $unique = array ("column", array ("column1", "column2");) +- public $unique = array ("column", array ("column1", "column2");) +- public $foreign : Add the support of foreign keys. Must be defined like : + public $foreign = array ( + "localfield"=>array ("foreign Table", "foreign key", + "ON UPDATE CASCADE ON DELETE CASCADE"), + ); Optionnaly, you can add the -- protected $debug = TRUE : enable the debug on screen (NOT FOR PROD !!) +- public $debug = TRUE : enable the debug on screen (NOT FOR PROD !!) */ require_once ("domframework/verify.php"); @@ -89,20 +94,23 @@ class dblayer extends PDO public function __construct ($dsn, $username=null, $password=null, $driver_options=null) { - try - { - $this->db = new PDO ($dsn, $username, $password, $driver_options); - $this->db->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - } - catch (Exception $e) - { - throw new Exception ("PDO error : ".$e->getMessage(), 500); - } - + $driver = @explode (":", $dsn); + if (! isset ($driver[0])) + throw new Exception (_("No valid DSN provided"), 500); // Force specifics initialisations - switch ($this->db->getAttribute(PDO::ATTR_DRIVER_NAME)) + switch ($driver[0]) { case "sqlite": + try + { + $this->db = new PDO ($dsn, $username, $password, $driver_options); + $this->db->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + catch (Exception $e) + { + throw new Exception ("PDO error : ".$e->getMessage(), 500); + } + // Look at the right to write in database and in the directory $file = substr ($dsn, 7); if (! is_writeable (dirname ($file))) @@ -121,11 +129,32 @@ class dblayer extends PDO $this->sep = "`"; break; case "mysql": + try + { + $driver_options[PDO::MYSQL_ATTR_FOUND_ROWS] = 1; + $this->db = new PDO ($dsn, $username, $password, $driver_options); + $this->db->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + catch (Exception $e) + { + throw new Exception ("PDO error : ".$e->getMessage(), 500); + } + // Set the coding to UTF8 $this->db->exec("SET CHARACTER SET utf8"); $this->sep = "`"; break; case "pgsql": + try + { + $this->db = new PDO ($dsn, $username, $password, $driver_options); + $this->db->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + catch (Exception $e) + { + throw new Exception ("PDO error : ".$e->getMessage(), 500); + } + // Set the coding to UTF8 $this->db->exec("SET NAMES 'utf8'"); $this->sep = "\""; @@ -202,9 +231,11 @@ class dblayer extends PDO */ public function verify ($datas, $updatekey=false) { + if ($this->debug) echo "== Entering verify\n"; $errors = array (); foreach ($this->fields as $key=>$params) { + if ($this->debug) echo " verify ($key)\n"; if ($updatekey === false) { // Don't check if there is an update : the database is already filled @@ -311,6 +342,7 @@ class dblayer extends PDO if (count ($errors) !== 0) return $errors; + if ($this->debug) echo " verify inconsistency\n"; // Check for inconsistency $verify = $this->verifyAll ($datas); if (count ($verify)) @@ -325,6 +357,7 @@ class dblayer extends PDO if ($updatekey !== false) { + if ($this->debug) echo " verify in updatekey\n"; // Check if the unique constrain is valid before doing the insertion // 1. Read the actual state $before = $this->read (array (array ($this->primary, $updatekey))); @@ -339,14 +372,22 @@ class dblayer extends PDO } else { + if ($this->debug) echo " verify NO updatekey\n"; $after = $datasOK; } // Check if the unique constrain is valid before doing the insertion - foreach ($this->unique as $columns) + foreach ($this->unique as $k=>$columns) { + if ($this->primary === null) + { + return array (dgettext("domframework", + "No field primary defined for tests in primary")); + } + if (is_array ($columns)) { // Multiple columns in unique + if ($this->debug) echo " verify unique multiple $k\n"; $select = array (); if ($updatekey !== false) $select[] = array ($this->primary, $updatekey, "!="); @@ -372,6 +413,7 @@ class dblayer extends PDO else { // One column in unique + if ($this->debug) echo " verify unique one column $k\n"; if (!array_key_exists ($columns, $after)) continue; $select = array (); if ($updatekey !== false) @@ -395,36 +437,49 @@ class dblayer extends PDO // Check if the foreign keys constrains are valid before doing the insertion foreach ($this->foreign as $foreign=>$data) { + if ($this->debug) echo " verify foreign $foreign\n"; if (! isset ($datas[$foreign])) { $errors[] = array ("error", sprintf (dgettext("domframework", "The foreign column '%s' is not provided"), $foreign)); + return $errors; + } + if (! isset ($datas[$foreign][0])) + { + $errors[] = array ("error", sprintf (dgettext("domframework", + "The field type for column '%s' is not provided"), + $foreign)); + return $errors; continue; } $table = $data[0]; $column = $data[1]; - $req = "SELECT $column ". + $req = "SELECT $this->sep$column$this->sep ". "FROM $this->sep$this->tableprefix$table$this->sep ". "WHERE $this->sep$column$this->sep=:".md5 ($column); if ($this->debug) echo "DEBUG : $req\n"; $st = $this->db->prepare ($req); $val = $datas[$foreign]; $key = $column; + if ($this->debug) echo "DEBUG BIND : ".$this->fields[$foreign][0]."\n"; if ($this->debug) echo "DEBUG BIND : $column(".md5 ($column)."->". var_export ($val, TRUE)."\n"; if ($val === null) $st->bindValue (":".md5 ($key), $val, PDO::PARAM_NULL); - elseif ($this->fields[$key][0] === "integer") + elseif ($this->fields[$foreign][0] === "integer") $st->bindValue (":".md5 ($key), $val, PDO::PARAM_INT); - elseif ($this->fields[$key][0] === "varchar") + elseif ($this->fields[$foreign][0] === "varchar") $st->bindValue (":".md5 ($key), $val, PDO::PARAM_STR); - elseif ($this->fields[$key][0] === "datetime") + elseif ($this->fields[$foreign][0] === "datetime") $st->bindValue (":".md5 ($key), $val, PDO::PARAM_STR); - elseif ($this->fields[$key][0] === "date") + elseif ($this->fields[$foreign][0] === "date") $st->bindValue (":".md5 ($key), $val, PDO::PARAM_STR); else - throw new Exception ("TO BE DEVELOPPED : ".$this->fields[$key][0], 500); + { + throw new Exception ("TO BE DEVELOPPED : ".$this->fields[$foreign][0], + 500); + } $st->execute (); $res = array (); while ($d = $st->fetch (PDO::FETCH_ASSOC)) @@ -452,12 +507,16 @@ class dblayer extends PDO @param array $datas Datas to be recorded (column=>value)*/ public function insert ($datas) { + if ($this->debug) echo "== Entering insert\n"; if ($this->db === null) throw new Exception (dgettext("domframework", "Database not connected"), 500); if ($this->unique === null) throw new Exception (dgettext("domframework", "Unique fields of table are not defined"), 500); + if (! is_array ($this->unique)) + throw new Exception (dgettext("domframework", + "The unique configuration is not an array"), 500); if (!is_array ($datas)) throw new Exception (dgettext("domframework", "The datas provided to create are not array"), @@ -472,7 +531,10 @@ class dblayer extends PDO $datasOK = array (); $errors = $this->verify ($datas); if (count ($errors) !== 0) - throw new Exception (reset ($errors), 405); + { + $errors = reset ($errors); + throw new Exception ($errors[1], 405); + } foreach ($this->fields as $field=>$desc) { if (isset ($datas[$field])) @@ -527,6 +589,7 @@ class dblayer extends PDO public function read ($select=null, $display=null, $order=null, $whereOr=false) { + if ($this->debug) echo "== Entering read\n"; if ($this->db === null) throw new Exception (dgettext("domframework", "Database not connected"), 500); @@ -638,9 +701,11 @@ class dblayer extends PDO Return the number of rows modified @param string|integer $updatekey The key applied on primary key to be updated - @param array $datas The values to be updated */ + @param array $datas The values to be updated + @return the number of lines modified */ public function update ($updatekey, $datas) { + if ($this->debug) echo "== Entering update\n"; if ($this->db === null) throw new Exception (dgettext("domframework", "Database not connected"), 500); @@ -652,7 +717,6 @@ class dblayer extends PDO $errors = $this->verify ($datas, $updatekey); if (count ($errors) !== 0) { - $errors = reset ($errors); throw new Exception ($errors[1], 405); } foreach ($this->fields as $field=>$desc) @@ -724,6 +788,7 @@ class dblayer extends PDO @param strin|integer $deletekey The key of primary key to be deleted */ public function delete ($deletekey) { + if ($this->debug) echo "== Entering delete\n"; if ($this->db === null) throw new Exception (dgettext("domframework", "Database not connected")); $req = "DELETE FROM $this->sep$this->tableprefix$this->table$this->sep "; @@ -740,6 +805,7 @@ class dblayer extends PDO /** Translation of fields */ public function titles () { + if ($this->debug) echo "== Entering titles\n"; if (count ($this->fields) === 0) throw new Exception (dgettext("domframework", "No Field defined"), 500); $arr = array (); @@ -759,6 +825,7 @@ class dblayer extends PDO /** Drop the table */ public function dropTable () { + if ($this->debug) echo "== Entering dropTables\n"; if ($this->db === null) throw new Exception (dgettext("domframework", "Database not connected")); $sql = "DROP TABLE $this->sep$this->tableprefix$this->table$this->sep"; @@ -785,6 +852,7 @@ class dblayer extends PDO */ public function createTable () { + if ($this->debug) echo "== Entering createTable\n"; if ($this->db === null) throw new Exception (dgettext("domframework", "Database not connected"), 500); @@ -1089,6 +1157,7 @@ class dblayer extends PDO Return the an array with the datas */ public function directRead ($sql) { + if ($this->debug) echo "== Entering directRead\n"; $st = $this->db->prepare ($sql); $st->execute (); $res = array ();