Skip to content
Browse files

Merge branch 'dev'

  • Loading branch information...
2 parents b057417 + d0acd17 commit 65d8feabfa24f589414365ecba1cba7e4a2335ef @nateabele nateabele committed
Showing with 1,173 additions and 273 deletions.
  1. 0 action/{readme.wiki → readme.md}
  2. +7 −1 console/lithium.php
  3. 0 console/{readme.wiki → readme.md}
  4. +9 −3 data/DocumentSchema.php
  5. +5 −13 data/Entity.php
  6. +64 −21 data/Model.php
  7. +3 −1 data/entity/Document.php
  8. +30 −4 data/model/Query.php
  9. +76 −34 data/source/Database.php
  10. +31 −13 data/source/MongoDb.php
  11. +6 −27 data/source/database/adapter/MySql.php
  12. +6 −27 data/source/database/adapter/PostgreSql.php
  13. +7 −29 data/source/database/adapter/Sqlite3.php
  14. +7 −5 data/source/mongo_db/Exporter.php
  15. 0 readme.wiki → readme.md
  16. 0 template/{readme.wiki → readme.md}
  17. +1 −0 template/view/Renderer.php
  18. +212 −0 test/Mocker.php
  19. +6 −0 tests/cases/console/RequestTest.php
  20. +43 −35 tests/cases/core/ObjectTest.php
  21. +8 −1 tests/cases/data/EntityTest.php
  22. +1 −1 tests/cases/data/ModelTest.php
  23. +29 −0 tests/cases/data/collection/DocumentSetTest.php
  24. +1 −1 tests/cases/data/entity/DocumentTest.php
  25. +1 −1 tests/cases/data/entity/RecordTest.php
  26. +22 −1 tests/cases/data/model/QueryTest.php
  27. +220 −13 tests/cases/data/source/DatabaseTest.php
  28. +74 −0 tests/cases/data/source/MongoDbTest.php
  29. +5 −0 tests/cases/data/source/database/adapter/MySqlTest.php
  30. +13 −8 tests/cases/data/source/database/adapter/PostgreSqlTest.php
  31. +12 −0 tests/cases/data/source/database/adapter/Sqlite3Test.php
  32. +55 −1 tests/cases/data/source/mongo_db/ExporterTest.php
  33. +5 −5 tests/cases/template/helper/FormTest.php
  34. +4 −2 tests/cases/template/view/RendererTest.php
  35. +118 −0 tests/cases/test/MockerTest.php
  36. +2 −2 tests/cases/util/SetTest.php
  37. +40 −3 tests/cases/util/ValidatorTest.php
  38. +21 −1 tests/mocks/core/MockCallable.php
  39. +0 −2 tests/mocks/data/MockPost.php
  40. +12 −0 tests/mocks/data/model/MockDatabase.php
  41. +2 −2 tests/mocks/data/source/database/adapter/{MockPostgreSQL.php → MockPostgreSql.php}
  42. +10 −12 util/Set.php
  43. +5 −4 util/Validator.php
