From 3c5a5d9603d4e81e6873f10469fa64f1d914b436 Mon Sep 17 00:00:00 2001 From: Anton Grebnev Date: Fri, 11 Nov 2011 18:01:17 +0400 Subject: [PATCH] rebuild Model package for NoSQL drivers --- .gitignore | 1 + model/DbDriver.php | 177 +----------------- model/Model.php | 197 +++++---------------- model/MongoDriver.php | 77 ++++++++ model/MySQLiDriver.php | 2 +- model/NoSqlDbDriver.php | 13 ++ model/SqlDbDriver.php | 185 +++++++++++++++++++ model/SqlModel.php | 173 ++++++++++++++++++ model_class_diadram.png | Bin 0 -> 43038 bytes tests/model/MongoDriverTest.php | 65 +++++++ tests/model/MySQLiDriverTest.php | 1 + .../{DbDriverTest.php => SqlDbDriverTest.php} | 9 +- tests/model/{ModelTest.php => SqlModelTest.php} | 23 +-- uml_model.zargo | Bin 0 -> 12190 bytes 14 files changed, 589 insertions(+), 334 deletions(-) create mode 100644 model/MongoDriver.php create mode 100644 model/NoSqlDbDriver.php create mode 100644 model/SqlDbDriver.php create mode 100644 model/SqlModel.php create mode 100644 model_class_diadram.png create mode 100644 tests/model/MongoDriverTest.php rename tests/model/{DbDriverTest.php => SqlDbDriverTest.php} (93%) rename tests/model/{ModelTest.php => SqlModelTest.php} (92%) create mode 100644 uml_model.zargo diff --git a/.gitignore b/.gitignore index f6a98c3..24bad3a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.project /.cache /tests/report +*~ diff --git a/model/DbDriver.php b/model/DbDriver.php index 444d2af..b5f2efd 100644 --- a/model/DbDriver.php +++ b/model/DbDriver.php @@ -12,8 +12,6 @@ abstract class DbDriver { - protected $identifier_quote = '`'; - /** * Database connection * @@ -44,173 +42,20 @@ abstract class DbDriver } } } - + public function getConnection() { $this->connect(); return $this->connection; } - - public function beginTransaction() - { - $this->connect(); - $this->driverBeginTransaction(); - return $this; - } - - public function commit() - { - $this->connect(); - $this->driverCommitTransaction(); - return $this; - } - - public function rollback() - { - $this->connect(); - $this->driverRollbackTransaction(); - return $this; - } - - /** - * @param string $sql - * @param mixed $params - * @return DbStatement - */ - public function query($sql, $params = array()) - { - $this->connect(); - if (!is_array($params)) { - $params = array($params); - } - $stmt = $this->prepare($sql); - $stmt->execute($params); - return $stmt; - } - - /** - * @param string $table - * @param mixed $bind - * @param mixed $on_duplicate - * @return int Affected rows count - */ - public function insert($table, $bind, $on_duplicate = array()) - { - $columns = array(); - foreach ($bind as $col => $val) { - $columns[] = $this->quoteIdentifier($col); - } - $values = array_values($bind); - - $sql = 'INSERT INTO ' . $this->quoteIdentifier($table) - . ' (' . implode(', ', $columns) . ') VALUES (' . $this->quote($values) . ')'; - return $this->query($sql)->affectedRows(); - } - - /** - * @param string $table - * @param array $bind - * @param mixed $where - * @return int - */ - public function update($table, $bind, $where = '') - { - $set = array(); - foreach ($bind as $col => $val) { - $set[] = $this->quoteIdentifier($col) . '=' . $this->quote($val); - } - $where = $this->whereExpr($where); - $sql = 'UPDATE ' . $this->quoteIdentifier($table) . ' SET ' . implode(', ', $set) - . (($where) ? (' WHERE ' . $where) : ''); - return $this->query($sql)->affectedRows(); - } - - /** - * @param string $table - * @param mixed $where - * @return int - */ - public function delete($table, $where = '') - { - $where = $this->whereExpr($where); - $sql = 'DELETE FROM ' . $this->quoteIdentifier($table) . (($where) ? (' WHERE ' . $where) : ''); - return $this->query($sql)->affectedRows(); - } - - /** - * @param mixed $value - * @return string - */ - public function quote($value) - { - if ($value instanceof DbExpr) { - return (string) $value; - } - if (is_array($value)) { - foreach ($value as &$val) { - $val = $this->quote($val); - } - return implode(', ', $value); - } - return $this->driverQuote($value); - } - - /** - * @param string $ident - * @return string - */ - public function quoteIdentifier($ident) - { - $ident = explode('.', $ident); - if (!is_array($ident)) { - $ident = array($ident); - } - foreach ($ident as &$segment) { - $segment = $this->identifier_quote . $segment . $this->identifier_quote; - } - return implode('.', $ident); - } - - public function quoteInto($text, $value) - { - $pos = mb_strpos($text, '?'); - if ($pos === false) { - return $text; - } - return mb_substr($text, 0, $pos) . $this->quote($value) . mb_substr($text, $pos + 1); - } - - /** - * @param mixed $where - * @return string - */ - protected function whereExpr($where) - { - if (empty($where)) { - return $where; - } - - if (!is_array($where)) { - $where = array($where); - } - foreach ($where as $cond => &$term) { - if (is_int($cond)) { - if ($term instanceof DbExpr) { - $term = (string) $term; - } - } else { - $term = $this->quoteInto($cond, $term); - } - } - return implode(' AND ', $where); - } - + /* Abstract methods */ + + abstract public function insert($table, $data); + + abstract public function update($table, $data, $condition); - /** - * @return DbStatement - */ - abstract public function prepare($sql); + abstract public function delete($table, $condition); abstract public function getInsertId($table = null, $key = null); @@ -219,12 +64,4 @@ abstract class DbDriver abstract public function disconnect(); abstract protected function connect(); - - abstract protected function driverQuote($value); - - abstract protected function driverBeginTransaction(); - - abstract protected function driverCommitTransaction(); - - abstract protected function driverRollbackTransaction(); } \ No newline at end of file diff --git a/model/Model.php b/model/Model.php index 53278c5..10e97f8 100644 --- a/model/Model.php +++ b/model/Model.php @@ -62,41 +62,13 @@ abstract class Model } /** - * @param string $ident - * @return string Quoted identifier. - */ - public function identify($ident) - { - return $this->db->quoteIdentifier($ident); - } - - /** - * @param mixed $value - * @return string Quoted value. - */ - public function quote($value) - { - return $this->db->quote($value); - } - - /** - * @param int $id - * @return object - */ - public function get($id) - { - $sql = 'SELECT * FROM :table WHERE :pk=?'; - return $this->fetch($sql, $id); - } - - /** * @param array $data * @param array $on_duplicate * @return int Id of inserted row */ - public function insert($data, $on_duplicate = array()) + public function insert($data) { - $affected = $this->db->insert($this->table(), $data, $on_duplicate); + $affected = $this->db->insert($this->table(), $data); return ($this->getInsertId()) ? $this->getInsertId() : $affected; } @@ -107,21 +79,8 @@ abstract class Model */ public function update($data, $where) { - if (is_int($where) || $where === (string) (int) $where) { - $where = $this->identify($this->key) . '=' . (int) $where; - } return $this->db->update($this->table(), $data, $where); } - - /** - * @param int $id Int id - * @return int Number of affected rows - */ - public function delete($id) - { - $where = $this->identify($this->key) . '=' . (int) $id; - return $this->db->delete($this->table(), $where); - } /** * @return string @@ -133,112 +92,7 @@ abstract class Model } return $this->table; } - - /** - * Creates order sql string - * - * @param array $params - * @param array $sortable - * @return string - */ - protected function order($params, $sortable = array('id')) - { - $sql = ''; - if (isset($params['sort'])) { - $order = (isset($params['order']) && $params['order'] == 'desc') ? 'DESC' : 'ASC'; - if (in_array($params['sort'], $sortable)) { - $sql = ' ORDER BY ' . $this->identify($params['sort']) . ' ' . $order; - } - } - return $sql; - } - - /** - * Searches using like - * - * @param array $params - * @param array $searchable - * @param string $table_prefix - * @return string - */ - protected function search($params, $searchable = array('id'), $table_prefix = '') - { - $sql = ''; - if (isset($params['q']) && isset($params['qt']) && in_array($params['qt'], $searchable)) { - if ($table_prefix) { - $sql = $table_prefix . '.'; - } - $sql .= $this->identify($params['qt']) . ' LIKE ' . $this->quote('%' . $params['q'] . '%'); - } - return $sql; - } - /** - * This method appends to params table and primary key. - * So they can be accessed thru `:table` and `:pk` placeholders. - * - * @return DbStatement - */ - protected function query($sql, $params = array()) - { - if (!is_array($params)) { - $params = array($params); - } - $params = array( - 'table' => new DbExpr($this->identify($this->table())), - 'pk' => new DbExpr($this->identify($this->key)), - ) + $params; - return $this->db->query($sql, $params); - } - - /** - * @param string $sql - * @param array $params - * @param string $field - * @param CacheKey $cache_key - */ - protected function fetchField($sql, $params = array(), $field, $cache_key = null) - { - if (!$cache_key || !$result = $cache_key->get()) { - $result = $this->query($sql, $params)->fetchField($field); - if ($cache_key) { - $cache_key->set($result); - } - } - return $result; - } - - /** - * @param string $sql - * @param array $params - * @param CacheKey $cache_key - */ - protected function fetch($sql, $params = array(), $cache_key = null) - { - if (!$cache_key || !$result = $cache_key->get()) { - $result = $this->query($sql, $params)->fetch(); - if ($cache_key) { - $cache_key->set($result); - } - } - return $result; - } - - /** - * @param string $sql - * @param array $params - * @param CacheKey $cache_key - */ - protected function fetchAll($sql, $params = array(), $cache_key = null) - { - if (!$cache_key || !$result = $cache_key->get()) { - $result = $this->query($sql, $params)->fetchAll(); - if ($cache_key) { - $cache_key->set($result); - } - } - return $result; - } /* Cache workaround */ @@ -280,4 +134,51 @@ abstract class Model } $this->caches_clean = array(); } + + /** + * Abstract methods + */ + + /** + * @param int $id + * @return object + */ + abstract public function get($id); + + /** + * @param int $id Int id + * @return int Number of affected rows + */ + abstract public function delete($id); + + /** + * Creates order sql string + * + * @param array $params + * @param array $sortable + * @return string + */ + abstract protected function order($params, $sortable = array('id')); + + /** + * @param string $sql + * @param array $params + * @param string $field + * @param CacheKey $cache_key + */ + abstract protected function fetchField($data, $params = array(), $field, $cache_key = null); + + /** + * @param string $sql + * @param array $params + * @param CacheKey $cache_key + */ + abstract protected function fetch($data, $params = array(), $cache_key = null); + + /** + * @param string $sql + * @param array $params + * @param CacheKey $cache_key + */ + abstract protected function fetchAll($data, $params = array(), $cache_key = null); } \ No newline at end of file diff --git a/model/MongoDriver.php b/model/MongoDriver.php new file mode 100644 index 0000000..c1d48e0 --- /dev/null +++ b/model/MongoDriver.php @@ -0,0 +1,77 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage db + * @since 2011-11-10 + */ + +/** + * @property Mongo $connection + */ +class MongoDriver extends NoSqlDbDriver +{ + + public function find($data, $params = array(), $cache_key = null) + { + + } + + public function insert($table, $bind, $on_duplicate = array()) + { + return parent::insert($table, $bind, $on_duplicate); + } + + public function update($table, $bind, $where = '') + { + return parent::update($table, $bind, $where); + } + + public function delete($table, $where = '') + { + return parent::delete($table, $where); + } + + + public function getInsertId($table = null, $key = null) + { + // TODO: Implement getInsertId() method. + } + + public function isConnected() + { + if (!is_null($this->connection)) { + return $this->connection->connected; + } else { + return false; + } + } + + public function disconnect() + { + if ($this->isConnected()) { + $this->connection->close(); + } + $this->connection = null; + } + + protected function connect() + { + if ($this->connection) { + return; + } + + $host = $this->config['hostname']; + $port = isset($this->config['port']) ? ':' . (string) $this->config['port'] : ''; + + $this->config = array( + 'username' => $this->config['username'], + 'password' => $this->config['password'], + 'db' => $this->config['database'] + ); + + $this->connection = new Mongo('mongodb://' . $host . $port, $this->config); + } +} + diff --git a/model/MySQLiDriver.php b/model/MySQLiDriver.php index 4afa22c..2cae3f1 100644 --- a/model/MySQLiDriver.php +++ b/model/MySQLiDriver.php @@ -12,7 +12,7 @@ /** * @property MySQLi $connection */ -class MySQLiDriver extends DbDriver +class MySQLiDriver extends SqlDbDriver { public function insert($table, $bind, $on_duplicate = array()) diff --git a/model/NoSqlDbDriver.php b/model/NoSqlDbDriver.php new file mode 100644 index 0000000..66a1f8f --- /dev/null +++ b/model/NoSqlDbDriver.php @@ -0,0 +1,13 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage db + * @since 2011-11-11 + */ + +abstract class NoSqlDbDriver extends DbDriver +{ + abstract function find($data, $params = array(), $cache_key = null); +} \ No newline at end of file diff --git a/model/SqlDbDriver.php b/model/SqlDbDriver.php new file mode 100644 index 0000000..8d8b9af --- /dev/null +++ b/model/SqlDbDriver.php @@ -0,0 +1,185 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage db + * @since 2011-11-11 + */ + +abstract class SqlDbDriver extends DbDriver +{ + + protected $identifier_quote = '`'; + + + + public function beginTransaction() + { + $this->connect(); + $this->driverBeginTransaction(); + return $this; + } + + public function commit() + { + $this->connect(); + $this->driverCommitTransaction(); + return $this; + } + + public function rollback() + { + $this->connect(); + $this->driverRollbackTransaction(); + return $this; + } + + /** + * @param string $sql + * @param mixed $params + * @return DbStatement + */ + public function query($sql, $params = array()) + { + $this->connect(); + if (!is_array($params)) { + $params = array($params); + } + $stmt = $this->prepare($sql); + $stmt->execute($params); + return $stmt; + } + + /** + * @param string $table + * @param mixed $bind + * @param mixed $on_duplicate + * @return int Affected rows count + */ + public function insert($table, $data) + { + $columns = array(); + foreach ($data as $col => $val) { + $columns[] = $this->quoteIdentifier($col); + } + $values = array_values($data); + + $sql = 'INSERT INTO ' . $this->quoteIdentifier($table) + . ' (' . implode(', ', $columns) . ') VALUES (' . $this->quote($values) . ')'; + return $this->query($sql)->affectedRows(); + } + + /** + * @param string $table + * @param array $bind + * @param mixed $where + * @return int + */ + public function update($table, $data, $condition = '') + { + $set = array(); + foreach ($data as $col => $val) { + $set[] = $this->quoteIdentifier($col) . '=' . $this->quote($val); + } + $where = $this->whereExpr($condition); + $sql = 'UPDATE ' . $this->quoteIdentifier($table) . ' SET ' . implode(', ', $set) + . (($where) ? (' WHERE ' . $where) : ''); + return $this->query($sql)->affectedRows(); + } + + /** + * @param string $table + * @param mixed $where + * @return int + */ + public function delete($table, $condition = '') + { + $where = $this->whereExpr($condition); + $sql = 'DELETE FROM ' . $this->quoteIdentifier($table) . (($where) ? (' WHERE ' . $where) : ''); + return $this->query($sql)->affectedRows(); + } + + /** + * @param mixed $value + * @return string + */ + public function quote($value) + { + if ($value instanceof DbExpr) { + return (string) $value; + } + if (is_array($value)) { + foreach ($value as &$val) { + $val = $this->quote($val); + } + return implode(', ', $value); + } + return $this->driverQuote($value); + } + + /** + * @param string $ident + * @return string + */ + public function quoteIdentifier($ident) + { + $ident = explode('.', $ident); + if (!is_array($ident)) { + $ident = array($ident); + } + foreach ($ident as &$segment) { + $segment = $this->identifier_quote . $segment . $this->identifier_quote; + } + return implode('.', $ident); + } + + public function quoteInto($text, $value) + { + $pos = mb_strpos($text, '?'); + if ($pos === false) { + return $text; + } + return mb_substr($text, 0, $pos) . $this->quote($value) . mb_substr($text, $pos + 1); + } + + /** + * @param mixed $where + * @return string + */ + protected function whereExpr($where) + { + if (empty($where)) { + return $where; + } + + if (!is_array($where)) { + $where = array($where); + } + foreach ($where as $cond => &$term) { + if (is_int($cond)) { + if ($term instanceof DbExpr) { + $term = (string) $term; + } + } else { + $term = $this->quoteInto($cond, $term); + } + } + return implode(' AND ', $where); + } + + /** + * @return DbStatement + */ + abstract public function prepare($sql); + + abstract protected function driverQuote($value); + + abstract protected function driverBeginTransaction(); + + abstract protected function driverCommitTransaction(); + + abstract protected function driverRollbackTransaction(); + +} + \ No newline at end of file diff --git a/model/SqlModel.php b/model/SqlModel.php new file mode 100644 index 0000000..7724232 --- /dev/null +++ b/model/SqlModel.php @@ -0,0 +1,173 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage Model + * @since 2011-11-11 + */ + +abstract class SqlModel extends Model +{ + + /** + * @param string $ident + * @return string Quoted identifier. + */ + public function identify($ident) + { + return $this->db->quoteIdentifier($ident); + } + + /** + * @param mixed $value + * @return string Quoted value. + */ + public function quote($value) + { + return $this->db->quote($value); + } + + /** + * @param int $id + * @return object + */ + public function get($id) + { + $sql = 'SELECT * FROM :table WHERE :pk=?'; + return $this->fetch($sql, $id); + } + + /** + * @param array $data + * @param mixed $where + * @return int Number of affected rows + */ + public function update($data, $where) + { + if (is_int($where) || $where === (string) (int) $where) { + $where = $this->identify($this->key) . '=' . (int) $where; + } + return parent::update($data, $where); + } + + /** + * @param int $id Int id + * @return int Number of affected rows + */ + public function delete($id) + { + $where = $this->identify($this->key) . '=' . (int) $id; + return $this->db->delete($this->table(), $where); + } + + /** + * Creates order sql string + * + * @param array $params + * @param array $sortable + * @return string + */ + protected function order($params, $sortable = array('id')) + { + $sql = ''; + if (isset($params['sort'])) { + $order = (isset($params['order']) && $params['order'] == 'desc') ? 'DESC' : 'ASC'; + if (in_array($params['sort'], $sortable)) { + $sql = ' ORDER BY ' . $this->identify($params['sort']) . ' ' . $order; + } + } + return $sql; + } + + /** + * Searches using like + * + * @param array $params + * @param array $searchable + * @param string $table_prefix + * @return string + */ + protected function search($params, $searchable = array('id'), $table_prefix = '') + { + $sql = ''; + if (isset($params['q']) && isset($params['qt']) && in_array($params['qt'], $searchable)) { + if ($table_prefix) { + $sql = $table_prefix . '.'; + } + $sql .= $this->identify($params['qt']) . ' LIKE ' . $this->quote('%' . $params['q'] . '%'); + } + return $sql; + } + + /** + * This method appends to params table and primary key. + * So they can be accessed thru `:table` and `:pk` placeholders. + * + * @return DbStatement + */ + protected function query($sql, $params = array()) + { + if (!is_array($params)) { + $params = array($params); + } + $params = array( + 'table' => new DbExpr($this->identify($this->table())), + 'pk' => new DbExpr($this->identify($this->key)), + ) + $params; + return $this->db->query($sql, $params); + } + + /** + * @param string $sql + * @param array $params + * @param string $field + * @param CacheKey $cache_key + */ + protected function fetchField($data, $params = array(), $field, $cache_key = null) + { + if (!$cache_key || !$result = $cache_key->get()) { + $result = $this->query($data, $params)->fetchField($field); + if ($cache_key) { + $cache_key->set($result); + } + } + return $result; + } + + /** + * @param string $sql + * @param array $params + * @param CacheKey $cache_key + */ + protected function fetch($data, $params = array(), $cache_key = null) + { + if (!$cache_key || !$result = $cache_key->get()) { + $result = $this->query($data, $params)->fetch(); + if ($cache_key) { + $cache_key->set($result); + } + } + return $result; + } + + /** + * @param string $sql + * @param array $params + * @param CacheKey $cache_key + */ + protected function fetchAll($data, $params = array(), $cache_key = null) + { + if (!$cache_key || !$result = $cache_key->get()) { + $result = $this->query($data, $params)->fetchAll(); + if ($cache_key) { + $cache_key->set($result); + } + } + return $result; + } + +} \ No newline at end of file diff --git a/model_class_diadram.png b/model_class_diadram.png new file mode 100644 index 0000000000000000000000000000000000000000..1c74e42c7f5fd65f23669a5439c24fa0859374f4 GIT binary patch literal 43038 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

0vT;DA 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 qUk=i6&i@z>|8vy--xJc+5n8E!oiSH0jd!8YZg + * @link http://netmonsters.ru + * @package Majestic + * @subpackage UnitTests + * @since 2011-11-10 + * + * Unit tests for MongoDriver class + */ + +require_once dirname(__FILE__) . '/../../model/DbDriver.php'; +require_once dirname(__FILE__) . '/../../model/NoSqlDbDriver.php'; +require_once dirname(__FILE__) . '/../../model/MongoDriver.php'; + +class MongoDriverTest extends PHPUnit_Framework_TestCase +{ + + private $conf = array(); + + public function setUp() + { + $this->conf = array( + 'hostname' => 'localhost', + 'database' => 'test', + 'username' => 'test', + 'password' => '1234', + 'port' => 27017 + ); + } + /** + * @expectedException Exception + * @expectedExceptionMessage Configuration must have a "hostname". + */ + public function testGetConnectionNoHostname() + { + unset($this->conf['hostname']); + $mongo = new MongoDriver($this->conf); + $mongo->getConnection(); + } + + /** + * @expectedException MongoConnectionException + * @expectedExceptionMessage Couldn't authenticate with database + */ + public function testGetConnectionWrongPassword() + { + $this->conf['password'] = 'nopass'; + $mongo = new MongoDriver($this->conf); + $this->assertInstanceOf('Mongo', $mongo->getConnection()); + } + + public function testGetConnection() + { + $mongo = new MongoDriver($this->conf); + + $this->assertFalse($mongo->isConnected()); + $this->assertInstanceOf('Mongo', $mongo->getConnection()); + $this->assertTrue($mongo->isConnected()); + $mongo->disconnect(); + $this->assertFalse($mongo->isConnected()); + } + +} \ No newline at end of file diff --git a/tests/model/MySQLiDriverTest.php b/tests/model/MySQLiDriverTest.php index 156cdad..a9a5747 100644 --- a/tests/model/MySQLiDriverTest.php +++ b/tests/model/MySQLiDriverTest.php @@ -14,6 +14,7 @@ require_once dirname(__FILE__) . '/../../model/Db.php'; require_once dirname(__FILE__) . '/../../model/DbStatement.php'; require_once dirname(__FILE__) . '/../../model/MySQLiStatement.php'; require_once dirname(__FILE__) . '/../../model/DbDriver.php'; +require_once dirname(__FILE__) . '/../../model/SqlDbDriver.php'; require_once dirname(__FILE__) . '/../../model/MySQLiDriver.php'; class MySQLiDriverTest extends PHPUnit_Extensions_Database_TestCase diff --git a/tests/model/DbDriverTest.php b/tests/model/SqlDbDriverTest.php similarity index 93% rename from tests/model/DbDriverTest.php rename to tests/model/SqlDbDriverTest.php index 891b0a0..8fc8e89 100644 --- a/tests/model/DbDriverTest.php +++ b/tests/model/SqlDbDriverTest.php @@ -13,8 +13,9 @@ require_once dirname(__FILE__) . '/../../model/DbExpr.php'; require_once dirname(__FILE__) . '/../../model/Db.php'; require_once dirname(__FILE__) . '/../../model/DbDriver.php'; +require_once dirname(__FILE__) . '/../../model/SqlDbDriver.php'; -class DbDriverTest extends PHPUnit_Framework_TestCase +class SqlDbDriverTest extends PHPUnit_Framework_TestCase { private $driver; @@ -22,13 +23,13 @@ class DbDriverTest extends PHPUnit_Framework_TestCase public function setUp() { $conf = array('hostname' => 'localhost', 'database' => 'db', 'username' => 'test', 'password' => '1234'); - $this->driver = $this->getMockForAbstractClass('DbDriver', array($conf)); + $this->driver = $this->getMockForAbstractClass('SqlDbDriver', array($conf)); } public function testConstruct() { $conf = array('hostname' => 'localhost', 'database' => 'db', 'username' => 'test', 'password' => '1234'); - $this->driver = $this->getMockForAbstractClass('DbDriver', array($conf)); + $this->driver = $this->getMockForAbstractClass('SqlDbDriver', array($conf)); } /** @@ -38,7 +39,7 @@ class DbDriverTest extends PHPUnit_Framework_TestCase public function testConstructWrongConfig() { $conf = array('hostname' => 'localhost', 'database' => 'db'); - $this->getMockForAbstractClass('DbDriver', array($conf)); + $this->getMockForAbstractClass('SqlDbDriver', array($conf)); } public function testGetConnection() diff --git a/tests/model/ModelTest.php b/tests/model/SqlModelTest.php similarity index 92% rename from tests/model/ModelTest.php rename to tests/model/SqlModelTest.php index e089eca..18de4ce 100644 --- a/tests/model/ModelTest.php +++ b/tests/model/SqlModelTest.php @@ -17,8 +17,9 @@ require_once dirname(__FILE__) . '/../../model/DbExpr.php'; require_once dirname(__FILE__) . '/../../model/Db.php'; require_once dirname(__FILE__) . '/../../model/DbDriver.php'; require_once dirname(__FILE__) . '/../../model/Model.php'; +require_once dirname(__FILE__) . '/../../model/SqlModel.php'; -class ModelTest extends PHPUnit_Framework_TestCase +class SqlModelTest extends PHPUnit_Framework_TestCase { private $model; @@ -32,7 +33,7 @@ class ModelTest extends PHPUnit_Framework_TestCase Config::set('Db', $conf); if (!class_exists('MockModel')) { - $this->model = $this->getMockForAbstractClass('Model', array(), 'MockModel'); + $this->model = $this->getMockForAbstractClass('SqlModel', array(), 'MockModel'); } else { $this->model = new MockModel(); } @@ -41,7 +42,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testModel() { - $this->assertInstanceOf('Model', $this->model); + $this->assertInstanceOf('SqlModel', $this->model); } public function testGetInsertId() @@ -83,7 +84,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testOrder() { - $model = new ReflectionClass('Model'); + $model = new ReflectionClass('SqlModel'); $method = $model->getMethod('order'); $method->setAccessible(true); $this->assertEquals(' ORDER BY id DESC', $method->invoke($this->model, array('sort' => 'id', 'order' => 'desc'))); @@ -98,7 +99,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testSearch() { - $model = new ReflectionClass('Model'); + $model = new ReflectionClass('SqlModel'); $method = $model->getMethod('search'); $method->setAccessible(true); $this->assertEmpty($method->invoke($this->model, array())); @@ -108,7 +109,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testFetch() { - $model = new ReflectionClass('Model'); + $model = new ReflectionClass('SqlModel'); $method = $model->getMethod('fetch'); $method->setAccessible(true); @@ -118,7 +119,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testFetchField() { - $model = new ReflectionClass('Model'); + $model = new ReflectionClass('SqlModel'); $method = $model->getMethod('fetchField'); $method->setAccessible(true); @@ -128,7 +129,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testFetchAll() { - $model = new ReflectionClass('Model'); + $model = new ReflectionClass('SqlModel'); $method = $model->getMethod('fetchAll'); $method->setAccessible(true); @@ -167,7 +168,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testAddCleanCache() { - $model = new ReflectionClass('Model'); + $model = new ReflectionClass('SqlModel'); $method = $model->getMethod('addCleanCache'); $method->setAccessible(true); @@ -180,7 +181,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testCleanCaches() { - $model = new ReflectionClass('Model'); + $model = new ReflectionClass('SqlModel'); $method = $model->getMethod('addCleanCache'); $method->setAccessible(true); @@ -190,7 +191,7 @@ class ModelTest extends PHPUnit_Framework_TestCase $method->invoke($this->model, $key); $this->assertAttributeEquals(array($key, $key, $key), 'caches_clean', $this->model); - $model = new ReflectionClass('Model'); + $model = new ReflectionClass('SqlModel'); $method = $model->getMethod('cleanCaches'); $method->setAccessible(true); diff --git a/uml_model.zargo b/uml_model.zargo new file mode 100644 index 0000000000000000000000000000000000000000..a775ed3720a006a2872af95b5dcaec08c2237aeb GIT binary patch literal 12190 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ⅇ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 literal 0 HcmV?d00001