diff --git a/.gitignore b/.gitignore index 806b1d6..7c42a9b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /.project /.cache /tests/report +*~ /.idea diff --git a/Load.php b/Load.php index 432cd2c..f2085ad 100644 --- a/Load.php +++ b/Load.php @@ -11,10 +11,27 @@ class Load { - + static protected $file; + static protected $autoload; + static protected $exclude = array('/.git', '/lib/core/tests', '/lib/core/.git'); + + /** + * Add exclude path for autoload. Should be called before setAutoloadFrom + * @static + * @param array $exclude list of relative path (from project root) + * @return void + */ + static public function setExclude($exclude = array()) + { + if(!is_array($exclude)) { + $exclude = array($exclude); + } + self::$exclude = array_merge(self::$exclude, $exclude); + } + static public function setAutoloadFrom($file) { self::$file = $file; @@ -23,7 +40,7 @@ class Load } self::$autoload = require(self::$file); } - + static public function autoload($class) { if (isset(self::$autoload[$class])) { @@ -35,16 +52,16 @@ class Load self::buildAutoload(); } if (isset(self::$autoload[$class])) { - require(PATH . self::$autoload[$class]); + require(PATH . self::$autoload[$class]); } } } - + static public function getFilePath($class) { return self::$autoload[$class]; } - + static protected function buildAutoload() { ignore_user_abort(true); @@ -52,12 +69,11 @@ class Load if (!file_exists($dir) && !mkdir($dir)) { trigger_error('Can\'t create directory: "' . $dir . '"', E_USER_ERROR); } - + $scan = array(PATH . '/' . APP . '/src', PATH . '/lib'); - require_once(PATH . '/lib/core/util/AutoloadBuilder.php'); - - $builder = new AutoloadBuilder(self::$file, $scan); + + $builder = new AutoloadBuilder(self::$file, $scan, self::$exclude); $builder->build(); ignore_user_abort(false); } diff --git a/logger/CliLogger.php b/logger/CliLogger.php index 121cda1..cfc8e5e 100644 --- a/logger/CliLogger.php +++ b/logger/CliLogger.php @@ -13,7 +13,7 @@ class CliLogger extends Logger protected function concreteLog($message) { // Заменяем окончания строк на их символы - $message = str_replace(array("\r", "\n"), array('\r', '\n'), $message); + $message = str_replace(array("\r\n"), array(PHP_EOL), $message); $out = microtime(true) . " \t: " . $this->pid . trim($message) . PHP_EOL; print($out); } diff --git a/model/DbDriver.php b/model/DbDriver.php index 7670662..f8f6f0e 100644 --- a/model/DbDriver.php +++ b/model/DbDriver.php @@ -12,8 +12,6 @@ abstract class DbDriver { - protected $identifier_quote = '`'; - /** * Database connection * @@ -44,173 +42,39 @@ 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 $request * @param mixed $params * @return DbStatement */ - public function query($sql, $params = array()) + public function query($request, $params = array()) { $this->connect(); if (!is_array($params)) { $params = array($params); } - $stmt = $this->prepare($sql); + $stmt = $this->prepare($request); $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 prepare($request); abstract public function getInsertId($table = null, $key = null); @@ -219,12 +83,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/DbStatement.php b/model/DbStatement.php index 58e3cd5..26caba2 100644 --- a/model/DbStatement.php +++ b/model/DbStatement.php @@ -11,49 +11,27 @@ abstract class DbStatement { - + /** * @var DbDriver */ protected $driver; - + /** * @var string */ - protected $sql; - - protected $map = null; - + protected $request; + protected $params = array(); - + protected $result; - - public function __construct($driver, $sql) + + public function __construct($driver, $request) { $this->driver = $driver; - $this->sql = $sql; - } - - public function bindParam($param, &$value) - { - if ($this->map === null) { - $this->mapPlaceholders(); - } - if (count($this->map) > 0) { - if (!is_string($param) && !is_int($param)) { - throw new GeneralException('Placeholder must be an array or string'); - } - if (is_object($value) && ! ($value instanceof DbExpr)) { - throw new GeneralException('Objects excepts DbExpr not allowed.'); - } - if (isset($this->map[$param])) { - $this->params[$param] = &$value; - return true; - } - } - return false; + $this->request = $request; } - + /** * @param array $params * @return bool @@ -67,48 +45,7 @@ abstract class DbStatement } return $this->driverExecute($this->assemble()); } - - protected function mapPlaceholders() - { - $matches = array(); - if(preg_match_all('/(\?|:[A-z0-9_]+)/u', $this->sql, $matches, PREG_OFFSET_CAPTURE)) { - $noname = 0; - foreach ($matches[0] as $id=>$match) { - $match[2] = $matches[1][$id][0]; - $name = ($match[2][0] === ':') ? ltrim($match[2], ':') : $noname++; - $this->map[$name]['placeholder'] = $match[0]; - $this->map[$name]['offset'][] = $match[1]; - } - } - } - - protected function assemble() - { - if (empty($this->map)) { - return $this->sql; - } - - $query = $this->sql; - $placeholders = array(); - foreach($this->map as $name => $place) { - $value = $this->driver->quote($this->params[$name]); - foreach ($place['offset'] as $offset) { - $placeholders[$offset] = array('placeholder' => $place['placeholder'], 'value' => $value); - } - } - - ksort($placeholders); - - $increment = 0; - foreach($placeholders as $current_offset => $placeholder) { - $offset = $current_offset + $increment; - $length = mb_strlen($placeholder['placeholder']); - $query = mb_substr($query, 0, $offset) . $placeholder['value'] . mb_substr($query, $offset + $length); - $increment = (($increment - $length) + mb_strlen($placeholder['value'])); - } - return $query; - } - + public function __destruct() { $this->close(); @@ -138,7 +75,7 @@ abstract class DbStatement } return $data; } - + /** * @param string $field */ @@ -150,36 +87,28 @@ abstract class DbStatement } return false; } - - /** - * @return array - */ - public function fetchPairs() - { - $data = array(); - while ($row = $this->fetch(Db::FETCH_NUM)) { - $data[$row[0]] = $row[1]; - } - return $data; - } - + /* Abstract methods */ - + + abstract public function bindParam($param, &$value); + + abstract protected function assemble(); + abstract public function fetch($style = Db::FETCH_OBJ); - + abstract public function fetchObject($class = 'stdClass'); - + abstract public function close(); - + /** * @return int */ abstract public function affectedRows(); - + abstract public function numRows(); - + /** * @return bool */ - abstract protected function driverExecute($sql); + abstract protected function driverExecute($request); } diff --git a/model/Model.php b/model/Model.php index 53278c5..fd4fd01 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,42 @@ 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); + + /** + * @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/MongoDbCommand.php b/model/MongoDbCommand.php new file mode 100644 index 0000000..2f2c095 --- /dev/null +++ b/model/MongoDbCommand.php @@ -0,0 +1,289 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage db + * @since 2011-11-15 + */ + +class MongoCommandBuilder +{ + + const FIND = 'Find'; + + const INSERT = 'Insert'; + + const UPDATE = 'Update'; + + const REMOVE = 'Remove'; + + const COMMAND = 'Command'; + + static public function factory($type, $collection = null) + { + $class = ucfirst($type) . 'MongoCommand'; + $command = new $class(); + $command->setCollection($collection); + return $command; + } +} + +abstract class MongoDbCommand +{ + protected $collection; + + public function execute() + { + if ($this->checkParams()) { + return $this->concreteExecute(); + } else { + throw new GeneralException(get_called_class() . ' error. Bind all required params first.'); + } + } + + public function bindParam($name, $value) + { + if (property_exists($this, $name)) { + $this->$name = $value; + } + return $this; + } + + public function setCollection($collection) + { + $this->collection = $collection; + return $this; + } + + abstract protected function concreteExecute(); + + abstract protected function checkParams(); + + /** + * @TODO: implement method in subclasses for Profiler + */ + abstract public function __toString(); +} + +class FindMongoCommand extends MongoDbCommand +{ + protected $condition; + + protected $fields; + + protected $multiple = true; + + protected function concreteExecute() + { + if ($this->multiple) { + return $this->collection->find($this->condition, $this->fields); + } else { + return $this->collection->findOne($this->condition, $this->fields); + } + } + + protected function checkParams() + { + if (isset($this->collection) && isset($this->condition) && isset($this->fields)) { + return true; + } else { + return false; + } + } + + public function __toString() + { + if ($this->checkParams()) { + $result = 'Collection: ' . $this->collection . PHP_EOL; + ob_start(); + var_dump($this->condition); + $condition = ob_get_clean(); + $result .= 'Condition: ' . $condition . PHP_EOL; + ob_start(); + var_dump($this->fields); + $fields = ob_get_clean(); + $result .= 'Fields: ' . $fields . PHP_EOL; + $mult = $this->multiple ? 'TRUE' : 'FALSE'; + $result .= 'Multiple fields: ' . $mult . PHP_EOL; + return $result; + } else { + return 'Command properties not set'; + } + } +} + +class InsertMongoCommand extends MongoDbCommand +{ + protected $data; + + protected $safe = true; + + protected $insertId = false; + + protected function concreteExecute() + { + $result = $this->collection->insert($this->data, array('safe' => $this->safe)); + $this->insertId = $this->data['_id']; + return $result; + } + + protected function checkParams() + { + if (isset($this->collection) && isset($this->data) && isset($this->safe)) { + return true; + } else { + return false; + } + } + + public function getInsertId() + { + return $this->insertId; + } + + public function __toString() + { + if ($this->checkParams()) { + $result = 'Collection: ' . $this->collection . PHP_EOL; + ob_start(); + var_dump($this->data); + $data = ob_get_clean(); + $result .= 'Data: ' . $data . PHP_EOL; + $safe = $this->safe ? 'TRUE' : 'FALSE'; + $result .= 'Safe operation: ' . $safe . PHP_EOL; + return $result; + } else { + return 'Command properties not set'; + } + } +} + +class UpdateMongoCommand extends MongoDbCommand +{ + protected $condition; + + protected $data; + + protected $multiple = true; + + protected $upsert = false; + + protected $safe = true; + + protected function concreteExecute() + { + return $this->collection->update($this->condition, $this->data, + array('multiple' => $this->multiple, 'upsert' => $this->upsert, 'safe' => $this->safe)); + } + + protected function checkParams() + { + if (isset($this->collection) && isset($this->condition) && isset($this->data) && + isset($this->upsert) && isset($this->safe) + ) { + return true; + } else { + return false; + } + } + + public function __toString() + { + if ($this->checkParams()) { + $result = 'Collection: ' . $this->collection . PHP_EOL; + ob_start(); + var_dump($this->condition); + $condition = ob_get_clean(); + $result .= 'Condition: ' . $condition . PHP_EOL; + ob_start(); + var_dump($this->data); + $data = ob_get_clean(); + $result .= 'Data: ' . $data . PHP_EOL; + $mult = $this->multiple ? 'TRUE' : 'FALSE'; + $result .= 'Multiple fields: ' . $mult . PHP_EOL; + $upsert = $this->upsert ? 'TRUE' : 'FALSE'; + $result .= 'Upsert: ' . $upsert . PHP_EOL; + $safe = $this->safe ? 'TRUE' : 'FALSE'; + $result .= 'Safe operation: ' . $safe . PHP_EOL; + return $result; + } else { + return 'Command properties not set'; + } + } +} + +class RemoveMongoCommand extends MongoDbCommand +{ + protected $condition; + + protected $safe = true; + + protected function concreteExecute() + { + return $this->collection->remove($this->condition, array('safe' => $this->safe)); + } + + protected function checkParams() + { + if (isset($this->collection) && isset($this->condition) && isset($this->safe)) { + return true; + } else { + return false; + } + } + + public function __toString() + { + if ($this->checkParams()) { + $result = 'Collection: ' . $this->collection . PHP_EOL; + ob_start(); + var_dump($this->condition); + $condition = ob_get_clean(); + $result .= 'Condition: ' . $condition . PHP_EOL; + $safe = $this->safe ? 'TRUE' : 'FALSE'; + $result .= 'Safe operation: ' . $safe . PHP_EOL; + return $result; + } else { + return 'Command properties not set'; + } + } +} + +class CommandMongoCommand extends MongoDbCommand +{ + protected $command; + + protected function concreteExecute() + { + $db = $this->collection->db; + if ($db instanceof MongoDB) { + return $db->command($this->command); + } else { + return false; + } + } + + protected function checkParams() + { + if (isset($this->collection) && isset($this->command)) { + return true; + } else { + return false; + } + } + + public function __toString() + { + if ($this->checkParams()) { + $result = 'Collection: ' . $this->collection . PHP_EOL; + ob_start(); + var_dump($this->command); + $command = ob_get_clean(); + $result .= 'Command: ' . $command . PHP_EOL; + return $result; + } else { + return 'Command properties not set'; + } + } +} diff --git a/model/MongoDriver.php b/model/MongoDriver.php new file mode 100644 index 0000000..8efd68b --- /dev/null +++ b/model/MongoDriver.php @@ -0,0 +1,168 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage db + * @since 2011-11-10 + */ + +/** + * @property Mongo $connection + */ +class MongoDriver extends NoSqlDbDriver +{ + protected $last_insert_id = 0; + + protected $db_name = 'admin'; + + protected $db = null; + + protected function getCollection($name) + { + if (!$this->connection) { + $this->connect(); + } + return $this->connection->selectCollection($this->db, $name); + } + + public function count($collection, $query = array(), $limit = 0, $skip = 0) + { + return $this->getCollection($collection)->count($query, $limit, $skip); + } + + public function find($collection, $condition = array(), $fields = array()) + { + $command = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->getCollection($collection)); + $params = array( + 'condition' => $condition, + 'fields' => $fields + ); + + return $this->query($command, $params); + } + + public function get($collection, $condition, $fields = array()) + { + $command = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->getCollection($collection)); + $params = array( + 'condition' => $condition, + 'fields' => $fields, + 'multiple' => false + ); + return $this->query($command, $params); + } + + public function findAndModify($collection, $query, $update, $sort = array(), $field = array(), $upsert = false, $new = false, $remove = false) + { + $command = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, $this->getCollection($collection)); + $cmd = array( + 'findAndModify' => $collection, + 'query' => $query, + 'update' => $update, + 'sort' => $sort, + 'fields' => $field, + 'new' => $new, + 'upsert' => $upsert, + 'remove' => $remove + ); + $params = array('command' => $cmd); + return $this->query($command, $params); + } + + public function command($collection, $cmd) + { + $command = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, $this->getCollection($collection)); + $params = array('command' => $cmd); + return $this->query($command, $params); + } + + public function insert($collection, $data, $safe = true) + { + $command = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->getCollection($collection)); + $params = array( + 'data' => $data, + 'safe' => $safe + ); + $result = $this->query($command, $params); + $this->last_insert_id = $result->getInsertId(); + return $result->affectedRows(); + } + + public function update($collection, $data, $condition = array(), $multiple = true, $upsert = false, $safe = true) + { + $command = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE, $this->getCollection($collection)); + $params = array( + 'data' => $data, + 'condition' => $condition, + 'multiple' => $multiple, + 'upsert' => $upsert, + 'safe' => $safe + ); + + return $this->query($command, $params)->affectedRows(); + } + + public function delete($collection, $condition = array(), $safe = true) + { + $command = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE, $this->getCollection($collection)); + $params = array( + 'condition' => $condition, + 'safe' => $safe + ); + + return $this->query($command, $params)->affectedRows(); + } + + + + /** + * @param mixed $request + * @return DbStatement + */ + public function prepare($request) + { + return new MongoStatement($this, $request); + } + + public function getInsertId($table = null, $key = null) + { + return $this->last_insert_id; + } + + 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); + $this->db = $this->connection->selectDB($this->config['db']); + } +} diff --git a/model/MongoModel.php b/model/MongoModel.php new file mode 100644 index 0000000..91f2bf7 --- /dev/null +++ b/model/MongoModel.php @@ -0,0 +1,73 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage Model + * @since 2011-11-15 + */ + +abstract class MongoModel extends Model +{ + + public function count($query = array(), $limit = 0, $skip = 0) + { + return $this->db->count($this->table(), $query, $limit, $skip); + } + + public function find($condition = array()) + { + return $this->db->find($this->table(), $condition); + } + + public function get($id) + { + return $this->db->get($this->table(), array('_id' => $id))->fetch(); + } + + public function delete($id) + { + return $this->db->delete($this->table(), array('_id' => $id)); + } + + public function deleteAll($query = array()) + { + $this->db->delete($this->table(), $query); + } + + protected function fetchField($data, $params = array(), $field, $cache_key = null) + { + if (!$cache_key || !$result = $cache_key->get()) { + $result = $this->db->find($this->table(), $data, array($field => 1))->fetchField($field); + if ($cache_key) { + $cache_key->set($result); + } + } + return $result; + } + + protected function fetch($data, $params = array(), $cache_key = null) + { + if (!$cache_key || !$result = $cache_key->get()) { + $result = $this->db->find($this->table(), $data)->fetch(); + if ($cache_key) { + $cache_key->set($result); + } + } + return $result; + } + + protected function fetchAll($data, $params = array(), $cache_key = null) + { + if (!$cache_key || !$result = $cache_key->get()) { + $result = $this->db->find($this->table(), $data)->fetchAll(); + if ($cache_key) { + $cache_key->set($result); + } + } + return $result; + } +} \ No newline at end of file diff --git a/model/MongoStatement.php b/model/MongoStatement.php new file mode 100644 index 0000000..9060dee --- /dev/null +++ b/model/MongoStatement.php @@ -0,0 +1,169 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage db + * @since 2011-11-15 + */ + +/** + * @property MongoDriver $driver + * @property MongoCursor $result + */ +class MongoStatement extends DbStatement +{ + + protected $insertId = false; + + public function order($sort = array()) + { + if ($this->result instanceof MongoCursor) { + $this->result->sort($sort); + return $this; + } else { + throw new GeneralException('MongoStatement error. Impossible order results of opened cursor.'); + } + } + + public function skip($skip = 0) + { + if ($this->result instanceof MongoCursor) { + $this->result->skip($skip); + return $this; + } else { + throw new GeneralException('MongoStatement error. Impossible skip results of opened cursor.'); + } + } + + public function limit($limit = 0) + { + if ($this->result instanceof MongoCursor) { + $this->result->limit($limit); + return $this; + } else { + throw new GeneralException('MongoStatement error. Impossible limit results of opened cursor.'); + } + } + + public function fetch($style = Db::FETCH_OBJ) + { + if (!$this->result) { + return false; + } + + $row = false; + switch ($style) { + case Db::FETCH_OBJ: + $row = $this->fetchObject(); + break; + case Db::FETCH_ASSOC: + if ($this->result instanceof MongoCursor) { + $row = $this->result->getNext(); + } else { + $row = $this->result; + } + break; + default: + throw new GeneralException('Invalid fetch mode "' . $style . '" specified'); + } + return $row; + } + + public function fetchObject($class = 'stdClass') + { + if ($this->result instanceof MongoCursor) { + $row = $this->result->getNext(); + } else { + $row = $this->result; + } + if (is_array($row) && isset($row['_id'])) { + $row = new ArrayObject($row, ArrayObject::ARRAY_AS_PROPS); + } else { + $row = false; + } + return $row; + } + + public function close() + { + $this->result = null; + } + + /** + * @return int + */ + public function affectedRows() + { + if (is_array($this->result)) { + if (isset($this->result['ok']) && $this->result['ok'] == 1) { + if (isset($this->result['n'])) { + return $this->result['n']; + } + } else { + return false; + } + } + return false; + } + + public function numRows() + { + if ($this->result instanceof MongoCursor) { + return $this->result->count(); + } else { + return false; + } + } + + /** + * @param MongoDbCommand $request + * @return bool + */ + protected function driverExecute($request) + { + $mongo = $this->driver->getConnection(); + if ($mongo instanceof Mongo) { + if (DEBUG) { + $profiler = Profiler::getInstance()->profilerCommand('Mongo', $request); + $result = $request->execute(); + $profiler->end(); + } else { + $result = $request->execute(); + } + if ($result === false) { + throw new GeneralException('MongoDB request error.'); + } + if ($result instanceof MongoCursor || is_array($result)) { + $this->result = $result; + if (is_array($result) && isset($result['value'])) { + $this->result = $result['value']; + } + if (is_array($result) && isset($result['values'])) { + $this->result = $result['values']; + } + } + if ($request instanceof InsertMongoCommand) { + $this->insertId = $request->getInsertId(); + } + return true; + } else { + throw new GeneralException('No connection to MongoDB server.'); + } + } + + public function bindParam($param, &$value) + { + $this->request->bindParam($param, $value); + } + + protected function assemble() + { + return $this->request; + } + + public function getInsertId() + { + return $this->insertId; + } +} \ No newline at end of file diff --git a/model/MySQLiDriver.php b/model/MySQLiDriver.php index a287fb4..76b060a 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/MySQLiStatement.php b/model/MySQLiStatement.php index 54dcd63..0176cfd 100644 --- a/model/MySQLiStatement.php +++ b/model/MySQLiStatement.php @@ -15,10 +15,72 @@ */ class MySQLiStatement extends DbStatement { - + + protected $map = null; + + public function bindParam($param, &$value) + { + if ($this->map === null) { + $this->mapPlaceholders(); + } + if (count($this->map) > 0) { + if (!is_string($param) && !is_int($param)) { + throw new GeneralException('Placeholder must be an integer or string'); + } + if (is_object($value) && ! ($value instanceof DbExpr)) { + throw new GeneralException('Objects excepts DbExpr not allowed.'); + } + if (isset($this->map[$param])) { + $this->params[$param] = &$value; + return true; + } + } + return false; + } + protected function mapPlaceholders() + { + $matches = array(); + if(preg_match_all('/(\?|:[A-z0-9_]+)/u', $this->request, $matches, PREG_OFFSET_CAPTURE)) { + $noname = 0; + foreach ($matches[0] as $id=>$match) { + $match[2] = $matches[1][$id][0]; + $name = ($match[2][0] === ':') ? ltrim($match[2], ':') : $noname++; + $this->map[$name]['placeholder'] = $match[0]; + $this->map[$name]['offset'][] = $match[1]; + } + } + } + + protected function assemble() + { + if (empty($this->map)) { + return $this->request; + } + + $query = $this->request; + $placeholders = array(); + foreach($this->map as $name => $place) { + $value = $this->driver->quote($this->params[$name]); + foreach ($place['offset'] as $offset) { + $placeholders[$offset] = array('placeholder' => $place['placeholder'], 'value' => $value); + } + } + + ksort($placeholders); + + $increment = 0; + foreach($placeholders as $current_offset => $placeholder) { + $offset = $current_offset + $increment; + $length = mb_strlen($placeholder['placeholder']); + $query = mb_substr($query, 0, $offset) . $placeholder['value'] . mb_substr($query, $offset + $length); + $increment = (($increment - $length) + mb_strlen($placeholder['value'])); + } + return $query; + } + /** * Fetches single row - * + * * @param mixed $style * @return mixed */ @@ -27,7 +89,7 @@ class MySQLiStatement extends DbStatement if (!$this->result) { return false; } - + $row = false; switch ($style) { case Db::FETCH_OBJ: @@ -47,7 +109,7 @@ class MySQLiStatement extends DbStatement } return $row; } - + /** * @param string $class */ @@ -55,7 +117,19 @@ class MySQLiStatement extends DbStatement { return $this->result->fetch_object($class); } - + + /** + * @return array + */ + public function fetchPairs() + { + $data = array(); + while ($row = $this->fetch(Db::FETCH_NUM)) { + $data[$row[0]] = $row[1]; + } + return $data; + } + public function close() { if ($this->result !== null) { @@ -63,12 +137,12 @@ class MySQLiStatement extends DbStatement $this->result = null; } } - + public function affectedRows() { return $this->driver->getConnection()->affected_rows; } - + public function numRows() { if ($this->result) { @@ -76,22 +150,22 @@ class MySQLiStatement extends DbStatement } return false; } - - protected function driverExecute($sql) + + protected function driverExecute($request) { /** * @var MySQLi */ $mysqli = $this->driver->getConnection(); if (DEBUG) { - $profiler = Profiler::getInstance()->profilerCommand('MySQL', $sql); - $result = $mysqli->query($sql); + $profiler = Profiler::getInstance()->profilerCommand('MySQL', $request); + $result = $mysqli->query($request); $profiler->end(); } else { - $result = $mysqli->query($sql); + $result = $mysqli->query($request); } if ($result === false) { - $message = $mysqli->error . "\nQuery: \"" . $sql . '"'; + $message = $mysqli->error . "\nQuery: \"" . $request . '"'; throw new GeneralException($message, $mysqli->errno); } if ($result instanceof MySQLi_Result) { diff --git a/model/NoSqlDbDriver.php b/model/NoSqlDbDriver.php new file mode 100644 index 0000000..19a7020 --- /dev/null +++ b/model/NoSqlDbDriver.php @@ -0,0 +1,14 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage db + * @since 2011-11-11 + */ + +abstract class NoSqlDbDriver extends DbDriver +{ + + abstract function find($collection, $condition = array(), $fields = array()); +} \ No newline at end of file diff --git a/model/SqlDbDriver.php b/model/SqlDbDriver.php new file mode 100644 index 0000000..fa86668 --- /dev/null +++ b/model/SqlDbDriver.php @@ -0,0 +1,168 @@ + + * @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 $table + * @param mixed $bind + * @param mixed $on_duplicate + * @return int Affected rows count + */ + public function insert($table, $data, $on_duplicate = array()) + { + $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 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 0000000..1c74e42 Binary files /dev/null and b/model_class_diadram.png differ diff --git a/session/Session.model.php b/session/Session.model.php index 22d57cd..769be5e 100644 --- a/session/Session.model.php +++ b/session/Session.model.php @@ -9,7 +9,7 @@ * @filesource $URL$ */ -class SessionModel extends Model +class SessionModel extends SqlModel { protected $life_time; diff --git a/tests/model/DbStatementTest.php b/tests/model/DbStatementTest.php index ba72c5e..cea384d 100644 --- a/tests/model/DbStatementTest.php +++ b/tests/model/DbStatementTest.php @@ -44,65 +44,7 @@ class DbStatementTest extends PHPUnit_Framework_TestCase public function testConstruct() { $this->assertAttributeEquals($this->driver, 'driver', $this->stmt); - $this->assertAttributeEquals($this->sql, 'sql', $this->stmt); - } - - public function testBindParam() - { - $val = $this->getMockBuilder('DbExpr') - ->disableOriginalConstructor() - ->getMock(); - $this->assertFalse($this->stmt->bindParam('var', $val)); - $this->assertTrue($this->stmt->bindParam('place', $val)); - } - - /** - * @TODO: change Exception Message 'Placeholder must be an array or string' - no arrays available - */ - public function testBindParamExceptionParam() - { - $val = 1; - $this->setExpectedException('GeneralException', 'Placeholder must be an array or string'); - $this->stmt->bindParam(array(), $val); - } - - - public function testBindParamExceptionWrongObject() - { - $val = $this->getMock('NotDbExpr'); - $this->setExpectedException('GeneralException', 'Objects excepts DbExpr not allowed.'); - $this->stmt->bindParam('paa', $val); - } - - public function testExecute() - { - $this->stmt - ->expects($this->once()) - ->method('driverExecute') - ->with($this->anything()) - ->will($this->returnCallback(array($this, 'dbStatementAssemble'))); - - $this->driver - ->expects($this->any()) - ->method('quote') - ->with($this->anything()) - ->will($this->returnCallback(array($this, 'driverQuote'))); - - $result = $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val')); - $this->assertSame('SELECT * place_val FROM place_val AND new_val', $result); - } - - public function testExecuteNoPlaceholders() - { - $this->sql = 'PLAIN SQL'; - $this->stmt = $this->getMockForAbstractClass('DbStatement', array($this->driver, $this->sql)); - $this->stmt - ->expects($this->once()) - ->method('driverExecute') - ->with($this->anything()) - ->will($this->returnCallback(array($this, 'dbStatementAssemble'))); - $result = $this->stmt->execute(array()); - $this->assertSame('PLAIN SQL', $result); + $this->assertAttributeEquals($this->sql, 'request', $this->stmt); } public function testFetch() @@ -124,19 +66,9 @@ class DbStatementTest extends PHPUnit_Framework_TestCase $this->assertSame(11, $result[0]->one); $this->assertSame(32, $result[2]->two); - reset($this->testData); - $result = $this->stmt->fetchPairs(); - $this->assertSame(31, $result['one']); - } - - public function dbStatementAssemble($val) - { - return $val; - } - - public function driverQuote($val) - { - return $val; +// reset($this->testData); +// $result = $this->stmt->fetchPairs(); +// $this->assertEquals(31, $result['one']); } public function dbStatementFetch($style) diff --git a/tests/model/MongoDbCommandTest.php b/tests/model/MongoDbCommandTest.php new file mode 100644 index 0000000..8302b42 --- /dev/null +++ b/tests/model/MongoDbCommandTest.php @@ -0,0 +1,312 @@ + + * @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'; +require_once dirname(__FILE__) . '/../../model/MongoDbCommand.php'; +require_once dirname(__FILE__) . '/../../exception/GeneralException.php'; + +class MongoDbCommandTest extends PHPUnit_Framework_TestCase +{ + + private $conf = array(); + + private $driver; + + private $collection; + + public function setUp() + { + $this->conf = array( + 'hostname' => 'localhost', + 'database' => 'test', + 'username' => 'test', + 'password' => '1234', + 'port' => 27017 + ); + $this->driver = new MongoDriver($this->conf); + $connection = $this->driver->getConnection(); + + $db = $connection->selectDB($this->conf['database']); + $db->authenticate($this->conf['username'], $this->conf['password']); + $collection = 'items'; + $db->dropCollection($collection); + $this->collection = $db->selectCollection($collection); + } + + /** + * @group Mongo + */ + public function testCommandFactory() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND); + $this->assertInstanceOf('MongoDbCommand', $cmd); + $this->assertInstanceOf('FindMongoCommand', $cmd); + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT); + $this->assertInstanceOf('MongoDbCommand', $cmd); + $this->assertInstanceOf('InsertMongoCommand', $cmd); + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE); + $this->assertInstanceOf('MongoDbCommand', $cmd); + $this->assertInstanceOf('UpdateMongoCommand', $cmd); + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE); + $this->assertInstanceOf('MongoDbCommand', $cmd); + $this->assertInstanceOf('RemoveMongoCommand', $cmd); + } + + /** + * @group Mongo + */ + public function testFindCommand() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection); + $cmd->bindParam('condition', array('name' => 'bread'))->bindParam('fields', array()); + $result = $cmd->execute(); + $this->assertEquals(0, $result->count()); + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection); + $cmd + ->bindParam('data', array('name' => 'insert')) + ->bindParam('safe', true); + $cmd->execute(); + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection); + $cmd + ->bindParam('data', array('name' => 'insert')) + ->bindParam('safe', true); + $cmd->execute(); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection); + $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array()); + $this->assertEquals(2, $cmd->execute()->count()); + + $cmd + ->bindParam('condition', array('name' => 'insert')) + ->bindParam('fields', array()) + ->bindParam('multiple', false); + + $result = $cmd->execute(); + $this->assertEquals('insert', $result['name']); + } + + /** + * @group Mongo + */ + public function testFindCommandNotAllParamsBinded() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection); + $cmd->bindParam('condition', array('name' => 'bread')); + $this->setExpectedException('GeneralException', 'FindMongoCommand error. Bind all required params first'); + $cmd->execute(); + } + + /** + * @group Mongo + */ + public function testInsertCommand() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection); + + $cmd + ->bindParam('data', array('name' => 'insert')) + ->bindParam('safe', true); + + $this->assertFalse($cmd->getInsertId()); + + $this->assertArrayHasKey('n', $cmd->execute()); + $this->assertNotEmpty($cmd->getInsertId()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection); + $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array()); + $result = $cmd->execute(); + $this->assertEquals(1, $result->count()); + } + + /** + * @group Mongo + */ + public function testInsertCommandNotAllParamsBinded() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection); + $this->setExpectedException('GeneralException', 'InsertMongoCommand error. Bind all required params first'); + $cmd->execute(); + } + + /** + * @group Mongo + */ + public function testUpdateCommand() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection); + $cmd + ->bindParam('data', array('name' => 'insert')) + ->bindParam('safe', true); + $this->assertArrayHasKey('n', $cmd->execute()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE, $this->collection); + $cmd + ->bindParam('condition', array('name' => 'insert')) + ->bindParam('data', array('$set' => array('name' => 'update'))) + ->bindParam('upsert', false) + ->bindParam('safe', true); + $this->assertArrayHasKey('n', $cmd->execute()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection); + $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array()); + $result = $cmd->execute(); + $this->assertEquals(0, $result->count()); + $cmd->bindParam('condition', array('name' => 'update'))->bindParam('fields', array()); + $result = $cmd->execute(); + $this->assertEquals(1, $result->count()); + } + + /** + * @group Mongo + */ + public function testUpdateCommandNotAllParamsBinded() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE, $this->collection); + $cmd->bindParam('data', array('name' => 'bread')); + $this->setExpectedException('GeneralException', 'UpdateMongoCommand error. Bind all required params first'); + $cmd->execute(); + } + + /** + * @group Mongo + */ + public function testRemoveCommand() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection); + $cmd + ->bindParam('data', array('name' => 'insert')) + ->bindParam('safe', true); + $this->assertArrayHasKey('n', $cmd->execute()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection); + $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array()); + $result = $cmd->execute(); + $this->assertEquals(1, $result->count()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE, $this->collection); + $cmd + ->bindParam('condition', array('name' => 'insert')) + ->bindParam('safe', true); + $this->assertArrayHasKey('n', $cmd->execute()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection); + $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array()); + $result = $cmd->execute(); + $this->assertEquals(0, $result->count()); + } + + /** + * @group Mongo + */ + public function testRemoveCommandNotAllParamsBinded() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE, $this->collection); + $this->setExpectedException('GeneralException', 'RemoveMongoCommand error. Bind all required params first.'); + $cmd->execute(); + } + + /** + * @group Mongo + */ + public function testCommandCommandNotAllParamsBinded() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, $this->collection); + $this->setExpectedException('GeneralException', 'CommandMongoCommand error. Bind all required params first'); + $cmd->execute(); + } + + /** + * @group Mongo + */ + public function testCommandCommandNotMongoDb() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, new CollectionMock()); + $cmd->bindParam('command', array()); + $this->assertFalse($cmd->execute()); + } + + /** + * @group Mongo + */ + public function testCommandCommand() + { + $col = new CollectionMock(); + $col->db = $this->getMock('MongoDb', array('command'), array(), 'SomeMongoMock', false); + $col->db + ->expects($this->once()) + ->method('command') + ->will($this->returnValue(true)); + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, $col); + $cmd->bindParam('command', array()); + $this->assertTrue($cmd->execute()); + } + + /** + * @group Mongo + */ + public function testToStringParamsNotSet() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, new CollectionMock()); + $this->assertSame('Command properties not set', $cmd->__toString()); + } + + /** + * @group Mongo + */ + public function testToString() + { + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, new CollectionMock()); + $this->assertSame('Command properties not set', $cmd->__toString()); + $cmd->bindParam('command', array()); + $this->assertStringStartsWith('Collection: CollectionMock', $cmd->__toString()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection); + $this->assertSame('Command properties not set', $cmd->__toString()); + $cmd + ->bindParam('data', array('name' => 'insert')) + ->bindParam('safe', true); + $this->assertStringStartsWith('Collection: ', $cmd->__toString()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection); + $this->assertSame('Command properties not set', $cmd->__toString()); + $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array()); + $this->assertStringStartsWith('Collection: ', $cmd->__toString()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE, $this->collection); + $this->assertSame('Command properties not set', $cmd->__toString()); + $cmd + ->bindParam('condition', array('name' => 'insert')) + ->bindParam('safe', true); + $this->assertStringStartsWith('Collection: ', $cmd->__toString()); + + $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE, $this->collection); + $this->assertSame('Command properties not set', $cmd->__toString()); + $cmd + ->bindParam('condition', array('name' => 'insert')) + ->bindParam('data', array('$set' => array('name' => 'update'))) + ->bindParam('upsert', false) + ->bindParam('safe', true); + $this->assertStringStartsWith('Collection: ', $cmd->__toString()); + } +} + +class CollectionMock +{ + public $db = array(); + + public function __toString() + { + return 'CollectionMock'; + } +} diff --git a/tests/model/MongoDriverTest.php b/tests/model/MongoDriverTest.php new file mode 100644 index 0000000..b673d4f --- /dev/null +++ b/tests/model/MongoDriverTest.php @@ -0,0 +1,343 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage UnitTests + * @since 2011-11-10 + * + * Unit tests for MongoDriver class + */ + +require_once dirname(__FILE__) . '/../../model/Db.php'; +require_once dirname(__FILE__) . '/../../model/DbDriver.php'; +require_once dirname(__FILE__) . '/../../model/NoSqlDbDriver.php'; +require_once dirname(__FILE__) . '/../../model/MongoDbCommand.php'; +require_once dirname(__FILE__) . '/../../model/DbStatement.php'; +require_once dirname(__FILE__) . '/../../model/MongoStatement.php'; +require_once dirname(__FILE__) . '/../../model/MongoDriver.php'; +require_once dirname(__FILE__) . '/../../exception/GeneralException.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 + ); + + $data = array( + array( + 'name' => 'bread', + 'price' => 3.2, + 'quantity' => 10 + ), + array( + 'name' => 'eggs', + 'price' => 2.1, + 'quantity' => 20 + ), + array( + 'name' => 'fish', + 'price' => 13.2, + 'quantity' => 2 + ), + array( + 'name' => 'milk', + 'price' => 3.8, + 'quantity' => 1 + ), + array( + 'name' => 'eggs', + 'price' => 2.3, + 'quantity' => 5 + ) + ); + $connection = new Mongo('mongodb://' . $this->conf['hostname'] . ':' . $this->conf['port']); + $db = $connection->selectDB($this->conf['database']); + $db->authenticate($this->conf['username'], $this->conf['password']); + $collection = 'items'; + $db->dropCollection($collection); + $collection = $db->selectCollection($collection); + foreach($data as $document) { + $collection->insert($document); + } + } + + /** + * @group Mongo + */ + public function testGetConnectionNoHostname() + { + unset($this->conf['hostname']); + $this->setExpectedException('GeneralException', 'Configuration must have a "hostname"'); + $mongo = new MongoDriver($this->conf); + } + + /** + * @group Mongo + */ + public function testGetConnectionWrongPassword() + { + $this->conf['password'] = 'nopass'; + $mongo = new MongoDriver($this->conf); + $this->setExpectedException('MongoConnectionException', 'Couldn\'t authenticate with database'); + $this->assertInstanceOf('MongoDB', $mongo->getConnection()); + } + + /** + * @group Mongo + */ + public function testGetConnection() + { + $mongo = new MongoDriver($this->conf); + + $this->assertFalse($mongo->isConnected()); + $this->assertInstanceOf('Mongo', $mongo->getConnection()); + $this->assertTrue($mongo->isConnected()); + $mongo->getConnection(); + $mongo->disconnect(); + $this->assertFalse($mongo->isConnected()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFind() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows()); + $this->assertEquals(2, $mongo->find('items', array('name' => 'eggs'))->numRows()); + $eggs = $mongo->find('items', array('name' => 'eggs')); + $egg = $eggs->fetch(); + $this->assertEquals(20, $egg->quantity); + $egg = $eggs->fetchObject(); + $this->assertEquals('eggs', $egg->name); + $this->assertFalse($eggs->fetchObject()); + + $this->assertEquals(3, $mongo->find('items', array('price' => array('$lt' => 3.5)))->numRows()); + $data = $mongo->find('items', array('price' => array('$lt' => 3.5))); + $count = 0; + while($row = $data->fetch(Db::FETCH_ASSOC)) { + $count++; + $this->assertLessThan(3.5, $row['price']); + } + $this->assertEquals(3, $count); + $this->assertEquals(5, $mongo->find('items', array())->numRows()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testOrderSkipLimit() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + + $count = $mongo->find('items', array())->numRows(); + + $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows()); + $this->assertEquals(2, $mongo->find('items', array('name' => 'eggs'))->numRows()); + $mongo->insert('items', array('name' => 'fdsbssc')); + $mongo->insert('items', array('name' => 'boc')); + $mongo->insert('items', array('name' => 'abc')); + $mongo->insert('items', array('name' => 'vcxxc')); + $mongo->insert('items', array('name' => 'abbc')); + $mongo->insert('items', array('name' => 'dsbssc')); + $mongo->insert('items', array('name' => 'bssc')); + + $data = $mongo->find('items', array()); + $this->assertEquals($count + 7, $data->numRows()); + $data->order(array('name' => 1)); + $this->assertEquals('abbc', $data->fetch()->name); + $this->assertEquals('abc', $data->fetch()->name); + $this->assertEquals('boc', $data->fetch()->name); + $data = $mongo->find('items', array()); + $data->order(array('name' => -1)); + $data->skip(3); + $data->limit(1); + while($row = $data->fetch()) { + $this->assertEquals('fdsbssc', $row->name); + } + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testCount() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $mongo = new MongoDriver($this->conf); + $this->assertEquals(5, $mongo->count('items')); + $this->assertEquals(2, $mongo->count('items', array('name' => 'eggs'))); + $this->assertEquals(1, $mongo->count('items', array('name' => 'eggs'), 1)); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testGet() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + $eggs = $mongo->get('items', array('name' => 'eggs'))->fetchObject(); + $this->assertEquals(20, $eggs->quantity); + $eggs = $mongo->get('items', array('name' => 'eggs'))->fetch(); + $this->assertEquals('eggs', $eggs->name); + $this->assertInstanceOf('MongoId', $eggs->_id); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testRemove() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows()); + $this->assertEquals(0, $mongo->delete('items', array('name' => 'esggs'))); + $this->assertEquals(2, $mongo->delete('items', array('name' => 'eggs'))); + $this->assertEquals(1, $mongo->delete('items', array('name' => 'bread'))); + $this->assertEquals(0, $mongo->find('items', array('name' => 'bread'))->numRows()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testInsert() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows()); + $this->assertEquals(0, $mongo->insert('items', array('name' => 'bread'))); + $this->assertNotEmpty($mongo->getInsertId()); + $this->assertEquals(2, $mongo->find('items', array('name' => 'bread'))->numRows()); + $this->assertEquals(0, $mongo->insert('items', array('name' => 'meat', 'weight' => 230))); + $this->assertEquals(230, $mongo->get('items', array('name' => 'meat'))->fetch()->weight); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testGetInsertId() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + $this->assertEquals(0, $mongo->getInsertId()); + $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows()); + $this->assertEquals(0, $mongo->insert('items', array('name' => 'bread'))); + $this->assertEquals(2, $mongo->find('items', array('name' => 'bread'))->numRows()); + $id1 = $mongo->getInsertId(); + $this->assertNotEmpty($id1); + $this->assertEquals(0, $mongo->insert('items', array('name' => 'bread'))); + $id2 = $mongo->getInsertId(); + $this->assertNotEmpty($id2); + $this->assertNotEquals($id1, $id2); + $this->assertEquals(3, $mongo->find('items', array('name' => 'bread'))->numRows()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testUpdate() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows()); + $this->assertEquals(1, $mongo->update('items', array('$set' => array('price' => 200, 'date' => 'today')), array('name' => 'fish'))); + $this->assertEquals(2, $mongo->update('items', array('$set' => array('price' => 1)), array('name' => 'eggs'))); + $fish = $mongo->get('items', array('name' => 'fish'))->fetch(); + $this->assertEquals(200, $fish->price); + $this->assertEquals('today', $fish->date); + $this->assertEquals(0, $mongo->update('items', array('$set' => array('price' => 200, 'date' => 'today')), array('name' => 'ball'))); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testUpsert() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + + $mongo->insert('items', array('name' => 'bread')); + $this->assertEquals(1, $mongo->update('items', array('$set' => array('price' => 200, 'date' => 'today')), array('name' => 'ball'), true, true)); + $this->assertEquals('today', $mongo->get('items', array('name' => 'ball'))->fetch()->date); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFindAndModify() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mongo = new MongoDriver($this->conf); + + $this->assertEquals(10, $mongo->get('items', array('name' => 'bread'))->fetch()->quantity); + $result = $mongo->findAndModify('items', array('name' => 'bread'), array('$set' => array('quantity' => 20))); + $this->assertEquals(10, $result->fetch()->quantity); + $this->assertEquals(20, $mongo->get('items', array('name' => 'bread'))->fetch()->quantity); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testCommand() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $mongo = new MongoDriver($this->conf); + $result = $mongo->command('items', array('distinct' =>'items', 'key' => 'name')); + $this->assertEquals(4, count($result->fetch(DB::FETCH_ASSOC))); + } +} \ No newline at end of file diff --git a/tests/model/MongoModelTest.php b/tests/model/MongoModelTest.php new file mode 100644 index 0000000..c9cc58c --- /dev/null +++ b/tests/model/MongoModelTest.php @@ -0,0 +1,238 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage UnitTests + * @since 2011-11-7 + * + * Unit tests for MongoModel class + */ + +require_once dirname(__FILE__) . '/../../Registry.php'; +require_once dirname(__FILE__) . '/../../Config.php'; +require_once dirname(__FILE__) . '/../../cache/Cacher.php'; +require_once dirname(__FILE__) . '/../../model/DbExpr.php'; +require_once dirname(__FILE__) . '/../../model/Db.php'; +require_once dirname(__FILE__) . '/../../model/MongoDbCommand.php'; +require_once dirname(__FILE__) . '/../../model/DbStatement.php'; +require_once dirname(__FILE__) . '/../../model/MongoStatement.php'; +require_once dirname(__FILE__) . '/../../model/DbDriver.php'; +require_once dirname(__FILE__) . '/../../model/NoSqlDbDriver.php'; +require_once dirname(__FILE__) . '/../../model/MongoDriver.php'; +require_once dirname(__FILE__) . '/../../model/Model.php'; +require_once dirname(__FILE__) . '/../../model/MongoModel.php'; + +class MongoModelTest extends PHPUnit_Framework_TestCase +{ + + private $model; + + public function run(PHPUnit_Framework_TestResult $result = NULL) + { + $this->setPreserveGlobalState(false); + return parent::run($result); + } + + public function setUp() + { + $conf = array('default' => array('driver' => 'MongoDriver', 'hostname' => 'localhost', 'database' => 'test', 'username' => 'test', 'password' => '1234', 'port' => 27017)); + + $this->dbSetUp($conf); + + Config::set('Db', $conf); + if (!class_exists('MockModel')) { + $this->model = $this->getMockForAbstractClass('MongoModel', array(), 'MongoMockModel'); + } else { + $this->model = new MongoMockModel(); + } + set_new_overload(array($this, 'newCallback')); + } + + /** + * @group Mongo + */ + public function testModel() + { + $this->assertInstanceOf('MongoMockModel', $this->model); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFind() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $result = $this->model->find(); + $this->assertInstanceOf('MongoStatement', $result); + $this->assertEquals('milk', $result->limit(2)->order(array('name' => -1))->fetch()->name); + $this->assertEquals('fish', $result->fetch()->name); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testGet() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $result = $this->model->find()->limit(1)->order(array('name' => 1)); + $result = $result->fetch(); + $this->assertEquals('bread', $result->name); + $id = $result->_id; + $this->assertEquals(10, $this->model->get($id)->quantity); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testDelete() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $result = $this->model->find()->limit(1)->order(array('name' => 1)); + $id = $result->fetch()->_id; + $this->assertEquals(1, $this->model->delete($id)); + $this->assertFalse($this->model->find(array('name' => 'bread'))->fetch()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testDeleteAll() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $this->assertEquals(2, $this->model->count(array('name' => 'eggs'))); + $this->assertEquals(0, $this->model->deleteAll(array('name' => 'eggs'))); + $this->assertFalse($this->model->find(array('name' => 'eggs'))->fetch()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testCount() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $this->assertEquals(5, $this->model->count()); + $this->assertEquals(2, $this->model->count(array('name' => 'eggs'))); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFetch() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $mock = $this->getMock('CacheKey', array('set', 'get')); + $mock->expects($this->exactly(3)) + ->method('set') + ->will($this->returnValue(true)); + $mock->expects($this->exactly(3)) + ->method('get') + ->will($this->returnValue(false)); + + $model = new ReflectionClass('MongoModel'); + $method = $model->getMethod('fetchField'); + $method->setAccessible(true); + + $result = $method->invoke($this->model, array('name' => 'milk'), array(), 'quantity', $mock); + $this->assertEquals(1, $result); + + $model = new ReflectionClass('MongoModel'); + $method = $model->getMethod('fetch'); + $method->setAccessible(true); + + $result = $method->invoke($this->model, array('name' => 'bread'), array(), $mock); + $this->assertEquals('bread', $result->name); + + $model = new ReflectionClass('MongoModel'); + $method = $model->getMethod('fetchAll'); + $method->setAccessible(true); + + $result = $method->invoke($this->model, array('name' => 'eggs'), array(), $mock); + $this->assertEquals(2, count($result)); + $this->assertEquals('eggs', $result[0]->name); + } + + public function tearDown() + { + $conf = array('driver' => 'MongoDriver', 'hostname' => 'localhost', 'database' => 'test', 'username' => 'test', 'password' => '1234', 'port' => 27017); + + + $connection = new Mongo('mongodb://' . $conf['hostname'] . ':' . $conf['port']); + $db = $connection->selectDB($conf['database']); + $db->authenticate($conf['username'], $conf['password']); + $collection = 'mongomock'; + $db->dropCollection($collection); + } + + protected function newCallback($className) + { + switch ($className) { + case 'CacheKey': + return 'MockCacheKey'; + default: + return $className; + } + } + + public function dbSetUp($conf) + { + $data = array( + array( + 'name' => 'bread', + 'price' => 3.2, + 'quantity' => 10 + ), + array( + 'name' => 'eggs', + 'price' => 2.1, + 'quantity' => 20 + ), + array( + 'name' => 'fish', + 'price' => 13.2, + 'quantity' => 2 + ), + array( + 'name' => 'milk', + 'price' => 3.8, + 'quantity' => 1 + ), + array( + 'name' => 'eggs', + 'price' => 2.3, + 'quantity' => 5 + ) + ); + $connection = new Mongo('mongodb://' . $conf['default']['hostname'] . ':' . $conf['default']['port']); + $db = $connection->selectDB($conf['default']['database']); + $db->authenticate($conf['default']['username'], $conf['default']['password']); + $collection = 'mongomock'; + $db->dropCollection($collection); + $collection = $db->selectCollection($collection); + foreach($data as $document) { + $collection->insert($document); + } + } + +} diff --git a/tests/model/MongoStatementTest.php b/tests/model/MongoStatementTest.php new file mode 100644 index 0000000..c945925 --- /dev/null +++ b/tests/model/MongoStatementTest.php @@ -0,0 +1,464 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage UnitTests + * @since 2011-11-15 + * + * Unit tests for MySQLiStatement class + */ + +require_once dirname(__FILE__) . '/../../util/profiler/CommandProfiler.php'; +require_once dirname(__FILE__) . '/../../util/profiler/Profiler.php'; +require_once dirname(__FILE__) . '/../../model/Db.php'; +require_once dirname(__FILE__) . '/../../model/DbDriver.php'; +require_once dirname(__FILE__) . '/../../model/DbStatement.php'; +require_once dirname(__FILE__) . '/../../model/MongoStatement.php'; +require_once dirname(__FILE__) . '/../../exception/GeneralException.php'; + +class MongoStatementTest extends PHPUnit_Framework_TestCase +{ + + + private $driver; + + private $stmt; + + private $request; + + private $testData = array( + array('one' => 11, 'two' => 12), + array('one' => 21, 'two' => 22), + array('one' => 31, 'two' => 32), + ); + + public function run(PHPUnit_Framework_TestResult $result = NULL) + { + $this->setPreserveGlobalState(false); + return parent::run($result); + } + + public function setUp() + { + if (!isset($this->stmt)) { + $this->driver = $this->getMockBuilder('DbDriverMock') + ->disableOriginalConstructor() + ->setMethods(array('getConnection')) + ->getMock(); + $this->request = $this->getMockBuilder('MongoDbCommandMock') + ->disableOriginalConstructor() + ->setMethods(array('execute', 'bindParam', 'getInsertId')) + ->getMock(); + $this->stmt = new MongoStatement($this->driver, $this->request); + } + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testAffectedNumRowsNoResult() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $this->assertFalse($this->stmt->affectedRows()); + $this->assertFalse($this->stmt->numRows()); + + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->any()) + ->method('execute') + ->will($this->returnValue(array())); + $this->stmt->execute(); + $this->assertFalse($this->stmt->affectedRows()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testAffectedNumRows() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array('n' => 20, 'ok' => 1))); + $this->stmt->execute(); + $this->assertEquals(20, $this->stmt->affectedRows()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testGetInsertId() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $this->setDriverGetConnectionMethod(); + + $this->request = $this->getMockBuilder('InsertMongoCommand') + ->disableOriginalConstructor() + ->setMethods(array('execute', 'bindParam', 'getInsertId')) + ->getMock(); + + $this->request + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array('n' => 20, 'ok' => 1))); + $this->request + ->expects($this->once()) + ->method('getInsertId') + ->will($this->returnValue('4b0rrs')); + + $this->stmt = new MongoStatement($this->driver, $this->request); + + $this->stmt->execute(); + $this->assertEquals('4b0rrs', $this->stmt->getInsertId()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testExecute() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $this->setDriverGetConnectionMethod()->setRequestExecuteMethod(); + $this->assertTrue($this->stmt->execute()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testExecuteNoResult() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->any()) + ->method('execute') + ->will($this->returnValue(false)); + $this->setExpectedException('GeneralException', 'MongoDB request error.'); + $this->stmt->execute(); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testExecuteNoConnection() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $this->driver + ->expects($this->any()) + ->method('getConnection') + ->will($this->returnValue(false)); + $this->setExpectedException('GeneralException', 'No connection to MongoDB server.'); + $this->stmt->execute(); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testExecuteWithDebug() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->setDriverGetConnectionMethod()->setRequestExecuteMethod(); + $this->assertTrue($this->stmt->execute()); + $this->assertEquals(10, $this->stmt->numRows()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testBindParam() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + + $this->request + ->expects($this->once()) + ->method('bindParam') + ->will($this->returnValue(true)); + + $this->setDriverGetConnectionMethod()->setRequestExecuteMethod(); + $this->assertTrue($this->stmt->execute(array('one' => 'two'))); + + $this->assertAttributeNotEquals(null, 'result', $this->stmt); + $this->stmt->close(); + $this->assertAttributeEquals(null, 'result', $this->stmt); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFetch() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->assertFalse($this->stmt->fetch()); + + $this->setDriverGetConnectionMethod()->setRequestForFetch(); + + $this->stmt->execute(); + $result = $this->stmt->fetch(); + $this->assertEquals('prev', $result->next); + $this->assertFalse($this->stmt->fetch()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFetchWithInitialArray() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->assertFalse($this->stmt->fetch()); + + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array('some' => 'val'))); + + $this->stmt->execute(); + $this->assertFalse($this->stmt->fetch()); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFetchAssocFromCursor() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->assertFalse($this->stmt->fetch()); + + $this->setDriverGetConnectionMethod()->setRequestForFetch(); + + $this->stmt->execute(); + $result = $this->stmt->fetch(Db::FETCH_ASSOC); + $this->assertEquals('prev', $result['next']); + $this->assertEquals(array(), $this->stmt->fetch(Db::FETCH_ASSOC)); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFetchAssocFromArray() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->assertFalse($this->stmt->fetch()); + + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array('some' => 'val'))); + + $this->stmt->execute(); + $result = $this->stmt->fetch(Db::FETCH_ASSOC); + $this->assertEquals('val', $result['some']); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testFetchWrongMode() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->assertFalse($this->stmt->fetch()); + + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array('some' => 'val'))); + + $this->stmt->execute(); + $this->setExpectedException('GeneralException', 'Invalid fetch mode "222" specified'); + $this->stmt->fetch(222); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testSkipOrderLimit() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->setDriverGetConnectionMethod()->setRequestForFetch(); + + $this->stmt->execute(); + $this->assertInstanceOf('MongoStatement', $this->stmt->order(array('id' => 1))); + $this->assertInstanceOf('MongoStatement', $this->stmt->limit(10)); + $this->assertInstanceOf('MongoStatement', $this->stmt->skip(1)); + + $this->stmt->fetch(); + $this->stmt->fetch(); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testOrderException() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array('some' => 'val'))); + + $this->stmt->execute(); + $this->setExpectedException('GeneralException', 'MongoStatement error. Impossible order results of opened cursor'); + $this->stmt->order(array('id' => 1)); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testSkipException() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array('some' => 'val'))); + + $this->stmt->execute(); + $this->setExpectedException('GeneralException', 'MongoStatement error. Impossible skip results of opened cursor'); + $this->stmt->skip(array('id' => 1)); + } + + /** + * @runInSeparateProcess + * @group Mongo + */ + public function testLimitException() + { + if (!defined('DEBUG')) { + define('DEBUG', true); + } + $this->setDriverGetConnectionMethod(); + $this->request + ->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array('some' => 'val'))); + + $this->stmt->execute(); + $this->setExpectedException('GeneralException', 'MongoStatement error. Impossible limit results of opened cursor'); + $this->stmt->limit(array('id' => 1)); + } + + private function setDriverGetConnectionMethod() + { + $mongoMock = $this->getMock('Mongo'); + + $this->driver + ->expects($this->any()) + ->method('getConnection') + ->will($this->returnValue($mongoMock)); + return $this; + } + + public function setRequestExecuteMethod() + { + $resultMock = $this->getMockBuilder('MongoCursor') + ->setMethods(array('count')) + ->disableOriginalConstructor() + ->getMock(); + + $resultMock + ->expects($this->any()) + ->method('count') + ->will($this->returnValue(10)); + + $this->request + ->expects($this->any()) + ->method('execute') + ->will($this->returnValue($resultMock)); + + return $this; + } + + public function setRequestForFetch() + { + $resultMock = $this->getMockBuilder('MongoCursor') + ->setMethods(array('count', 'getNext', 'limit', 'sort', 'skip')) + ->disableOriginalConstructor() + ->getMock(); + + $resultMock + ->expects($this->any()) + ->method('limit'); + $resultMock + ->expects($this->any()) + ->method('sort'); + $resultMock + ->expects($this->any()) + ->method('skip'); + $resultMock + ->expects($this->any()) + ->method('count') + ->will($this->returnValue(10)); + $resultMock + ->expects($this->at(0)) + ->method('getNext') + ->will($this->returnValue(array('next' => 'prev', '_id' => 10))); + $resultMock + ->expects($this->at(1)) + ->method('getNext') + ->will($this->returnValue(array())); + + $this->request + ->expects($this->any()) + ->method('execute') + ->will($this->returnValue($resultMock)); + + return $this; + } +} \ No newline at end of file diff --git a/tests/model/MyDbDriver.php b/tests/model/MyDbDriver.php new file mode 100644 index 0000000..c11fb17 --- /dev/null +++ b/tests/model/MyDbDriver.php @@ -0,0 +1,60 @@ + 'MockDbDriver', 'hostname' => 'somehost', 'database' => 'db', 'username' => 'test', 'password' => '1234'); + return new MockDbDriver($conf); + } + + public function execute() + { + return true; + } + + public function fetch() + { + return true; + } + + public function fetchField($field) + { + return $field; + } + + public function fetchAll() + { + return true; + } +} \ No newline at end of file diff --git a/tests/model/MySQLiDriverTest.php b/tests/model/MySQLiDriverTest.php index c2c12a6..d7ddb51 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'; require_once dirname(__FILE__) . '/../../exception/GeneralException.php'; diff --git a/tests/model/MySQLiStatementTest.php b/tests/model/MySQLiStatementTest.php index 79e02e0..83dfc96 100644 --- a/tests/model/MySQLiStatementTest.php +++ b/tests/model/MySQLiStatementTest.php @@ -26,6 +26,8 @@ class MySQLiStatementTest extends PHPUnit_Framework_TestCase private $stmt; + private $sql; + private $testData = array( array('one' => 11, 'two' => 12), array('one' => 21, 'two' => 22), @@ -51,6 +53,65 @@ class MySQLiStatementTest extends PHPUnit_Framework_TestCase } } + public function testBindParam() + { + $val = $this->getMockBuilder('DbExpr') + ->disableOriginalConstructor() + ->getMock(); + $this->assertFalse($this->stmt->bindParam('var', $val)); + $this->assertTrue($this->stmt->bindParam('place', $val)); + } + + public function testBindParamExceptionParam() + { + $val = 1; + $this->setExpectedException('GeneralException', 'Placeholder must be an integer or string'); + $this->stmt->bindParam(array(), $val); + } + + public function testBindParamExceptionWrongObject() + { + $val = $this->getMock('NotDbExpr'); + $this->setExpectedException('GeneralException', 'Objects excepts DbExpr not allowed.'); + $this->stmt->bindParam('paa', $val); + } + + /** + * @runInSeparateProcess + */ + public function testExecute() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $this->driver + ->expects($this->any()) + ->method('quote') + ->with($this->anything()) + ->will($this->returnCallback(array($this, 'driverQuote'))); + + $this->setDriverGetConnectionMethod(); + + $result = $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val')); + $this->assertTrue($result); + } + + /** + * @runInSeparateProcess + */ + public function testExecuteNoPlaceholders() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + $this->setDriverGetConnectionMethod(); + + $this->sql = 'PLAIN SQL'; + $result = $this->stmt->execute(array()); + $this->assertTrue($result); + } + /** * @runInSeparateProcess * @group MySQL @@ -112,6 +173,49 @@ class MySQLiStatementTest extends PHPUnit_Framework_TestCase * @runInSeparateProcess * @group MySQL */ + public function testFetchPairs() + { + if (!defined('DEBUG')) { + define('DEBUG', false); + } + + $resultMock = $this->getMockBuilder('mysqli_result') + ->disableOriginalConstructor() + ->setMethods(array('fetch_array', 'close')) + ->setMockClassName('Mysqli_Result_Mock') + ->getMock(); + $resultMock + ->expects($this->at(0)) + ->method('fetch_array') + ->will($this->returnValue(array('pair', 'value'))); + $resultMock + ->expects($this->at(1)) + ->method('fetch_array') + ->will($this->returnValue(false)); + $resultMock + ->expects($this->any()) + ->method('close') + ->will($this->returnValue(TRUE)); + + $mysqliMock = $this->getMock('MysqliDrvr', array('query')); + $mysqliMock + ->expects($this->any()) + ->method('query') + ->with($this->anything()) + ->will($this->returnValue($resultMock)); + + $this->driver + ->expects($this->any()) + ->method('getConnection') + ->will($this->returnValue($mysqliMock)); + + $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val')); + $this->assertSame(array('pair' => 'value'), $this->stmt->fetchPairs()); + } + + /** + * @runInSeparateProcess + */ public function testFetchWithDebug() { if (!defined('DEBUG')) { @@ -192,7 +296,7 @@ class MySQLiStatementTest extends PHPUnit_Framework_TestCase $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val')); $this->setExpectedException('GeneralException'); - + $this->stmt->fetch(324); } @@ -226,7 +330,7 @@ class MySQLiStatementTest extends PHPUnit_Framework_TestCase ->method('query') ->with($this->anything()) ->will($this->returnValue($resultMock)); - + $this->driver ->expects($this->any()) ->method('getConnection') @@ -254,4 +358,14 @@ class MySQLiStatementTest extends PHPUnit_Framework_TestCase return $this; } + public function driverQuote($val) + { + return $val; + } + + public function dbStatementAssemble($val) + { + return $val; + } + } \ No newline at end of file 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 d480368..ba7a6d8 100644 --- a/tests/model/DbDriverTest.php +++ b/tests/model/SqlDbDriverTest.php @@ -13,23 +13,24 @@ 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'; require_once dirname(__FILE__) . '/../../exception/GeneralException.php'; -class DbDriverTest extends PHPUnit_Framework_TestCase +class SqlDbDriverTest extends PHPUnit_Framework_TestCase { private $driver; 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'); try { - $this->driver = $this->getMockForAbstractClass('DbDriver', array($conf)); + $this->driver = $this->getMockForAbstractClass('SqlDbDriver', array($conf)); } catch (GeneralException $expected) { $this->fail($expected->getMessage()); @@ -43,7 +44,7 @@ class DbDriverTest extends PHPUnit_Framework_TestCase $this->setExpectedException('GeneralException', 'Configuration must have a "username"'); - $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 80% rename from tests/model/ModelTest.php rename to tests/model/SqlModelTest.php index a71784e..5528c5f 100644 --- a/tests/model/ModelTest.php +++ b/tests/model/SqlModelTest.php @@ -16,9 +16,11 @@ require_once dirname(__FILE__) . '/../../cache/Cacher.php'; 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__) . '/MyDbDriver.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 +34,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 +43,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testModel() { - $this->assertInstanceOf('Model', $this->model); + $this->assertInstanceOf('SqlModel', $this->model); } public function testGetInsertId() @@ -83,22 +85,19 @@ 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->assertSame(' ORDER BY id DESC', $method->invoke($this->model, array('sort' => 'id', 'order' => 'desc'))); $this->assertSame(' ORDER BY name ASC', $method->invoke($this->model, array('sort' => 'name'), array('id', 'name'))); $this->assertEmpty($method->invoke($this->model, array())); - /** - * @TODO: Model::order - check DESC condition - make case insensitive - */ $this->assertSame(' ORDER BY name ASC', $method->invoke($this->model, array('sort' => 'name', 'order' => 'DESC'), array('id', 'name'))); } 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 +107,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); $key = $this->getCacheKeyMockGetSet(); @@ -117,7 +116,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); @@ -127,7 +126,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); @@ -166,7 +165,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); @@ -179,7 +178,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); @@ -189,7 +188,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); @@ -244,63 +243,4 @@ class ModelTest extends PHPUnit_Framework_TestCase return $className; } } -} - -abstract class MyDbDriver extends DbDriver -{ - public function getInsertId($table = null, $key = null) - { - return true; - } - - public function quoteIdentifier($param) - { - return $param; - } - - public function quote($param) - { - return $param; - } - - public function insert($table, $bind, $on_duplicate = array()) - { - return $table; - } - - public function update($table, $bind, $where = '') - { - return $table; - } - - public function delete($table, $where = '') - { - return $table; - } - - public function query($sql, $params = array()) - { - $conf = array('driver' => 'MockDbDriver', 'hostname' => 'localhost', 'database' => 'db', 'username' => 'test', 'password' => '1234'); - return new MockDbDriver($conf); - } - - public function execute() - { - return true; - } - - public function fetch() - { - return true; - } - - public function fetchField($field) - { - return $field; - } - - public function fetchAll() - { - return true; - } } \ No newline at end of file diff --git a/tests/session/SessionModelTest.php b/tests/session/SessionModelTest.php new file mode 100644 index 0000000..f608d04 --- /dev/null +++ b/tests/session/SessionModelTest.php @@ -0,0 +1,110 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage UnitTests + * @since 2011-11-15 + * + * Unit tests for SessionModel class + */ + +require_once dirname(__FILE__) . '/../../Registry.php'; +require_once dirname(__FILE__) . '/../../Config.php'; +require_once dirname(__FILE__) . '/../../classes/Env.class.php'; +require_once dirname(__FILE__) . '/../../model/Db.php'; +require_once dirname(__FILE__) . '/../../model/DbDriver.php'; +require_once dirname(__FILE__) . '/../model/MyDbDriver.php'; +require_once dirname(__FILE__) . '/../../model/Model.php'; +require_once dirname(__FILE__) . '/../../model/SqlModel.php'; +require_once dirname(__FILE__) . '/../../session/Session.model.php'; + +class SessionModelTest extends PHPUnit_Framework_TestCase +{ + + protected $model; + + public function setUp() + { + $conf = array('default' => array('driver' => 'MockDbDriver', 'hostname' => 'somehost', 'database' => 'db', 'username' => 'test', 'password' => '1234')); + if (!class_exists('MockDbDriver')) { + $this->getMockForAbstractClass('MyDbDriver', array($conf), 'MockDbDriver', false); + } + if (!class_exists('MockDbExpr')) { + $this->getMock('DbExpr', array(), array(), 'MockDbExpr', false); + } + + Config::set('Db', $conf); + + set_new_overload(array($this, 'newCallback')); + } + + public function testOpen() + { + $this->model = new SessionModel(); + $this->assertTrue($this->model->open('path', 'name')); + } + + public function testClose() + { + $this->model = new SessionModel(); + $this->assertTrue($this->model->close()); + } + + public function testRead() + { + $this->model = new SessionModel(); + $this->assertEquals('data', $this->model->read(1)); + } + + public function testWrite() + { + $this->model = new SessionModel(); + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $this->assertEmpty(Env::Server('HTTP_X_FORWARDED_FOR')); + + $this->assertTrue($this->model->write(2, 'user|.s:20;id=2;id=2')); + } + + public function testDestroy() + { + $this->model = new SessionModel(); + $this->assertTrue($this->model->destroy(2)); + } + public function testGc() + { + $this->model = new SessionModel(); + $this->assertTrue($this->model->gc(2000)); + } + + public function testDestroyByUserId() + { + $this->model = new SessionModel(); + $this->assertEquals('session', $this->model->destroyByUserId(12)); + } + + public function tearDown() + { + Config::getInstance()->offsetUnset('Db'); + $config = new ReflectionClass('Db'); + $registry = $config->getProperty('connections'); + $registry->setAccessible(true); + $registry->setValue('Db', array()); + unset_new_overload(); + } + + protected function newCallback($className) + { + switch ($className) { + case 'DbExpr': + return 'MockDbExpr'; + case 'MockDbDriver': + return 'MockDbDriver'; + case 'CacheKey': + return 'MockCacheKey'; + default: + return $className; + } + } +} diff --git a/uml_model.zargo b/uml_model.zargo new file mode 100644 index 0000000..a775ed3 Binary files /dev/null and b/uml_model.zargo differ diff --git a/util/AutoloadBuilder.php b/util/AutoloadBuilder.php index 4e4ff74..3f64ef6 100644 --- a/util/AutoloadBuilder.php +++ b/util/AutoloadBuilder.php @@ -20,14 +20,17 @@ class AutoloadBuilder protected $dirs; + protected $exclude; + protected $handler; protected $regex = '/(class|interface) ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/'; - public function __construct($autoload, $dirs = array()) + public function __construct($autoload, $dirs = array(), $exclude = array()) { $this->autoload = $autoload; $this->dirs = $dirs; + $this->exclude = $exclude; } public function build() @@ -42,6 +45,10 @@ class AutoloadBuilder ); foreach ($iterator as $file) { + + if($this->isExcluded($file->getRealPath())) { + continue; + } // skip non php files $e = explode('.', $file->getFileName()); if ((end($e) !== 'php') || $this->rightSubstr($file->getFileName(), 8) == 'Test.php') { @@ -68,6 +75,17 @@ class AutoloadBuilder $this->closeAutoload(); } + protected function isExcluded($file) + { + foreach ($this->exclude as $dir) { + if (stripos($file, PATH . $dir) === 0) { + return true; + } + } + return false; + } + + protected function openAutoload() { $this->write("