From 5c045e58dccde8784ef79d9293e3364d041f817c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 15 Jan 2008 03:43:03 +0000 Subject: [PATCH] * added DibiTable * new connection options: "result:objects" & "result:withtables" * renamed DibiDriverInterface -> IDibiDriver, DibiVariableInterface -> IDibiVariable --- TODO.txt | 5 +- dibi/dibi.php | 3 +- dibi/drivers/mssql.php | 2 +- dibi/drivers/mysql.php | 2 +- dibi/drivers/mysqli.php | 4 +- dibi/drivers/odbc.php | 2 +- dibi/drivers/oracle.php | 2 +- dibi/drivers/pdo.php | 2 +- dibi/drivers/postgre.php | 2 +- dibi/drivers/sqlite.php | 2 +- dibi/libs/DibiConnection.php | 4 +- dibi/libs/DibiResult.php | 45 +++- dibi/libs/DibiResultIterator.php | 2 +- dibi/libs/DibiTable.php | 248 ++++++++++++++++++ dibi/libs/DibiTranslator.php | 8 +- dibi/libs/DibiVariable.php | 22 +- dibi/libs/NObject.php | 52 +++- ...DibiDriverInterface.php => interfaces.php} | 22 +- examples/connect.php | 1 + examples/datetime.demo.php | 2 +- examples/dibi.table.php | 87 ++++++ 21 files changed, 461 insertions(+), 58 deletions(-) create mode 100644 dibi/libs/DibiTable.php rename dibi/libs/{DibiDriverInterface.php => interfaces.php} (87%) create mode 100644 examples/dibi.table.php diff --git a/TODO.txt b/TODO.txt index 8e889c4c..2283f5f6 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,6 +1,5 @@ -- dibi::insertOrUpdate... -- sjednotit DibiVariable, modifier a type u DibiDriverInterface::format() -- odstranit podporu pro modifikátory v kličích pole? +- sjednotit DibiVariable, modifier a type u IDibiDriver::format() +- odstranit podporu pro modifikátory v klíčích pole? - odstranit podporu pro conditional query? - odstranit podporu pro meta types - odstraneno - event, log, profiler diff --git a/dibi/dibi.php b/dibi/dibi.php index 18c3ec20..b520584a 100644 --- a/dibi/dibi.php +++ b/dibi/dibi.php @@ -35,14 +35,15 @@ if (!class_exists('NObject', FALSE)) { require_once dirname(__FILE__) . '/libs/N if (!class_exists('NException', FALSE)) { require_once dirname(__FILE__) . '/libs/NException.php'; } // dibi libraries +require_once dirname(__FILE__) . '/libs/interfaces.php'; require_once dirname(__FILE__) . '/libs/DibiException.php'; require_once dirname(__FILE__) . '/libs/DibiConnection.php'; -require_once dirname(__FILE__) . '/libs/DibiDriverInterface.php'; require_once dirname(__FILE__) . '/libs/DibiResult.php'; require_once dirname(__FILE__) . '/libs/DibiResultIterator.php'; require_once dirname(__FILE__) . '/libs/DibiTranslator.php'; require_once dirname(__FILE__) . '/libs/DibiLogger.php'; require_once dirname(__FILE__) . '/libs/DibiVariable.php'; +require_once dirname(__FILE__) . '/libs/DibiTable.php'; diff --git a/dibi/drivers/mssql.php b/dibi/drivers/mssql.php index cff82e3a..e9d3ebc1 100644 --- a/dibi/drivers/mssql.php +++ b/dibi/drivers/mssql.php @@ -34,7 +34,7 @@ * @package dibi * @version $Revision$ $Date$ */ -class DibiMsSqlDriver extends NObject implements DibiDriverInterface +class DibiMsSqlDriver extends NObject implements IDibiDriver { /** diff --git a/dibi/drivers/mysql.php b/dibi/drivers/mysql.php index 04552778..253be23a 100644 --- a/dibi/drivers/mysql.php +++ b/dibi/drivers/mysql.php @@ -40,7 +40,7 @@ * @package dibi * @version $Revision$ $Date$ */ -class DibiMySqlDriver extends NObject implements DibiDriverInterface +class DibiMySqlDriver extends NObject implements IDibiDriver { /** diff --git a/dibi/drivers/mysqli.php b/dibi/drivers/mysqli.php index ab036258..021f78a1 100644 --- a/dibi/drivers/mysqli.php +++ b/dibi/drivers/mysqli.php @@ -40,7 +40,7 @@ * @package dibi * @version $Revision$ $Date$ */ -class DibiMySqliDriver extends NObject implements DibiDriverInterface +class DibiMySqliDriver extends NObject implements IDibiDriver { /** @@ -92,7 +92,7 @@ class DibiMySqliDriver extends NObject implements DibiDriverInterface // default values if (!isset($config['username'])) $config['username'] = ini_get('mysqli.default_user'); - if (!isset($config['password'])) $config['password'] = ini_get('mysqli.default_password'); + if (!isset($config['password'])) $config['password'] = ini_get('mysqli.default_pw'); if (!isset($config['socket'])) $config['socket'] = ini_get('mysqli.default_socket'); if (!isset($config['host'])) { $config['host'] = ini_get('mysqli.default_host'); diff --git a/dibi/drivers/odbc.php b/dibi/drivers/odbc.php index 543cb7d6..406ff476 100644 --- a/dibi/drivers/odbc.php +++ b/dibi/drivers/odbc.php @@ -33,7 +33,7 @@ * @package dibi * @version $Revision$ $Date$ */ -class DibiOdbcDriver extends NObject implements DibiDriverInterface +class DibiOdbcDriver extends NObject implements IDibiDriver { /** diff --git a/dibi/drivers/oracle.php b/dibi/drivers/oracle.php index 7d306512..df8be526 100644 --- a/dibi/drivers/oracle.php +++ b/dibi/drivers/oracle.php @@ -33,7 +33,7 @@ * @package dibi * @version $Revision$ $Date$ */ -class DibiOracleDriver extends NObject implements DibiDriverInterface +class DibiOracleDriver extends NObject implements IDibiDriver { /** diff --git a/dibi/drivers/pdo.php b/dibi/drivers/pdo.php index fe5e2362..f9e9cbb3 100644 --- a/dibi/drivers/pdo.php +++ b/dibi/drivers/pdo.php @@ -33,7 +33,7 @@ * @package dibi * @version $Revision$ $Date$ */ -class DibiPdoDriver extends NObject implements DibiDriverInterface +class DibiPdoDriver extends NObject implements IDibiDriver { /** diff --git a/dibi/drivers/postgre.php b/dibi/drivers/postgre.php index 5b0f87f7..41886bf6 100644 --- a/dibi/drivers/postgre.php +++ b/dibi/drivers/postgre.php @@ -33,7 +33,7 @@ * @package dibi * @version $Revision$ $Date$ */ -class DibiPostgreDriver extends NObject implements DibiDriverInterface +class DibiPostgreDriver extends NObject implements IDibiDriver { /** diff --git a/dibi/drivers/sqlite.php b/dibi/drivers/sqlite.php index c79d92c0..74124279 100644 --- a/dibi/drivers/sqlite.php +++ b/dibi/drivers/sqlite.php @@ -34,7 +34,7 @@ * @package dibi * @version $Revision$ $Date$ */ -class DibiSqliteDriver extends NObject implements DibiDriverInterface +class DibiSqliteDriver extends NObject implements IDibiDriver { /** diff --git a/dibi/libs/DibiConnection.php b/dibi/libs/DibiConnection.php index 8b2640a7..e856b88a 100644 --- a/dibi/libs/DibiConnection.php +++ b/dibi/libs/DibiConnection.php @@ -36,7 +36,7 @@ class DibiConnection extends NObject private $config; /** - * DibiDriverInterface + * IDibiDriver * @var array */ private $driver; @@ -254,7 +254,7 @@ class DibiConnection extends NObject $time = -microtime(TRUE); dibi::notify($this, 'beforeQuery', $sql); - $res = $this->driver->query($sql) ? new DibiResult(clone $this->driver) : TRUE; // backward compatibility - will be changed to NULL + $res = $this->driver->query($sql) ? new DibiResult(clone $this->driver, $this->config) : TRUE; // backward compatibility - will be changed to NULL $time += microtime(TRUE); dibi::$elapsedTime = $time; diff --git a/dibi/libs/DibiResult.php b/dibi/libs/DibiResult.php index 9bb2c770..5bc344de 100644 --- a/dibi/libs/DibiResult.php +++ b/dibi/libs/DibiResult.php @@ -26,6 +26,7 @@ * $result = dibi::query('SELECT * FROM [table]'); * * $row = $result->fetch(); + * $obj = $result->fetch(TRUE); * $value = $result->fetchSingle(); * $table = $result->fetchAll(); * $pairs = $result->fetchPairs(); @@ -43,7 +44,7 @@ class DibiResult extends NObject implements IteratorAggregate, Countable { /** - * DibiDriverInterface + * IDibiDriver * @var array */ private $driver; @@ -60,7 +61,6 @@ class DibiResult extends NObject implements IteratorAggregate, Countable */ private $metaCache; - /** * Already fetched? Used for allowance for first seek(0) * @var bool @@ -73,6 +73,12 @@ class DibiResult extends NObject implements IteratorAggregate, Countable */ private $withTables = FALSE; + /** + * Fetch as object? + * @var bool + */ + public $asObjects = FALSE; + private static $types = array( @@ -85,13 +91,19 @@ class DibiResult extends NObject implements IteratorAggregate, Countable - - public function __construct($driver) + /** + * @param IDibiDriver + * @param array + */ + public function __construct($driver, $config) { $this->driver = $driver; + $this->setWithTables(!empty($config['result:withtables'])); + $this->asObjects = !empty($config['result:objects']); } + /** * Automatically frees the resources allocated for this result set * @@ -207,9 +219,10 @@ class DibiResult extends NObject implements IteratorAggregate, Countable * Fetches the row at current position, process optional type conversion * and moves the internal cursor to the next position * + * @param bool fetch as object? Overrides $this->asObjects * @return array|FALSE array on success, FALSE if no next record */ - final public function fetch() + final public function fetch($asObject = NULL) { if ($this->withTables === FALSE) { $row = $this->getDriver()->fetch(TRUE); @@ -232,6 +245,10 @@ class DibiResult extends NObject implements IteratorAggregate, Countable } } + if ($asObject || ($asObject === NULL && $this->asObjects)) { + $row = (object) $row; + } + return $row; } @@ -268,7 +285,7 @@ class DibiResult extends NObject implements IteratorAggregate, Countable final function fetchAll() { $this->seek(0); - $row = $this->fetch(); + $row = $this->fetch(FALSE); if (!$row) return array(); // empty resultset $data = array(); @@ -276,10 +293,10 @@ class DibiResult extends NObject implements IteratorAggregate, Countable $key = key($row); do { $data[] = $row[$key]; - } while ($row = $this->fetch()); + } while ($row = $this->fetch(FALSE)); } else { - + if ($this->asObjects) $row = (object) $row; do { $data[] = $row; } while ($row = $this->fetch()); @@ -302,7 +319,7 @@ class DibiResult extends NObject implements IteratorAggregate, Countable final function fetchAssoc($assoc) { $this->seek(0); - $row = $this->fetch(); + $row = $this->fetch(FALSE); if (!$row) return array(); // empty resultset $data = NULL; @@ -366,7 +383,7 @@ class DibiResult extends NObject implements IteratorAggregate, Countable if ($leaf === '=') $x = $row; else $x = (object) $row; } - } while ($row = $this->fetch()); + } while ($row = $this->fetch(FALSE)); unset($x); return $data; @@ -385,7 +402,7 @@ class DibiResult extends NObject implements IteratorAggregate, Countable final function fetchPairs($key = NULL, $value = NULL) { $this->seek(0); - $row = $this->fetch(); + $row = $this->fetch(FALSE); if (!$row) return array(); // empty resultset $data = array(); @@ -412,7 +429,7 @@ class DibiResult extends NObject implements IteratorAggregate, Countable if ($key === NULL) { // indexed-array do { $data[] = $row[$value]; - } while ($row = $this->fetch()); + } while ($row = $this->fetch(FALSE)); return $data; } @@ -423,7 +440,7 @@ class DibiResult extends NObject implements IteratorAggregate, Countable do { $data[ $row[$key] ] = $row[$value]; - } while ($row = $this->fetch()); + } while ($row = $this->fetch(FALSE)); return $data; } @@ -558,7 +575,7 @@ class DibiResult extends NObject implements IteratorAggregate, Countable /** * Safe access to property $driver * - * @return DibiDriverInterface + * @return IDibiDriver * @throws DibiException */ private function getDriver() diff --git a/dibi/libs/DibiResultIterator.php b/dibi/libs/DibiResultIterator.php index 9965f018..235298a1 100644 --- a/dibi/libs/DibiResultIterator.php +++ b/dibi/libs/DibiResultIterator.php @@ -129,7 +129,7 @@ final class DibiResultIterator implements Iterator */ public function valid() { - return is_array($this->row) && ($this->limit < 0 || $this->pointer < $this->limit); + return !empty($this->row) && ($this->limit < 0 || $this->pointer < $this->limit); } diff --git a/dibi/libs/DibiTable.php b/dibi/libs/DibiTable.php new file mode 100644 index 00000000..999dac7f --- /dev/null +++ b/dibi/libs/DibiTable.php @@ -0,0 +1,248 @@ +options = $options; + + $this->setup(); + + if ($this->connection === NULL) { + $this->connection = dibi::getConnection(); + } + } + + + + /** + * Returns the table name + * @return string + */ + public function getName() + { + return $this->name; + } + + + + /** + * Returns the primary key name + * @return string + */ + public function getPrimary() + { + return $this->primary; + } + + + + /** + * Returns the dibi connection + * @return DibiConnection + */ + public function getConnection() + { + return $this->connection; + } + + + + /** + * Setup object + * @return void + */ + protected function setup() + { + // autodetect table name + if ($this->name === NULL) { + $name = $this->getClass(); + if (FALSE !== ($pos = strrpos($name, ':'))) { + $name = substr($name, $pos + 1); + } + if (self::$lowerCase) { + $name = strtolower($name); + } + $this->name = $name; + } + + // autodetect primary key name + if ($this->primary === NULL) { + $this->primary = str_replace( + array('%p', '%s'), + array($this->name, trim($this->name, 's')), // the simplest inflector in the world :-)) + self::$primaryMask + ); + } + } + + + + /** + * Inserts row into a table + * @param array|object + * @return int new primary key + */ + public function insert($data) + { + if (is_object($data)) { + $data = (array) $data; + } elseif (!is_array($data)) { + throw new DibiException('Dataset must be array or anonymous object'); + } + + $this->connection->query( + 'INSERT INTO %n', $this->name, '%v', $data + ); + return $this->connection->insertId(); + } + + + + /** + * Updates rows in a table + * @param mixed primary key value(s) + * @param array|object + * @return int number of updated rows + */ + public function update($where, $data) + { + if (is_object($data)) { + $data = (array) $data; + } elseif (!is_array($data)) { + throw new DibiException('Dataset must be array or anonymous object'); + } + + $this->connection->query( + 'UPDATE %n', $this->name, + 'SET %a', $data, + 'WHERE %n', $this->primary, 'IN (' . $this->primaryModifier, $where, ')' + ); + return $this->connection->affectedRows(); + } + + + + /** + * Deletes rows from a table by primary key + * @param mixed primary key value(s) + * @return int number of deleted rows + */ + public function delete($where) + { + $this->connection->query( + 'DELETE FROM %n', $this->name, + 'WHERE %n', $this->primary, 'IN (' . $this->primaryModifier, $where, ')' + ); + return $this->connection->affectedRows(); + } + + + + /** + * Finds rows by primary key + * @param mixed primary key value(s) + * @return DibiResult + */ + public function find($what) + { + if (!is_array($what)) { + $what = func_get_args(); + } + return $this->connection->query( + 'SELECT * FROM %n', $this->name, + 'WHERE %n', $this->primary, 'IN (' . $this->primaryModifier, $what, ')' + ); + } + + + + /** + * Selects all rows + * @param string column to order by + * @return DibiResult + */ + public function findAll($order = NULL) + { + if ($order === NULL) { + return $this->connection->query( + 'SELECT * FROM %n', $this->name + ); + } else { + $order = func_get_args(); + return $this->connection->query( + 'SELECT * FROM %n', $this->name, + 'ORDER BY %n', $order + ); + } + } + + + + /** + * Fetches single row + * @param scalar primary key value + * @return array row + */ + public function fetch($what) + { + $this->connection->query( + 'SELECT * FROM %n', $this->name, + 'WHERE %n', $this->primary, '=' . $this->primaryModifier, $what + )->fetch(); + } + +} diff --git a/dibi/libs/DibiTranslator.php b/dibi/libs/DibiTranslator.php index 7b6b4127..5a6942d0 100644 --- a/dibi/libs/DibiTranslator.php +++ b/dibi/libs/DibiTranslator.php @@ -35,7 +35,7 @@ final class DibiTranslator extends NObject /** @var string NOT USED YET */ public $mask; - /** @var DibiDriverInterface */ + /** @var IDibiDriver */ private $driver; /** @var string last modifier */ @@ -55,7 +55,7 @@ final class DibiTranslator extends NObject - public function __construct(DibiDriverInterface $driver) + public function __construct(IDibiDriver $driver) { $this->driver = $driver; } @@ -207,7 +207,7 @@ final class DibiTranslator extends NObject return 'NULL'; } - if ($value instanceof DibiVariableInterface) { + if ($value instanceof IDibiVariable) { return $value->toSql($this->driver, $modifier); } @@ -311,7 +311,7 @@ final class DibiTranslator extends NObject if ($value === NULL) return 'NULL'; - if ($value instanceof DibiVariableInterface) + if ($value instanceof IDibiVariable) return $value->toSql($this->driver, NULL); $this->hasError = TRUE; diff --git a/dibi/libs/DibiVariable.php b/dibi/libs/DibiVariable.php index 04b61466..468c8314 100644 --- a/dibi/libs/DibiVariable.php +++ b/dibi/libs/DibiVariable.php @@ -20,26 +20,10 @@ /** - * Interface for user variable, used for generating SQL + * Default implemenation of IDibiVariable * @package dibi */ -interface DibiVariableInterface -{ - /** - * Format for SQL - * - * @param object destination DibiDriverInterface - * @param string optional modifier - * @return string SQL code - */ - public function toSql(DibiDriverInterface $driver, $modifier); -} - - - - - -class DibiVariable extends NObject implements DibiVariableInterface +class DibiVariable extends NObject implements IDibiVariable { /** @var mixed */ public $value; @@ -55,7 +39,7 @@ class DibiVariable extends NObject implements DibiVariableInterface } - public function toSql(DibiDriverInterface $driver, $modifier) + public function toSql(IDibiDriver $driver, $modifier) { return $driver->format($this->value, $this->type); } diff --git a/dibi/libs/NObject.php b/dibi/libs/NObject.php index 64fff0fa..0b308c03 100644 --- a/dibi/libs/NObject.php +++ b/dibi/libs/NObject.php @@ -25,8 +25,9 @@ * * It defines some handful methods and enhances object core of PHP: * - access to undeclared members throws exceptions - * - ability to add new methods to class (extension methods) * - support for conventional properties with getters and setters + * - support for event raising functionality + * - ability to add new methods to class (extension methods) * * Properties is a syntactic sugar which allows access public getter and setter * methods as normal object variables. A property is defined by a getter method @@ -38,6 +39,15 @@ * Property names are case-sensitive, and they are written in the camelCaps * or PascalCaps. * + * Event functionality is provided by declaration using pseudo-keyword 'event'. + * Multiple handlers are allowed. + * + * public $onClick = event; // declaration in class + * $this->onClick[] = 'callback'; // attaching event handler + * if (empty($this->onClick)) ... // are there any handler? + * $this->onClick($sender, $arg); // raises the event with arguments + * + * * Adding method to class (i.e. to all instances) works similar to JavaScript * prototype property. The syntax for adding a new method is: * @@ -93,9 +103,23 @@ abstract class NObject throw new BadMethodCallException("Call to method without name"); } + $class = get_class($this); + + // event functionality + if (self::hasEvent($class, $name)) { + $list = $this->$name; + if (is_array($list) || $list instanceof Traversable) { + foreach ($list as $handler) { + if ($handler === '') continue; + call_user_func_array($handler, $args); + } + } + return; + } + // object prototypes support Class__method() // (or use class Class__method { static function ... } with autoloading?) - $cl = $class = get_class($this); + $cl = $class; do { if (function_exists($nm = $cl . '_prototype_' . $name)) { array_unshift($args, $this); @@ -199,7 +223,7 @@ abstract class NObject /** - * Has property accessor? + * Has property an accessor? * * @param string class name * @param string method name @@ -221,4 +245,26 @@ abstract class NObject return isset($cache[$c][$m]); } + + + /** + * Is property an event? + * + * @param string class name + * @param string method name + * @return bool + */ + private static function hasEvent($c, $m) + { + if (strncmp($m, 'on', 2)) return FALSE; + + static $cache; + if (!isset($cache[$c])) { + // get_class_vars returns ONLY PUBLIC properties + // but returns static methods too (nothing doing...) + $cache[$c] = get_class_vars($c); + } + return isset($cache[$c][$m]) && $cache[$c][$m] === ''; + } + } diff --git a/dibi/libs/DibiDriverInterface.php b/dibi/libs/interfaces.php similarity index 87% rename from dibi/libs/DibiDriverInterface.php rename to dibi/libs/interfaces.php index e419c568..a41504f4 100644 --- a/dibi/libs/DibiDriverInterface.php +++ b/dibi/libs/interfaces.php @@ -19,6 +19,26 @@ +/** + * Interface for user variable, used for generating SQL + * @package dibi + */ +interface IDibiVariable +{ + /** + * Format for SQL + * + * @param object destination IDibiDriver + * @param string optional modifier + * @return string SQL code + */ + public function toSql(IDibiDriver $driver, $modifier); +} + + + + + /** * dibi driver interface * @@ -27,7 +47,7 @@ * @package dibi * @version $Revision$ $Date$ */ -interface DibiDriverInterface +interface IDibiDriver { /** diff --git a/examples/connect.php b/examples/connect.php index efd2a329..3b758bcc 100644 --- a/examples/connect.php +++ b/examples/connect.php @@ -10,6 +10,7 @@ try { dibi::connect(array( 'driver' => 'sqlite', 'database' => 'sample.sdb', + 'result:objects' => TRUE, // fetch rows as objects )); echo 'OK'; diff --git a/examples/datetime.demo.php b/examples/datetime.demo.php index 592f4823..9adaf583 100644 --- a/examples/datetime.demo.php +++ b/examples/datetime.demo.php @@ -1,4 +1,4 @@ -

DibiVariableInterface example

+

IDibiVariable example

DibiTable demo +
+ 'sqlite',
+    'database' => 'sample_tmp.sdb',
+));
+
+
+
+
+// table products
+class Products extends DibiTable
+{
+//   rely on autodetection...
+//   protected $name = 'products';
+//   protected $primary = 'product_id';
+
+}
+
+// autodetection: primary keys are customer_id, order_id, ...
+DibiTable::$primaryMask = '%s_id';
+
+
+
+// create table object
+$products = new Products();
+
+echo "Table name: $products->name\n";
+echo "Primary key: $products->primary\n";
+
+
+// Finds rows by primary key
+foreach ($products->find(1, 3) as $row) {
+    ...
+}
+
+
+// select all
+$products->findAll()->dump();
+
+
+// select all, order by title, product_id
+$products->findAll('title', $products->primary)->dump();
+
+
+// fetches single row with id 3
+$row = $products->fetch(3);
+
+
+// deletes row from a table
+$count = $products->delete(1);
+
+// deletes multiple rows
+$count = $products->delete(array(1, 2, 3));
+var_dump($count); // number of deleted rows
+
+
+// update row #2 in a table
+$data = (object) NULL;
+$data->title = 'New title';
+$count = $products->update(2, $data);
+var_dump($count); // number of updated rows
+
+
+// update multiple rows in a table
+$count = $products->update(array(3, 5), $data);
+var_dump($count); // number of updated rows
+
+
+// inserts row into a table
+$data = array();
+$data['title'] = 'New product';
+$id = $products->insert($data);
+var_dump($id); // generated id
+
+
+// is absolutely SQL injection safe
+$key = '3 OR 1=1';
+$products->delete($key);
+// --> DELETE FROM  [products] WHERE  [product_id] IN ( 3 )