View
0 action/readme.wiki → action/readme.md
File renamed without changes.
View
8 console/lithium.php
@@ -34,11 +34,17 @@
throw new ErrorException($message);
}
+ $resources = sys_get_temp_dir();
+ $templates = $resources . '/tmp/cache/templates/';
+ if (!is_dir($templates)) {
+ mkdir($resources . '/tmp/cache/templates/', 0777, true);
+ }
+
lithium\core\Libraries::add('lithium');
lithium\core\Libraries::add(basename($working), array(
'default' => true,
'path' => $working,
- 'resources' => sys_get_temp_dir()
+ 'resources' => $resources
));
};
View
0 console/readme.wiki → console/readme.md
File renamed without changes.
View
12 data/DocumentSchema.php
@@ -23,7 +23,13 @@ protected function _init() {
}
public function cast($object, $key, $data, array $options = array()) {
- $defaults = array('pathKey' => null, 'model' => null, 'wrap' => true, 'first' => false);
+ $defaults = array(
+ 'parent' => null,
+ 'pathKey' => null,
+ 'model' => null,
+ 'wrap' => true,
+ 'first' => false
+ );
$options += $defaults;
$basePathKey = $options['pathKey'];
@@ -61,7 +67,7 @@ protected function _castArray($object, $val, $pathKey, $options, $defaults) {
$numericArray = !$val || array_keys($val) === range(0, count($val) - 1);
}
- if (($isArray && !$isObject) || $numericArray) {
+ if ($isArray || ($numericArray && !$isObject)) {
$val = $valIsArray ? $val : array($val);
$class = 'set';
}
@@ -69,7 +75,7 @@ protected function _castArray($object, $val, $pathKey, $options, $defaults) {
if ($options['wrap']) {
$config = array(
'data' => $val,
- 'parent' => $object,
+ 'parent' => $options['parent'],
'model' => $options['model'],
'schema' => $this
);
View
18 data/Entity.php
@@ -182,25 +182,17 @@ public function __isset($name) {
* $record->validates();
* }}}
*
- * @see lithium\data\Model::instanceMethods
- * @param string $method
- * @param array $params
+ * @param string $method Method name caught by `__call()`.
+ * @param array $params Arguments given to the above `$method` call.
* @return mixed
*/
public function __call($method, $params) {
if ($model = $this->_model) {
- $methods = $model::instanceMethods();
array_unshift($params, $this);
-
- if (method_exists($model, $method)) {
- $class = $model::invokeMethod('_object');
- return call_user_func_array(array(&$class, $method), $params);
- }
- if (isset($methods[$method]) && is_callable($methods[$method])) {
- return call_user_func_array($methods[$method], $params);
- }
+ $class = $model::invokeMethod('_object');
+ return call_user_func_array(array(&$class, $method), $params);
}
- $message = "No model bound or unhandled method call `{$method}`.";
+ $message = "No model bound to call `{$method}`.";
throw new BadMethodCallException($message);
}
View
85 data/Model.php
@@ -8,6 +8,7 @@
namespace lithium\data;
+use lithium\core\Libraries;
use lithium\util\Set;
use lithium\util\Inflector;
use lithium\core\ConfigException;
@@ -134,7 +135,7 @@ class Model extends \lithium\core\StaticObject {
/**
* List of initialized instances.
*
- * @see lithium\data\Model::_init();
+ * @see lithium\data\Model::_initialize();
* @var array
*/
protected static $_initialized = array();
@@ -151,7 +152,7 @@ class Model extends \lithium\core\StaticObject {
*
* @var array
*/
- protected static $_classes = array(
+ protected $_classes = array(
'connections' => 'lithium\data\Connections',
'query' => 'lithium\data\model\Query',
'validator' => 'lithium\util\Validator',
@@ -324,7 +325,7 @@ class Model extends \lithium\core\StaticObject {
* @see lithium\data\Model::config()
* @var array
*/
- protected static $_autoConfig = array(
+ protected $_autoConfig = array(
'meta',
'finders',
'query',
@@ -355,7 +356,7 @@ public static function config(array $config = array()) {
}
$self = static::$_instances[$class];
- foreach (static::$_autoConfig as $key) {
+ foreach ($self->_autoConfig as $key) {
if (isset($config[$key])) {
$_key = "_{$key}";
$val = $config[$key];
@@ -375,7 +376,7 @@ public static function config(array $config = array()) {
* @param string $class The fully-namespaced class name to initialize.
* @return object Returns the initialized model instance.
*/
- protected static function _init($class) {
+ protected static function _initialize($class) {
$self = static::$_instances[$class];
if (isset(static::$_initialized[$class]) && static::$_initialized[$class]) {
@@ -388,13 +389,13 @@ protected static function _init($class) {
$meta = array();
$schema = array();
$source = array();
- $classes = static::$_classes;
+ $classes = $self->_classes;
$initializers = array();
foreach (static::_parents() as $parent) {
$parentConfig = get_class_vars($parent);
- foreach (static::$_autoConfig as $key) {
+ foreach ($self->_autoConfig as $key) {
if (isset($parentConfig["_{$key}"])) {
$val = $parentConfig["_{$key}"];
${$key} = is_array($val) ? ${$key} + $val : $val;
@@ -412,14 +413,11 @@ protected static function _init($class) {
$conn = $classes['connections']::get($tmp['connection']);
$source = (($conn) ? $conn->configureClass($class) : array()) + $source;
}
- static::$_classes = $classes;
+ $self->_classes = $classes;
$local = compact('class') + $self->_meta;
$self->_meta = ($local + $source['meta'] + $meta);
- if (is_object($schema)) {
- $schema = $schema->fields();
- }
$self->_initializers += array(
'name' => function($self) {
return basename(str_replace('\\', '/', $self));
@@ -434,7 +432,14 @@ protected static function _init($class) {
}
);
- $self->schema()->append($schema + $source['schema']);
+ if (is_object($self->_schema)) {
+ $self->_schema->append($source['schema']);
+ } elseif (is_array($self->_schema)) {
+ $self->_schema = $self->_schema + $source['schema'];
+ } else {
+ $self->_schema = $source['schema'];
+ }
+
$self->_finders += $source['finders'] + $self->_findFilters();
static::_relationsToLoad();
@@ -442,6 +447,23 @@ protected static function _init($class) {
}
/**
+ * Returns an instance of a class with given `config`. The `name` could be a key from the
+ * `classes` array, a fully-namespaced class name, or an object. Typically this method is used
+ * in `_init` to create the dependencies used in the current class.
+ *
+ * @param string|object $name A `classes` alias or fully-namespaced class name.
+ * @param array $options The configuration passed to the constructor.
+ * @return object
+ */
+ protected static function _instance($name, array $options = array()) {
+ $self = static::_object();
+ if (is_string($name) && isset($self->_classes[$name])) {
+ $name = $self->_classes[$name];
+ }
+ return Libraries::instance(null, $name, $options);
+ }
+
+ /**
* Allows the use of syntactic-sugar like `Model::all()` instead of `Model::find('all')`.
*
* @see lithium\data\Model::find()
@@ -484,6 +506,24 @@ public static function __callStatic($method, $params) {
}
/**
+ * Magic method that allows calling `Model::_instanceMethods`'s closure like normal methods
+ * on the model instance.
+ *
+ * @see lithium\data\Model::instanceMethods
+ * @param string $method Method name caught by `__call()`.
+ * @param array $params Arguments given to the above `$method` call.
+ * @return mixed
+ */
+ public function __call($method, $params) {
+ $methods = static::instanceMethods();
+ if (isset($methods[$method]) && is_callable($methods[$method])) {
+ return call_user_func_array($methods[$method], $params);
+ }
+ $message = "Unhandled method call `{$method}`.";
+ throw new BadMethodCallException($message);
+ }
+
+ /**
* The `find` method allows you to retrieve data from the connected data source.
*
* Examples:
@@ -660,9 +700,11 @@ public static function key($values = null) {
return $key;
}
+ $self = static::_object();
+ $entity = $self->_classes['entity'];
if (is_object($values) && is_string($key)) {
- return static::_key($key, $values);
- } elseif ($values instanceof static::$_classes['entity']) {
+ return static::_key($key, $values, $entity);
+ } elseif ($values instanceof $entity) {
$values = $values->to('array');
}
@@ -687,13 +729,14 @@ public static function key($values = null) {
* @see lithium\data\Model::key()
* @param string $key The key
* @param object $values Object with attributes.
+ * @param string $entity The fully-namespaced entity class name.
* @return mixed The key value array or `null` if the `$values` object has no attribute
* named `$key`
*/
- protected static function _key($key, $values) {
+ protected static function _key($key, $values, $entity) {
if (isset($values->$key)) {
return array($key => $values->$key);
- } elseif (!$values instanceof static::$_classes['entity']) {
+ } elseif (!$values instanceof $entity) {
return array($key => $values);
}
return null;
@@ -804,7 +847,7 @@ public static function schema($field = null) {
if (!is_object($self->_schema)) {
$self->_schema = static::connection()->describe(
- $self::meta('source'), $self->_schema, $self::meta()
+ $self::meta('source'), $self->_schema, $self->_meta
);
if (!is_object($self->_schema)) {
$class = get_called_class();
@@ -982,7 +1025,7 @@ public static function instanceMethods(array $methods = null) {
public function save($entity, $data = null, array $options = array()) {
$self = static::_object();
$_meta = array('model' => get_called_class()) + $self->_meta;
- $_schema = $self->_schema;
+ $_schema = $self->schema();
$defaults = array(
'validate' => true,
@@ -1076,7 +1119,7 @@ public function validates($entity, array $options = array()) {
);
$options += $defaults;
$self = static::_object();
- $validator = static::$_classes['validator'];
+ $validator = $self->_classes['validator'];
$params = compact('entity', 'options');
$filter = function($parent, $params) use (&$self, $validator) {
@@ -1175,7 +1218,7 @@ public static function remove($conditions = array(), array $options = array()) {
*/
public static function &connection() {
$self = static::_object();
- $connections = static::$_classes['connections'];
+ $connections = $self->_classes['connections'];
$name = isset($self->_meta['connection']) ? $self->_meta['connection'] : null;
if ($conn = $connections::get($name)) {
@@ -1233,7 +1276,7 @@ protected static function &_object() {
static::$_instances[$class] = new $class();
static::config();
}
- $object = static::_init($class);
+ $object = static::_initialize($class);
return $object;
}
View
4 data/entity/Document.php
@@ -137,6 +137,7 @@ public function &__get($name) {
'class' => 'set',
'schema' => $this->schema(),
'pathKey' => $this->_pathKey ? $this->_pathKey . '.' . $name : $name,
+ 'parent' => $this,
'model' => $this->_model
));
return $this->_updated[$name];
@@ -319,7 +320,8 @@ public function set(array $data, array $options = array()) {
if ($cast) {
$pathKey = $this->_pathKey;
$model = $this->_model;
- $val = $schema->cast($this, $key, $val, compact('pathKey', 'model'));
+ $parent = $this;
+ $val = $schema->cast($this, $key, $val, compact('pathKey', 'model', 'parent'));
}
if ($val instanceof self) {
$val->_exists = $options['init'] && $this->_exists;
View
34 data/model/Query.php
@@ -98,7 +98,7 @@ class Query extends \lithium\core\Object {
protected $_alias = array();
/**
- * Map the generated aliases to their corresponding relation path
+ * Map beetween generated aliases and corresponding relation paths
*
* @see lithium\data\model\Query::alias()
*
@@ -107,7 +107,16 @@ class Query extends \lithium\core\Object {
protected $_paths = array();
/**
- * Map the generated aliases to their corresponding model
+ * Map beetween relation paths and their corresponding fieldname paths
+ *
+ * @see lithium\data\model\Query::alias()
+ *
+ * @var array
+ */
+ protected $_relationNames = array();
+
+ /**
+ * Map beetween generated aliases and corresponding models.
*
* @see lithium\data\model\Query::alias()
*
@@ -720,23 +729,40 @@ public function alias($alias = true, $relpath = null) {
}
$this->_paths[$alias] = $relpath;
+ $fieldname = array();
foreach ($paths as $path) {
if (!$relation = $model::relations($path)) {
$model = null;
break;
}
+ $fieldname[] = $relation->fieldName();
$model = $relation->to();
}
$this->_models[$alias] = $model;
+ $this->_relationNames[$relpath] = join('.', $fieldname);
return $alias;
}
/**
+ * Return the relation paths mapped to their corredponding fieldname paths.
+ *
+ * @param object $source Instance of the data source (`lithium\data\Source`) to use for
+ * conversion.
+ * @return array Map between relation paths and their corresponding fieldname paths.
+ */
+ public function relationNames(Source $source = null) {
+ if ($source) {
+ $this->applyStrategy($source);
+ }
+ return $this->_relationNames;
+ }
+
+ /**
* Return the generated aliases mapped to their relation path
*
* @param object $source Instance of the data source (`lithium\data\Source`) to use for
* conversion.
- * @return array Map between alias and their corresponding dotted relation
+ * @return array Map between aliases and their corresponding dotted relation paths.
*/
public function paths(Source $source = null) {
if ($source) {
@@ -750,7 +776,7 @@ public function paths(Source $source = null) {
*
* @param object $source Instance of the data source (`lithium\data\Source`) to use for
* conversion.
- * @return array Map between alias and their corresponding model
+ * @return array Map between aliases and their corresponding fully-namespaced model names.
*/
public function models(Source $source = null) {
if ($source) {
View
110 data/source/Database.php
@@ -182,7 +182,7 @@ public function __construct(array $config = array()) {
$with = $context->with();
- $strategy = function($me, $model, $tree, $path, $from, $needPks) use ($self, $context, $with) {
+ $strategy = function($me, $model, $tree, $path, $from, &$deps) use ($self, $context, $with) {
foreach ($tree as $name => $childs) {
if (!$rel = $model::relations($name)) {
throw new QueryException("Model relationship `{$name}` not found.");
@@ -190,9 +190,9 @@ public function __construct(array $config = array()) {
$constraints = array();
$alias = $name;
- if (isset($with[$path])) {
-
- list($unallowed, $allowed) = Set::slice($with[$path], array(
+ $relPath = $path ? $path . '.' . $name : $name;
+ if (isset($with[$relPath])) {
+ list($unallowed, $allowed) = Set::slice($with[$relPath], array(
'alias',
'constraints'
));
@@ -201,16 +201,15 @@ public function __construct(array $config = array()) {
$message .= "`'with'` using the `'joined'` strategy.";
throw new QueryException($message);
}
- extract($with[$path]);
+ extract($with[$relPath]);
}
- $to = $context->alias($alias, $path);
+ $to = $context->alias($alias, $relPath);
- if ($needPks) {
- $context->fields(array($to => (array) $model::meta('key')));
- }
+ $deps[$to] = $deps[$from];
+ $deps[$to][] = $from;
- if ($context->relationships($path) === null) {
- $context->relationships($path, array(
+ if ($context->relationships($relPath) === null) {
+ $context->relationships($relPath, array(
'type' => $rel->type(),
'model' => $rel->to(),
'fieldName' => $rel->fieldName(),
@@ -221,19 +220,27 @@ public function __construct(array $config = array()) {
}
if (!empty($childs)) {
- $me($me, $rel->to(), $childs, "{$path}." . key($childs), $to, $needPks);
+ $me($me, $rel->to(), $childs, $relPath, $to, $deps);
}
}
};
$tree = Set::expand(Set::normalize(array_keys($with)));
$alias = $context->alias();
- $needPks = false;
- if ($context->fields()) {
- $needPks = true;
- $context->fields(array($alias => (array) $model::meta('key')));
+ $deps = array($alias => array());
+ $strategy($strategy, $model, $tree, '', $alias, $deps);
+
+ $models = $context->models();
+ foreach ($context->fields() as $field) {
+ list($alias, $field) = $self->invokeMethod('_splitFieldname', array($field));
+ $alias = $alias ?: $field;
+ if ($alias && isset($models[$alias])) {
+ foreach ($deps[$alias] as $depAlias) {
+ $depModel = $models[$depAlias];
+ $context->fields(array($depAlias => (array) $depModel::meta('key')));
+ }
+ }
}
- $strategy($strategy, $model, $tree, key($tree), $context->alias(), $needPks);
},
'nested' => function($self, $model, $context) {
throw new QueryException("This strategy is not yet implemented.");
@@ -322,7 +329,7 @@ public function name($name) {
*/
protected function _splitFieldname($field) {
if (is_string($field)) {
- if (preg_match('/^[a-z0-9_-]+\.[a-z0-9_-]+$/i', $field)) {
+ if (preg_match('/^[a-z0-9_-]+\.([a-z0-9_-]+|\*)$/i', $field)) {
return explode('.', $field, 2);
}
}
@@ -465,18 +472,30 @@ public function read($query, array $options = array()) {
return $result;
case 'array':
$columns = $args['schema'] ?: $self->schema($query, $result);
- $records = array();
- if (is_array(reset($columns))) {
- $columns = reset($columns);
+
+ if (!isset($columns['']) || !is_array($columns[''])) {
+ $columns = array('' => $columns);
}
- while ($data = $result->next()) {
- // @hack: Fix this to support relationships
- if (count($columns) != count($data) && is_array(current($columns))) {
- $columns = current($columns);
+
+ $relationNames = is_object($query) ? $query->relationNames($self) : array();
+ $i = 0;
+ $records = array();
+ foreach ($result as $data) {
+ $offset = 0;
+ $records[$i] = array();
+ foreach ($columns as $path => $cols) {
+ $len = count($cols);
+ $values = array_combine($cols, array_slice($data, $offset, $len));
+ if ($path) {
+ $records[$i][$relationNames[$path]] = $values;
+ } else {
+ $records[$i] += $values;
+ }
+ $offset += $len;
}
- $records[] = array_combine($columns, $data);
+ $i++;
}
- return $records;
+ return Set::expand($records);
case 'item':
return $self->item($query->model(), array(), compact('query', 'result') + array(
'class' => 'set'
@@ -672,8 +691,19 @@ public function renderCommand($type, $data = null, $context = null) {
* @param object $context
*/
public function schema($query, $resource = null, $context = null) {
- $query->applyStrategy($this);
- return $this->_schema($query, $this->_fields($query->fields(), $query));
+ if (is_object($query)) {
+ $query->applyStrategy($this);
+ return $this->_schema($query, $this->_fields($query->fields(), $query));
+ }
+
+ $result = array();
+ $count = $resource->resource()->columnCount();
+
+ for ($i = 0; $i < $count; $i++) {
+ $meta = $resource->resource()->getColumnMeta($i);
+ $result[] = $meta['name'];
+ }
+ return $result;
}
/**
@@ -686,6 +716,7 @@ public function _schema($query, $fields = null) {
$model = $query->model();
$paths = $query->paths($this);
$models = $query->models($this);
+ $alias = $query->alias();
$result = array();
if (!$model) {
@@ -707,8 +738,8 @@ public function _schema($query, $fields = null) {
}
$unalias = function ($value) {
- if (!is_string($value)) {
- return $value;
+ if (is_object($value) && isset($value->scalar)) {
+ $value = $value->scalar;
}
$aliasing = preg_split("/\s+as\s+/i", $value);
return isset($aliasing[1]) ? $aliasing[1] : $value;
@@ -719,6 +750,8 @@ public function _schema($query, $fields = null) {
unset($fields[0]);
}
+ $fields = isset($fields[$alias]) ? array($alias => $fields[$alias]) + $fields : $fields;
+
foreach ($fields as $field => $value) {
if (is_array($value)) {
if (isset($value['*'])) {
@@ -890,6 +923,7 @@ public function _processConditions($key, $value, $context, $schema = null, $glue
public function fields($fields, $context) {
$type = $context->type();
$schema = $context->schema()->fields();
+ $alias = $context->alias();
if (!is_array($fields)) {
return $this->_fieldsReturn($type, $context, $fields, $schema);
@@ -907,6 +941,8 @@ public function fields($fields, $context) {
unset($fields[0]);
}
+ $fields = isset($fields[$alias]) ? array($alias => $fields[$alias]) + $fields : $fields;
+
foreach ($fields as $field => $value) {
if (is_array($value)) {
if (isset($value['*'])) {
@@ -954,6 +990,7 @@ protected function _fields($fields, $context) {
}
return $list;
}
+
protected function _fieldsQuote($alias, $field) {
$open = $this->_quotes[0];
$close = $this->_quotes[1];
@@ -962,14 +999,19 @@ protected function _fieldsQuote($alias, $field) {
list($aliasname, $fieldname) = $this->_splitFieldname($aliasing[0]);
$alias = $aliasname ? : $alias;
return "{$open}{$alias}{$close}.{$open}{$fieldname}{$close} as {$aliasing[1]}";
- } else {
+ } elseif ($alias) {
return "{$open}{$alias}{$close}.{$open}{$field}{$close}";
+ } else {
+ return "{$open}{$field}{$close}";
}
}
protected function _fieldsReturn($type, $context, $fields, $schema) {
if ($type == 'create' || $type == 'update') {
$data = $context->data();
+ if (isset($data['data']) && is_array($data['data']) && count($data) == 1){
+ $data = $data['data'];
+ }
if ($fields && is_array($fields) && is_int(key($fields))) {
$data = array_intersect_key($data, array_combine($fields, $fields));
@@ -992,9 +1034,9 @@ public function limit($limit, $context) {
return;
}
if ($offset = $context->offset() ?: '') {
- $offset .= ', ';
+ $offset = " OFFSET {$offset}";
}
- return "LIMIT {$offset}{$limit}";
+ return "LIMIT {$limit}{$offset}";
}
/**
View
44 data/source/MongoDb.php
@@ -8,7 +8,6 @@
namespace lithium\data\source;
-use Mongo;
use MongoCode;
use MongoRegex;
use lithium\util\Inflector;
@@ -65,7 +64,8 @@ class MongoDb extends \lithium\data\Source {
'result' => 'lithium\data\source\mongo_db\Result',
'schema' => 'lithium\data\source\mongo_db\Schema',
'exporter' => 'lithium\data\source\mongo_db\Exporter',
- 'relationship' => 'lithium\data\model\Relationship'
+ 'relationship' => 'lithium\data\model\Relationship',
+ 'server' => 'Mongo'
);
/**
@@ -137,8 +137,12 @@ class MongoDb extends \lithium\data\Source {
* information for a model class. See the `$_schema` property for more information.
* - `'gridPrefix'` _string_: The default prefix for MongoDB's `chunks` and `files`
* collections. Defaults to `'fs'`.
- * - `'replicaSet'` _boolean_: See the documentation for `Mongo::__construct()`. Defaults
+ * - `'replicaSet'` _string_: See the documentation for `Mongo::__construct()`. Defaults
* to `false`.
+ * - `'readPreference'` _mixed_: May either be a single value such as Mongo::RP_NEAREST,
+ * or an array containing a read preference and a tag set such as:
+ * array(Mongo::RP_SECONDARY_PREFERRED, array('dc' => 'east) See the documentation for
+ * `Mongo::setReadPreference()`. Defaults to null.
*
* Typically, these parameters are set in `Connections::add()`, when adding the adapter to the
* list of active connections.
@@ -146,8 +150,9 @@ class MongoDb extends \lithium\data\Source {
public function __construct(array $config = array()) {
$host = 'localhost:27017';
- if (class_exists('Mongo', false)) {
- $host = Mongo::DEFAULT_HOST . ':' . Mongo::DEFAULT_PORT;
+ $server = $this->_classes['server'];
+ if (class_exists($server, false)) {
+ $host = $server::DEFAULT_HOST . ':' . $server::DEFAULT_PORT;
}
$defaults = compact('host') + array(
'persistent' => false,
@@ -157,7 +162,9 @@ public function __construct(array $config = array()) {
'timeout' => 100,
'replicaSet' => false,
'schema' => null,
- 'gridPrefix' => 'fs'
+ 'gridPrefix' => 'fs',
+ 'safe' => false,
+ 'readPreference' => null
);
parent::__construct($config + $defaults);
}
@@ -248,11 +255,17 @@ public function connect() {
if ($persist = $cfg['persistent']) {
$options['persist'] = $persist === true ? 'default' : $persist;
}
- $this->server = new Mongo($connection, $options);
+ $server = $this->_classes['server'];
+ $this->server = new $server($connection, $options);
if ($this->connection = $this->server->{$cfg['database']}) {
$this->_isConnected = true;
}
+
+ if ($prefs = $cfg['readPreference']) {
+ $prefs = !is_array($prefs) ? array($prefs, array()) : $prefs;
+ $this->server->setReadPreference($prefs[0], $prefs[1]);
+ }
} catch (Exception $e) {
throw new NetworkException("Could not connect to the database.", 503, $e);
}
@@ -361,12 +374,12 @@ public function schema($query, $resource = null, $context = null) {
* @filter
*/
public function create($query, array $options = array()) {
- $defaults = array('safe' => false, 'fsync' => false);
+ $_config = $this->_config;
+ $defaults = array('safe' => $_config['safe'], 'fsync' => false);
$options += $defaults;
$this->_checkConnection();
$params = compact('query', 'options');
- $_config = $this->_config;
$_exp = $this->_classes['exporter'];
return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config, $_exp) {
@@ -491,12 +504,17 @@ protected function _group($group, $args, $options) {
* @filter
*/
public function update($query, array $options = array()) {
- $defaults = array('upsert' => false, 'multiple' => true, 'safe' => false, 'fsync' => false);
+ $_config = $this->_config;
+ $defaults = array(
+ 'upsert' => false,
+ 'multiple' => true,
+ 'safe' => $_config['safe'],
+ 'fsync' => false
+ );
$options += $defaults;
$this->_checkConnection();
$params = compact('query', 'options');
- $_config = $this->_config;
$_exp = $this->_classes['exporter'];
return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config, $_exp) {
@@ -536,9 +554,9 @@ public function update($query, array $options = array()) {
*/
public function delete($query, array $options = array()) {
$this->_checkConnection();
- $defaults = array('justOne' => false, 'safe' => false, 'fsync' => false);
- $options = array_intersect_key($options + $defaults, $defaults);
$_config = $this->_config;
+ $defaults = array('justOne' => false, 'safe' => $_config['safe'], 'fsync' => false);
+ $options = array_intersect_key($options + $defaults, $defaults);
$params = compact('query', 'options');
return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config) {
View
33 data/source/database/adapter/MySql.php
@@ -156,7 +156,7 @@ public function sources($model = null) {
* @param mixed $entity Specifies the table name for which the schema should be returned, or
* the class name of the model object requesting the schema, in which case the model
* class will be queried for the correct table name.
- * @param array $schema Any schema data pre-defined by the model.
+ * @param array $fields Any schema data pre-defined by the model.
* @param array $meta
* @return array Returns an associative array describing the given table's schema, where the
* array keys are the available fields, and the values are arrays describing each
@@ -164,11 +164,14 @@ public function sources($model = null) {
* - `'type'`: The field type name
* @filter This method can be filtered.
*/
- public function describe($entity, $schema = array(), array $meta = array()) {
- $params = compact('entity', 'meta');
+ public function describe($entity, $fields = array(), array $meta = array()) {
+ $params = compact('entity', 'meta', 'fields');
return $this->_filter(__METHOD__, $params, function($self, $params) {
extract($params);
+ if ($fields) {
+ return $self->invokeMethod('_instance', array('schema', compact('fields')));
+ }
$name = $self->invokeMethod('_entityName', array($entity, array('quoted' => true)));
$columns = $self->read("DESCRIBE {$name}", array('return' => 'array', 'schema' => array(
'field', 'type', 'null', 'key', 'default', 'extra'
@@ -228,30 +231,6 @@ public function value($value, array $schema = array()) {
}
/**
- * In cases where the query is a raw string (as opposed to a `Query` object), to database must
- * determine the correct column names from the result resource.
- *
- * @param mixed $query
- * @param resource $resource
- * @param object $context
- * @return array
- */
- public function schema($query, $resource = null, $context = null) {
- if (is_object($query)) {
- return parent::schema($query, $resource, $context);
- }
-
- $result = array();
- $count = $resource->resource()->columnCount();
-
- for ($i = 0; $i < $count; $i++) {
- $meta = $resource->resource()->getColumnMeta($i);
- $result[] = $meta['name'];
- }
- return $result;
- }
-
- /**
* Retrieves database error message and error code.
*
* @return array
View
33 data/source/database/adapter/PostgreSql.php
@@ -165,7 +165,7 @@ public function sources($model = null) {
* @param mixed $entity Specifies the table name for which the schema should be returned, or
* the class name of the model object requesting the schema, in which case the model
* class will be queried for the correct table name.
- * @param array $schema Any schema data pre-defined by the model.
+ * @param array $fields Any schema data pre-defined by the model.
* @param array $meta
* @return array Returns an associative array describing the given table's schema, where the
* array keys are the available fields, and the values are arrays describing each
@@ -173,12 +173,15 @@ public function sources($model = null) {
* - `'type'`: The field type name
* @filter This method can be filtered.
*/
- public function describe($entity, $schema = array(), array $meta = array()) {
+ public function describe($entity, $fields = array(), array $meta = array()) {
$schema = $this->_config['schema'];
- $params = compact('entity', 'meta', 'schema');
+ $params = compact('entity', 'meta', 'fields', 'schema');
return $this->_filter(__METHOD__, $params, function($self, $params) {
extract($params);
+ if ($fields) {
+ return $self->invokeMethod('_instance', array('schema', compact('fields')));
+ }
$name = $self->connection->quote($self->invokeMethod('_entityName', array($entity)));
$schema = $self->connection->quote($schema);
@@ -272,30 +275,6 @@ public function value($value, array $schema = array()) {
}
/**
- * In cases where the query is a raw string (as opposed to a `Query` object), to database must
- * determine the correct column names from the result resource.
- *
- * @param mixed $query
- * @param resource $resource
- * @param object $context
- * @return array
- */
- public function schema($query, $resource = null, $context = null) {
- if (is_object($query)) {
- return parent::schema($query, $resource, $context);
- }
-
- $result = array();
- $count = $resource->resource()->columnCount();
-
- for ($i = 0; $i < $count; $i++) {
- $meta = $resource->resource()->getColumnMeta($i);
- $result[] = $meta['name'];
- }
- return $result;
- }
-
- /**
* Retrieves database error message and error code.
*
* @return array
View
36 data/source/database/adapter/Sqlite3.php
@@ -166,7 +166,7 @@ public function sources($model = null) {
* @param mixed $entity Specifies the table name for which the schema should be returned, or
* the class name of the model object requesting the schema, in which case the model
* class will be queried for the correct table name.
- * @param array $schema Any schema data pre-defined by the model.
+ * @param array $fields Any schema data pre-defined by the model.
* @param array $meta
* @return array Returns an associative array describing the given table's schema, where the
* array keys are the available fields, and the values are arrays describing each
@@ -174,13 +174,15 @@ public function sources($model = null) {
* - `'type'`: The field type name
* @filter This method can be filtered.
*/
- public function describe($entity, $schema = array(), array $meta = array()) {
- $params = compact('entity', 'meta');
+ public function describe($entity, $fields = array(), array $meta = array()) {
+ $params = compact('entity', 'meta', 'fields');
$regex = $this->_regex;
return $this->_filter(__METHOD__, $params, function($self, $params) use ($regex) {
- $entity = $params['entity'];
- $meta = $params['meta'];
+ extract($params);
+ if ($fields) {
+ return $self->invokeMethod('_instance', array('schema', compact('fields')));
+ }
$name = $self->invokeMethod('_entityName', array($entity, array('quoted' => true)));
$columns = $self->read("PRAGMA table_info({$name})", array('return' => 'array'));
$fields = array();
@@ -236,30 +238,6 @@ public function encoding($encoding = null) {
}
/**
- * In cases where the query is a raw string (as opposed to a `Query` object), to database must
- * determine the correct column names from the result resource.
- *
- * @param mixed $query
- * @param resource $resource
- * @param object $context
- * @return object
- */
- public function schema($query, $resource = null, $context = null) {
- if (is_object($query)) {
- return parent::schema($query, $resource, $context);
- }
-
- $result = array();
- $count = $resource->resource()->columnCount();
-
- for ($i = 0; $i < $count; $i++) {
- $meta = $resource->resource()->getColumnMeta($i);
- $result[] = $meta['name'];
- }
- return $result;
- }
-
- /**
* Retrieves database error message and error code.
*
* @return array
View
12 data/source/mongo_db/Exporter.php
@@ -110,15 +110,17 @@ protected static function _update($export) {
$original = $export['data'];
$isArray = is_object($value) && get_class($value) == static::$_classes['set'];
- $options = array('handlers' => array(
- 'MongoDate' => function($value) { return $value; },
- 'MongoId' => function($value) { return $value; }
- ));
+ $options = array(
+ 'indexed' => null,
+ 'handlers' => array(
+ 'MongoDate' => function($value) { return $value; },
+ 'MongoId' => function($value) { return $value; }
+ )
+ );
if ($isArray) {
$newValue = $value->to('array', $options);
$originalValue = null;
-
if (isset($original[$key])) {
$originalValue = $original[$key]->to('array', $options);
}
View
0 readme.wiki → readme.md
File renamed without changes.
View
0 template/readme.wiki → template/readme.md
File renamed without changes.
View
1 template/view/Renderer.php
@@ -202,6 +202,7 @@ protected function _init() {
},
'options' => '_attributes',
'title' => 'escape',
+ 'value' => 'escape',
'scripts' => function($scripts) use (&$context) {
return "\n\t" . join("\n\t", $context['scripts']) . "\n";
},
View
212 test/Mocker.php
@@ -0,0 +1,212 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2012, Union of RAD (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\test;
+
+use lithium\util\String;
+use ReflectionClass;
+use ReflectionMethod;
+use Reflection;
+
+/**
+ * The Mocker class aids in the creation of Mocks on the fly.
+ */
+class Mocker {
+
+ /**
+ * A list of code to be generated based on the type.
+ *
+ * @var array
+ */
+ protected static $_dynamicCode = array(
+ 'startClass' => array(
+ 'namespace {:namespace};',
+ 'class {:mockee} extends \{:mocker} {'
+ ),
+ 'staticMethod' => array(
+ '{:modifiers} function {:name}({:params}) {',
+ ' $params = func_get_args();',
+ ' list($class, $method) = explode(\'::\', __METHOD__, 2);',
+ ' $parent = \'parent::\' . $method;',
+ ' $result = call_user_func_array($parent, $params);',
+ ' return self::_filter($method, $params, function($self, $params) use(&$result) {',
+ ' return $result;',
+ ' });',
+ '}',
+ ),
+ 'method' => array(
+ '{:modifiers} function {:name}({:params}) {',
+ ' $params = func_get_args();',
+ ' list($class, $method) = explode(\'::\', __METHOD__, 2);',
+ ' $parent = \'parent::\' . $method;',
+ ' $result = call_user_func_array($parent, $params);',
+ ' return $this->_filter($parent, $params, function($self, $params) use(&$result) {',
+ ' return $result;',
+ ' });',
+ '}',
+ ),
+ 'endClass' => array(
+ '}',
+ ),
+ );
+
+ /**
+ * A list of methods we should not overwrite in our mock class.
+ *
+ * @var array
+ */
+ protected static $_blackList = array(
+ '__construct', '__destruct', '__call', '__callStatic',
+ '__get', '__set', '__isset', '__unset', '__sleep',
+ '__wakeup', '__toString', '__clone', '__invoke',
+ '__construct', '_init', 'applyFilter', 'invokeMethod',
+ '__set_state', '_instance', '_filter', '_parents',
+ '_stop',
+ );
+
+ /**
+ * Will register this class into the autoloader.
+ *
+ * @return void
+ */
+ public static function register() {
+ spl_autoload_register(array(__CLASS__, 'create'));
+ }
+
+ /**
+ * The main entrance to create a new Mock class.
+ *
+ * @param string $mockee The fully namespaced `\Mock` class
+ * @return void
+ */
+ public static function create($mockee) {
+ if (!self::_validateMockee($mockee)) {
+ return;
+ }
+
+ $mocker = self::_mocker($mockee);
+
+ $code = self::_dynamicCode('startClass', array(
+ 'namespace' => self::_namespace($mockee),
+ 'mocker' => $mocker,
+ 'mockee' => 'Mock',
+ ));
+
+ $reflectedClass = new ReflectionClass($mocker);
+ $reflecedMethods = $reflectedClass->getMethods();
+ foreach ($reflecedMethods as $method) {
+ if (!in_array($method->name, self::$_blackList)) {
+ $key = $method->isStatic() ? 'staticMethod' : 'method';
+ $code .= self::_dynamicCode($key, array(
+ 'name' => $method->name,
+ 'modifiers' => self::_methodModifiers($method),
+ 'params' => self::_methodParams($method),
+ 'visibility' => 'public',
+ ));
+ }
+ }
+
+ $code .= self::_dynamicCode('endClass');
+
+ eval($code);
+ }
+
+ /**
+ * Will determine what method mofifiers of a method.
+ *
+ * For instance: 'public static' or 'private abstract'
+ *
+ * @param ReflectionMethod $method
+ * @return string
+ */
+ protected static function _methodModifiers(ReflectionMethod $method) {
+ $modifierKey = $method->getModifiers();
+ $modifierArray = Reflection::getModifierNames($modifierKey);
+ return implode(' ', $modifierArray);
+ }
+
+ /**
+ * Will determine what parameter prototype of a method.
+ *
+ * For instance: 'ReflectionMethod $method' or '$name, array $foo = null'
+ *
+ * @param ReflectionMethod $method
+ * @return string
+ */
+ protected static function _methodParams(ReflectionMethod $method) {
+ $pattern = '/Parameter #[0-9]+ \[ [^\>]+>([^\]]+) \]/';
+ $replace = array(
+ 'from' => array('Array', 'or NULL'),
+ 'to' => array('array()', ''),
+ );
+ preg_match_all($pattern, $method, $matches);
+ $params = implode(', ', $matches[1]);
+ return str_replace($replace['from'], $replace['to'], $params);
+ }
+
+ /**
+ * Will generate the code you are wanting.
+ *
+ * @param string $key The key from self::$_dynamicCode
+ * @param array $tokens Tokens, if any, that should be inserted
+ * @return string
+ */
+ protected static function _dynamicCode($key, $tokens = array()) {
+ $code = implode("\n", self::$_dynamicCode[$key]);
+ return String::insert($code, $tokens) . "\n";
+ }
+
+ /**
+ * Will generate the mocker from the current mockee.
+ *
+ * @param string $mockee The fully namespaced `\Mock` class
+ * @return array
+ */
+ protected static function _mocker($mockee) {
+ $matches = array();
+ preg_match_all('/^(.*)\\\\([^\\\\]+)\\\\Mock$/', $mockee, $matches);
+ if (!isset($matches[1][0])) {
+ return;
+ }
+ return $matches[1][0] . '\\' . ucfirst($matches[2][0]);
+ }
+
+ /**
+ * Will generate the namespace from the current mockee.
+ *
+ * @param string $mockee The fully namespaced `\Mock` class
+ * @return string
+ */
+ protected static function _namespace($mockee) {
+ $matches = array();
+ preg_match_all('/^(.*)\\\\Mock$/', $mockee, $matches);
+ return isset($matches[1][0]) ? $matches[1][0] : null;
+ }
+
+ /**
+ * Will validate if mockee is a valid class we should mock.
+ *
+ * @param string $mockee The fully namespaced `\Mock` class
+ * @return bool
+ */
+ protected static function _validateMockee($mockee) {
+ if (class_exists($mockee) || preg_match('/\\\\Mock$/', $mockee) !== 1) {
+ return false;
+ }
+ $mocker = self::_mocker($mockee);
+ $isObject = is_subclass_of($mocker, 'lithium\core\Object');
+ $isStatic = is_subclass_of($mocker, 'lithium\core\StaticObject');
+ if (!$isObject && !$isStatic) {
+ return false;
+ }
+ return true;
+ }
+
+}
+
+?>
View
6 tests/cases/console/RequestTest.php
@@ -175,6 +175,12 @@ public function testShiftTwo() {
$result = $request->params;
$this->assertEqual($expected, $result);
}
+
+ public function testTemporaryFileStructureExists() {
+ $resources = Libraries::get(true, 'resources');
+ $template = $resources . '/tmp/cache/templates/';
+ $this->assert(is_dir($template));
+ }
}
?>
View
78 tests/cases/core/ObjectTest.php
@@ -8,7 +8,6 @@
namespace lithium\tests\cases\core;
-use lithium\core\Object;
use lithium\tests\mocks\core\MockRequest;
use lithium\tests\mocks\core\MockMethodFiltering;
use lithium\tests\mocks\core\MockExposed;
@@ -70,8 +69,6 @@ public function testMethodFiltering() {
/**
* Verifies workaround for accessing protected properties in filtered methods.
- *
- * @return void
*/
function testFilteringWithProtectedAccess() {
$object = new MockExposed();
@@ -82,8 +79,6 @@ function testFilteringWithProtectedAccess() {
/**
* Attaches a single filter to multiple methods.
- *
- * @return void
*/
function testMultipleMethodFiltering() {
$object = new MockMethodFiltering();
@@ -98,55 +93,70 @@ function testMultipleMethodFiltering() {
/**
* Tests that the correct parameters are always passed in Object::invokeMethod(), regardless of
* the number.
- *
- * @return void
*/
public function testMethodInvocationWithParameters() {
$callable = new MockCallable();
- $this->assertEqual($callable->invokeMethod('foo'), array());
- $this->assertEqual($callable->invokeMethod('foo', array('bar')), array('bar'));
+ $result = $callable->invokeMethod('foo');
+ $this->assertEqual($result['method'], 'foo');
+ $this->assertEqual($result['params'], array());
+
+ $expected = array('bar');
+ $result = $callable->invokeMethod('foo', $expected);
+ $this->assertEqual($result['method'], 'foo');
+ $this->assertEqual($result['params'], $expected);
+
+ $expected = array('one', 'two');
+ $result = $callable->invokeMethod('foo', $expected);
+ $this->assertEqual($result['method'], 'foo');
+ $this->assertEqual($result['params'], $expected);
+
+ $expected = array('short', 'parameter', 'list');
+ $result = $callable->invokeMethod('foo', $expected);
+ $this->assertEqual($result['method'], 'foo');
+ $this->assertEqual($result['params'], $expected);
+
+ $expected = array('a', 'longer', 'parameter', 'list');
+ $result = $callable->invokeMethod('foo', $expected);
+ $this->assertEqual($result['method'], 'foo');
+ $this->assertEqual($result['params'], $expected);
+
+ $expected = array('a', 'much', 'longer', 'parameter', 'list');
+ $result = $callable->invokeMethod('foo', $expected);
+ $this->assertEqual($result['method'], 'foo');
+ $this->assertEqual($result['params'], $expected);
+
+ $expected = array('an', 'extremely', 'long', 'list', 'of', 'parameters');
+ $result = $callable->invokeMethod('foo', $expected);
+ $this->assertEqual($result['method'], 'foo');
+ $this->assertEqual($result['params'], $expected);
+
+ $expected = array('an', 'extremely', 'long', 'list', 'of', 'parameters');
+ $result = $callable->invokeMethod('bar', $expected);
+ $this->assertEqual($result['method'], 'bar');
+ $this->assertEqual($result['params'], $expected);
- $params = array('one', 'two');
- $this->assertEqual($callable->invokeMethod('foo', $params), $params);
-
- $params = array('short', 'parameter', 'list');
- $this->assertEqual($callable->invokeMethod('foo', $params), $params);
-
- $params = array('a', 'longer', 'parameter', 'list');
- $this->assertEqual($callable->invokeMethod('foo', $params), $params);
-
- $params = array('a', 'much', 'longer', 'parameter', 'list');
- $this->assertEqual($callable->invokeMethod('foo', $params), $params);
-
- $params = array('an', 'extremely', 'long', 'list', 'of', 'parameters');
- $this->assertEqual($callable->invokeMethod('foo', $params), $params);
-
- $params = array('an', 'extremely', 'long', 'list', 'of', 'parameters');
- $this->assertEqual($callable->invokeMethod('foo', $params), $params);
-
- $params = array(
+ $expected = array(
'if', 'you', 'have', 'a', 'parameter', 'list', 'this',
'long', 'then', 'UR', 'DOIN', 'IT', 'RONG'
);
- $this->assertEqual($callable->invokeMethod('foo', $params), $params);
+ $result = $callable->invokeMethod('foo', $expected);
+ $this->assertEqual($result['method'], 'foo');
+ $this->assertEqual($result['params'], $expected);
}
public function testParents() {
- $expected = array('lithium\\core\\Object' => 'lithium\\core\\Object');
+ $expected = array('lithium\core\Object' => 'lithium\core\Object');
$result = MockObjectForParents::parents();
$this->assertEqual($expected, $result);
- // For caching
$result = MockObjectForParents::parents();
$this->assertEqual($expected, $result);
}
/**
* Test configuration handling
- *
- * @return void
*/
public function testObjectConfiguration() {
$expected = array('testScalar' => 'default', 'testArray' => array('default'));
@@ -165,8 +175,6 @@ public function testObjectConfiguration() {
/**
* Tests that an object can be instantiated using the magic `__set_state()` method.
- *
- * @return void
*/
public function testStateBasedInstantiation() {
$result = MockObjectConfiguration::__set_state(array(
View
9 tests/cases/data/EntityTest.php
@@ -91,7 +91,14 @@ public function testMethodDispatch() {
}));
$this->assertEqual('testInstanceMethod', $entity->testInstanceMethod($entity));
- $this->expectException("/^No model bound or unhandled method call `foo`.$/");
+ $this->expectException("/^Unhandled method call `foo`.$/");
+ $entity->foo();
+ }
+
+ public function testMethodDispatchWithNoModel() {
+ $data = array('foo' => true);
+ $entity = new Entity(compact('data'));
+ $this->expectException("/^No model bound to call `foo`.$/");
$entity->foo();
}
View
2 tests/cases/data/ModelTest.php
@@ -808,7 +808,7 @@ public function testLazyLoad() {
$object = MockPost::invokeMethod('_object');
$object->belongsTo = array('Unexisting');
MockPost::config();
- MockPost::invokeMethod('_init', array('lithium\tests\mocks\data\MockPost'));
+ MockPost::invokeMethod('_initialize', array('lithium\tests\mocks\data\MockPost'));
$exception = 'Related model class \'lithium\tests\mocks\data\Unexisting\' not found.';
$this->expectException($exception);
MockPost::relations('Unexisting');
View
29 tests/cases/data/collection/DocumentSetTest.php
@@ -300,6 +300,35 @@ public function testTo() {
);
$this->assertEqual($expected, $doc->to('array', array('indexed' => false)));
}
+
+ public function testParent() {
+ $model = $this->_model;
+ $schema = new Schema(array('fields' => array(
+ '_id' => array('type' => 'id'),
+ 'bar' => array('array' => true),
+ 'foo' => array('type' => 'object', 'array' => true),
+ 'foo.foo' => array('type' => 'integer'),
+ 'foo.bar' => array('type' => 'integer')
+ )));
+ $doc = new Document(compact('model', 'schema'));
+
+ $expected = array(
+ 'foo' => 1,
+ 'bar' => 2
+ );
+ $doc->foo[] = $expected;
+ $this->assertEqual($doc, $doc->foo->parent());
+ $this->assertEqual($expected, $doc->foo[0]->data());
+
+ $data = array(
+ '_id' => '4fb6e2df3e91581fe6e75737',
+ 'foo' => array($expected)
+ );
+
+ $doc = new Document(compact('model', 'schema', 'data'));
+ $this->assertEqual($doc, $doc->foo->parent());
+ $this->assertEqual($expected, $doc->foo[0]->data());
+ }
}
?>
View
2 tests/cases/data/entity/DocumentTest.php
@@ -445,7 +445,7 @@ public function testArrayValueSet() {
public function testInvalidCall() {
$doc = new Document();
- $this->expectException("No model bound or unhandled method call `medicin`.");
+ $this->expectException("No model bound to call `medicin`.");
$result = $doc->medicin();
$this->assertNull($result);
}
View
2 tests/cases/data/entity/RecordTest.php
@@ -120,7 +120,7 @@ public function testMethodDispatch() {
$this->assertEqual('create', $result['query']->type());
$this->assertEqual(array('title' => 'foo'), $result['query']->data());
- $this->expectException("No model bound or unhandled method call `invalid`.");
+ $this->expectException("Unhandled method call `invalid`.");
$this->assertNull($this->record->invalid());
}
}
View
23 tests/cases/data/model/QueryTest.php
@@ -633,6 +633,27 @@ public function testModels() {
$this->assertEqual($expected, $query->models($this->db));
}
+ public function testRelationNames() {
+ $model = 'lithium\tests\mocks\data\model\MockQueryPost';
+ $query = new Query(compact('model'));
+ $query->alias(null, 'MockQueryComment');
+
+ $expected = array(
+ 'MockQueryComment' => 'mock_query_comments'
+ );
+ $this->assertEqual($expected, $query->relationNames($this->db));
+
+ $query->alias('Post');
+ $query->alias('Comment', 'MockQueryComment');
+ $query->alias('Post2', 'MockQueryComment.MockQueryPost');
+
+ $expected = array(
+ 'MockQueryComment' => 'mock_query_comments',
+ 'MockQueryComment.MockQueryPost' => 'mock_query_comments.mock_query_post'
+ );
+ $this->assertEqual($expected, $query->relationNames($this->db));
+ }
+
public function testExportWithJoinedStrategy() {
$query = new Query(array(
'alias' => 'MyAlias',
@@ -656,7 +677,7 @@ public function testExportWithJoinedStrategy() {
'alias' => 'AS {MyAlias}',
'comment' => '/* No comment */',
'conditions' => 'WHERE {MyAlias}.{id} = 2',
- 'fields' => '{Tag}.*, {MyAlias}.{id}, {Image}.{id}, {ImageTag}.{id}',
+ 'fields' => '{MyAlias}.{id}, {Tag}.*, {Image}.{id}, {ImageTag}.{id}',
'having' => '',
'group' => null,
'order' => null,
View
233 tests/cases/data/source/DatabaseTest.php
@@ -16,6 +16,7 @@
use lithium\tests\mocks\data\model\MockDatabaseComment;
use lithium\tests\mocks\data\model\MockDatabaseTagging;
use lithium\tests\mocks\data\model\MockDatabasePostRevision;
+use lithium\tests\mocks\data\model\mock_database\MockResult;
class DatabaseTest extends \lithium\test\Unit {
@@ -24,8 +25,8 @@ class DatabaseTest extends \lithium\test\Unit {
protected $_configs = array();
protected $_model = 'lithium\tests\mocks\data\model\MockDatabasePost';
-
protected $_gallery = 'lithium\tests\mocks\data\model\MockGallery';
+ protected $_imageTag = 'lithium\tests\mocks\data\model\MockImageTag';
public function setUp() {
MockDatabasePost::config();
@@ -42,6 +43,7 @@ public function setUp() {
public function tearDown() {
$this->db->logs = array();
+ $this->db->return = array();
}
public function testDefaultConfig() {
@@ -181,7 +183,7 @@ public function testSchema() {
$options['fields'] = array('id', 'title');
$result = $this->db->schema(new Query($options));
- $expected = array($modelName => $options['fields'], 'MockDatabaseComment' => array('id'));
+ $expected = array($modelName => $options['fields']);
$this->assertEqual($expected, $result);
$options['fields'] = array(
@@ -192,7 +194,7 @@ public function testSchema() {
$result = $this->db->schema(new Query($options));
$expected = array(
$modelName => array('id', 'title'),
- 'MockDatabaseComment' => array('body', 'id')
+ 'MockDatabaseComment' => array('body')
);
$this->assertEqual($expected, $result);
@@ -203,7 +205,7 @@ public function testSchema() {
$result = $this->db->schema(new Query($options));
$expected = array(
$modelName => array('id', 'title'),
- 'MockDatabaseComment' => array('body', 'created', 'id')
+ 'MockDatabaseComment' => array('body', 'created')
);
$this->assertEqual($expected, $result);
@@ -374,6 +376,30 @@ public function testCreate() {
$this->assertEqual($expected, $result);
}
+ public function testCreateGenericSyntax() {
+ $entity = new Record(array(
+ 'model' => $this->_model,
+ 'data' => array('data' => array('title' => 'new post', 'body' => 'the body'))
+ ));
+ $query = new Query(compact('entity') + array(
+ 'type' => 'create',
+ 'model' => $this->_model
+ ));
+ $hash = $query->export($this->db);
+ ksort($hash);
+ $expected = sha1(serialize($hash));
+
+ $result = $this->db->create($query);
+ $this->assertTrue($result);
+ $result = $query->entity()->id;
+ $this->assertEqual($expected, $result);
+
+ $expected = "INSERT INTO {mock_database_posts} ({title}, {body})";
+ $expected .= " VALUES ('new post', 'the body');";
+ $result = $this->db->sql;
+ $this->assertEqual($expected, $result);
+ }
+
public function testCreateWithValueBySchema() {
$entity = new Record(array(
'model' => $this->_model,
@@ -875,6 +901,13 @@ public function testFields() {
$this->assertEqual($expected, $result);
}
+ public function testFieldsWithEmptyAlias() {
+ $query = new Query();
+ $result = $this->db->fields(array('id', 'name', 'created'), $query);
+ $expected = '{id}, {name}, {created}';
+ $this->assertEqual($expected, $result);
+ }
+
public function testRawConditions() {
$query = new Query(array('type' => 'read', 'model' => $this->_model, 'conditions' => null));
$this->assertFalse($this->db->conditions(5, $query));
@@ -978,6 +1011,18 @@ public function testGroup() {
$this->assertEqual($expected, $result);
}
+ public function testLimit() {
+ MockDatabasePost::find('all', array('limit' => 15));
+ $result = $this->db->sql;
+ $expected = 'SELECT * FROM {mock_database_posts} AS {MockDatabasePost} LIMIT 15;';
+ $this->assertEqual($expected, $result);
+
+ MockDatabasePost::find('all', array('limit' => 10, 'page' => 3));
+ $result = $this->db->sql;
+ $expected = 'SELECT * FROM {mock_database_posts} AS {MockDatabasePost} LIMIT 10 OFFSET 20;';
+ $this->assertEqual($expected, $result);
+ }
+
/**
* Tests that various syntaxes for the `'order'` key of the query object produce the correct
* SQL.
@@ -1111,6 +1156,9 @@ public function testsplitFieldname() {
$result = $this->db->invokeMethod('_splitFieldname', array('lower(Alias.fieldname)'));
$this->assertEqual(array(null, 'lower(Alias.fieldname)'), $result);
+
+ $result = $this->db->invokeMethod('_splitFieldname', array('Alias.*'));
+ $this->assertEqual(array('Alias', '*'), $result);
}
public function testOn() {
@@ -1153,6 +1201,36 @@ public function testOn() {
$this->assertEqual($expected, $result);
}
+ public function testWithGeneration() {
+ $model = $this->_gallery;
+
+ $options = array(
+ 'type' => 'read',
+ 'model' => $model,
+ 'with' => array('Image.ImageTag.Tag')
+ );
+
+ $result = $this->db->read(new Query($options));
+ $expected = 'SELECT * FROM {mock_gallery} AS {Gallery} LEFT JOIN {mock_image} AS {Image} ';
+ $expected .= 'ON {Gallery}.{id} = {Image}.{gallery_id} LEFT JOIN {mock_image_tag} AS ';
+ $expected .= '{ImageTag} ON {Image}.{id} = {ImageTag}.{image_id} LEFT JOIN {mock_tag} ';
+ $expected .= 'AS {Tag} ON {ImageTag}.{tag_id} = {Tag}.{id};';
+ $this->assertEqual($expected, $this->db->sql);
+
+ $model = $this->_imageTag;
+ $options = array(
+ 'type' => 'read',