diff --git a/Changelog b/Changelog index 1305e3289..c76a62713 100755 --- a/Changelog +++ b/Changelog @@ -1,4 +1,5 @@ 2017-08-01 DariusIII + * Chg: Remove nntmux/data folder * Chg: Update pnotify to latest version 2017-07-31 DariusIII * Chg: Rename www folder into public, adjust constants.php acordingly diff --git a/nntmux/data/Model.php b/nntmux/data/Model.php deleted file mode 100755 index c8149b895..000000000 --- a/nntmux/data/Model.php +++ /dev/null @@ -1,1519 +0,0 @@ -. - * @author niel - * @copyright 2014 nZEDb - */ -namespace nntmux\data; - -use nntmux\StaticObject; - -class Model extends StaticObject -{ - /** - * Model belongsTo relations. - * - * @var array - */ - public $belongsTo = []; - - /** - * Model hasOne relations. - * - * @var array - */ - public $hasOne = []; - - /** - * Model hasMany relations. - * - * @var array - */ - public $hasMany = []; - - /** - * Criteria for data validation. - * - * Example usage: - * {{{ - * public $validates = array( - * 'title' => 'please enter a title', - * 'email' => array( - * array('notEmpty', 'message' => 'Email is empty.'), - * array('email', 'message' => 'Email is not valid.'), - * ) - * ); - * }}} - * - * @var array - */ - public $validates = []; - - - /** - * Holds an array of values that should be processed on `Model::config()`. Each value should - * have a matching protected property (prefixed with `_`) defined in the class. If the - * property is an array, the property name should be the key and the value should be `'merge'`. - * - * @see lithium\data\Model::config() - * @var array - */ - protected $_autoConfig = [ - 'meta', - 'finders', - 'query', - 'schema', - 'classes', - 'initializers' - ]; - - /** - * Class dependencies. - * - * @var array - */ - protected $_classes = [ - 'connections' => 'lithium\data\Connections' - ]; - - /** - * Custom find query properties, indexed by name. - * - * @see lithium\data\Model::finder() - * @var array - */ - protected $_finders = []; - - /** - * List of initialized instances. - * - * @see lithium\data\Model::_initialize(); - * @var array - */ - protected static $_initialized = []; - - /** - * Array of closures used to lazily initialize metadata. - * - * @var array - */ - protected $_initializers = []; - - /** - * Stores all custom instance methods created by `Model::instanceMethods`. - * - * @var array - */ - protected static $_instanceMethods = []; - - /** - * Stores the filters that are applied to the model instances stored in `Model::$_instances`. - * - * @var array - */ - protected $_instanceFilters = []; - - /** - * Stores model instances for internal use. - * - * While the `Model` public API does not require instantiation thanks to late static binding - * introduced in PHP 5.3, LSB does not apply to class attributes. In order to prevent you - * from needing to redeclare every single `Model` class attribute in subclasses, instances of - * the models are stored and used internally. - * - * @var array - */ - protected static $_instances = []; - - /** - * Specifies all meta-information for this model class, including the name of the data source it - * connects to, how it interacts with that class, and how its data structure is defined. - * - * - `connection`: The name of the connection (as defined in `Connections::add()`) to which the - * model should bind - * - `key`: The primary key or identifier key for records / documents this model produces, - * i.e. `'id'` or `array('_id', '_rev')`. Defaults to `'id'`. - * - `name`: The canonical name of this model. Defaults to the class name. - * - `source`: The name of the database table or document collection to bind to. Defaults to the - * lower-cased and underscored name of the class, i.e. `class UserProfile` maps to - * `'user_profiles'`. - * - `title`: The field or key used as the title for each record. Defaults to `'title'` or - * `'name'`, if those fields are available. - * - * @var array - * @see lithium\data\Connections::add() - */ - protected $_meta = [ - 'name' => null, - 'title' => null, - 'class' => null, - 'source' => null, - 'connection' => 'default' - ]; - - /** - * Default query parameters for the model finders. - * - * - `'conditions'`: The conditional query elements, e.g. - * `'conditions' => array('published' => true)` - * - `'fields'`: The fields that should be retrieved. When set to `null`, defaults to - * all fields. - * - `'order'`: The order in which the data will be returned, e.g. `'order' => 'ASC'`. - * - `'limit'`: The maximum number of records to return. - * - `'page'`: For pagination of data. - * - `'with'`: An array of relationship names to be included in the query. - * - * @var array - */ - protected $_query = [ - 'conditions' => null, - 'fields' => null, - 'order' => null, - 'limit' => null, - 'page' => null, - 'with' => [] - ]; - - /** - * Matching between relation's fieldnames and their corresponding relation name. - * - * @var array - */ - protected $_relationFieldNames = []; - - /** - * List of relation types. - * - * Valid relation types are: - * - * - `belongsTo` - * - `hasOne` - * - `hasMany` - * - * @var array - */ - protected $_relationTypes = ['belongsTo', 'hasOne', 'hasMany']; - - /** - * A list of the current relation types for this `Model`. - * - * @var array - */ - protected $_relations = []; - - /** - * Store available relation names for this model which still unloaded. - * - * @var array This array use the following notation : `relation_name => relation_type`. - */ - protected $_relationsToLoad = []; - - /** - * Stores the data schema. - * - * The schema is lazy-loaded by the first call to `Model::schema()`, unless it has been - * manually defined in the `Model` subclass. - * - * For schemaless persistent storage (e.g. MongoDB), this is never populated automatically - if - * you desire a fixed schema to interact with in those cases, you will be required to define it - * yourself. - * - * Example: - * {{{ - * protected $_schema = array( - * '_id' => array('type' => 'id'), // required for Mongo - * 'name' => array('type' => 'string', 'default' => 'Moe', 'null' => false), - * 'sign' => array('type' => 'string', 'default' => 'bar', 'null' => false), - * 'age' => array('type' => 'integer', 'default' => 0, 'null' => false) - * ); - * }}} - * - * For MongoDB specifically, you can also implement a callback in your database connection - * configuration that fetches and returns the schema data, as in the following: - * - * {{{ - * // config/bootstrap/connections.php: - * Connections::add('default', array( - * 'type' => 'MongoDb', - * 'host' => 'localhost', - * 'database' => 'app_name', - * 'schema' => function($db, $collection, $meta) { - * $result = $db->connection->schemas->findOne(compact('collection')); - * return $result ? $result['data'] : array(); - * } - * )); - * }}} - * - * This example defines an optional MongoDB convention in which the schema for each individual - * collection is stored in a "schemas" collection, where each document contains the name of - * a collection, along with a `'data'` key, which contains the schema for that collection, in - * the format specified above. - * - * When defining `'$_schema'` where the data source is MongoDB, the types map to database - * types as follows: - * - * {{{ - * id => MongoId - * date => MongoDate - * regex => MongoRegex - * integer => integer - * float => float - * boolean => boolean - * code => MongoCode - * binary => MongoBinData - * }}} - * - * @see lithium\data\source\MongoDb::$_schema - * @var array - */ - protected $_schema = []; - - /** - * Holds an array of attributes to be inherited. - * - * @see lithium\data\Model::_inherited() - * @var array - */ - protected $_inherits = []; - - - /** - * 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. - * - * @throws \BadMethodCallException - * - * @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); - } - - /** - * Allows the use of syntactic-sugar like `Model::all()` instead of `Model::find('all')`. - * - * @see lithium\data\Model::find() - * @see lithium\data\Model::$_meta - * @link http://php.net/manual/en/language.oop5.overloading.php PHP Manual: Overloading - * @throws \BadMethodCallException On unhandled call, will throw an exception. - * - * @param string $method Method name caught by `__callStatic()`. - * @param array $params Arguments given to the above `$method` call. - * - * @return mixed Results of dispatched `Model::find()` call. - */ - public static function __callStatic($method, $params) - { - $self = static::_object(); - $isFinder = isset($self->_finders[$method]); - - if ($isFinder && count($params) === 2 && is_array($params[1])) { - $params = [$params[1] + [$method => $params[0]]]; - } - - if ($method === 'all' || $isFinder) { - if ($params && !is_array($params[0])) { - $params[0] = ['conditions' => static::key($params[0])]; - } - return $self::find($method, $params ? $params[0] : []); - } - preg_match('/^findBy(?P\w+)$|^find(?P\w+)By(?P\w+)$/', $method, $args); - - if (!$args) { - $message = "Method `%s` not defined or handled in class `%s`."; - throw new \BadMethodCallException(sprintf($message, $method, get_class($self))); - } - - $field = Inflector::underscore($args['field'] ? $args['field'] : $args['fields']); - $type = isset($args['type']) ? $args['type'] : 'first'; - $type[0] = strtolower($type[0]); - - $conditions = [$field => array_shift($params)]; - $params = (isset($params[0]) && count($params) === 1) ? $params[0] : $params; - return $self::find($type, compact('conditions') + $params); - } - - /** - * Configures the model for use. This method will set the `Model::$_schema`, `Model::$_meta`, - * `Model::$_finders` class attributes, as well as obtain a handle to the configured - * persistent storage connection. - * - * @param array $config Possible options are: - * - `meta`: Meta-information for this model, such as the connection. - * - `finders`: Custom finders for this model. - * - `query`: Default query parameters. - * - `schema`: A `Schema` instance for this model. - * - `classes`: Classes used by this model. - */ - public static function config(array $config = []) - { - if (($class = get_called_class()) === __CLASS__) { - return; - } - - if (!isset(static::$_instances[$class])) { - static::$_instances[$class] = new $class(); - } - $self = static::$_instances[$class]; - - foreach ($self->_autoConfig as $key) { - if (isset($config[$key])) { - $_key = "_{$key}"; - $val = $config[$key]; - $self->$_key = is_array($val) ? $val + $self->$_key : $val; - } - } - - static::$_initialized[$class] = false; - } - - /** - * Init default connection options and connects default finders. - * - * This method will set the `Model::$_schema`, `Model::$_meta`, `Model::$_finders` class - * attributes, as well as obtain a handle to the configured persistent storage connection - * - * @param string $class The fully-namespaced class name to initialize. - * - * @return object Returns the initialized model instance. - */ - protected static function _initialize($class) - { - $self = static::$_instances[$class]; - - if (isset(static::$_initialized[$class]) && static::$_initialized[$class]) { - return $self; - } - static::$_initialized[$class] = true; - - $self->_inherit(); - - $source = [ - 'classes' => [], 'meta' => [], 'finders' => [], 'schema' => [] - ]; - - $meta = $self->_meta; - if ($meta['connection']) { - $classes = $self->_classes; - $conn = $classes['connections']::get($meta['connection']); - $source = (($conn) ? $conn->configureClass($class) : []) + $source; - } - - $self->_classes += $source['classes']; - $self->_meta = compact('class') + $self->_meta + $source['meta']; - - $self->_initializers += [ - 'name' => function($self) { - return basename(str_replace('\\', '/', $self)); - }, - 'source' => function($self) { - return Inflector::tableize($self::meta('name')); - }, - 'title' => function($self) { - $titleKeys = ['title', 'name']; - $titleKeys = array_merge($titleKeys, (array)$self::meta('key')); - return $self::hasField($titleKeys); - } - ]; - - if (is_object($self->_schema)) { - $self->_schema->append($source['schema']); - } else { - $self->_schema += $source['schema']; - } - - $self->_finders += $source['finders'] + $self->_findFilters(); - - $self->_classes += [ - 'query' => 'lithium\data\model\Query', - 'validator' => 'lithium\util\Validator', - 'entity' => 'lithium\data\Entity' - ]; - - static::_relationsToLoad(); - return $self; - } - - /** - * Merge parent class attributes to the current instance. - */ - protected function _inherit() - { - - $inherited = array_fill_keys($this->_inherited(), []); - - foreach (static::_parents() as $parent) { - $parentConfig = get_class_vars($parent); - - foreach ($inherited as $key => $value) { - if (isset($parentConfig["{$key}"])) { - $val = $parentConfig["{$key}"]; - if (is_array($val)) { - $inherited[$key] += $val; - } - } - } - - if ($parent === __CLASS__) { - break; - } - } - - foreach ($inherited as $key => $value) { - if (is_array($this->{$key})) { - $this->{$key} += $value; - } - } - } - - /** - * Return inherited attributes. - * - * @param array - * - * @return array - */ - protected function _inherited() - { - return array_merge($this->_inherits, - [ - 'validates', - 'belongsTo', - 'hasMany', - 'hasOne', - '_meta', - '_finders', - '_query', - '_schema', - '_classes', - '_initializers' - ]); - } - - /** - * 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 = []) - { - $self = static::_object(); - if (is_string($name) && isset($self->_classes[$name])) { - $name = $self->_classes[$name]; - } - return Libraries::instance(null, $name, $options); - } - - /** - * Custom check to determine if our given magic methods can be responded to. - * - * @param string $method Method name. - * @param bool $internal Interal call or not. - * - * @return bool - */ - public static function respondsTo($method, $internal = false) - { - $self = static::_object(); - $methods = static::instanceMethods(); - $isFinder = isset($self->_finders[$method]); - preg_match('/^findBy(?P\w+)$|^find(?P\w+)By(?P\w+)$/', $method, $args); - $staticRepondsTo = $isFinder || $method === 'all' || !!$args; - $instanceRespondsTo = isset($methods[$method]); - return $instanceRespondsTo || $staticRepondsTo || parent::respondsTo($method, $internal); - } - - /** - * The `find` method allows you to retrieve data from the connected data source. - * - * Examples: - * {{{ - * Posts::find('all'); // returns all records - * Posts::find('count'); // returns a count of all records - * - * // The first ten records that have 'author' set to 'Bob' - * Posts::find('all', array( - * 'conditions' => array('author' => "Bob"), 'limit' => 10 - * )); - * }}} - * - * @see lithium\data\Model::$_finders - * - * @param string $type The find type, which is looked up in `Model::$_finders`. By default it - * accepts `all`, `first`, `list` and `count`, - * @param array $options Options for the query. By default, accepts: - * - `conditions`: The conditional query elements, e.g. - * `'conditions' => array('published' => true)` - * - `fields`: The fields that should be retrieved. When set to `null`, defaults to - * all fields. - * - `order`: The order in which the data will be returned, e.g. `'order' => 'ASC'`. - * - `limit`: The maximum number of records to return. - * - `page`: For pagination of data. - * - * @return mixed - * @filter This method can be filtered. - */ - public static function find($type, array $options = []) - { - $self = static::_object(); - $finder = []; - - if ($type === null) { - return null; - } - $isFinder = is_string($type) && isset($self->_finders[$type]); - - if ($type !== 'all' && !is_array($type) && !$isFinder) { - $options['conditions'] = static::key($type); - $type = 'first'; - } - - if ($isFinder && is_array($self->_finders[$type])) { - $options = Set::merge($self->_finders[$type], $options); - } - - $options = (array)$options + (array)$self->_query; - $meta = ['meta' => $self->_meta, 'name' => get_called_class()]; - $params = compact('type', 'options'); - - $filter = function($self, $params) use ($meta) { - $options = $params['options'] + ['type' => 'read', 'model' => $meta['name']]; - $query = $self::invokeMethod('_instance', ['query', $options]); - return $self::connection()->read($query, $options); - }; - if (is_string($type) && isset($self->_finders[$type])) { - $finder = is_callable($self->_finders[$type]) ? [$self->_finders[$type]] : []; - } - return static::_filter(__FUNCTION__, $params, $filter, $finder); - } - - /** - * Gets or sets a finder by name. This can be an array of default query options, - * or a closure that accepts an array of query options, and a closure to execute. - * - * @param string $name The finder name, e.g. `first`. - * @param string $finder If you are setting a finder, this is the finder definition. - * - * @return mixed Returns finder definition if querying, or `null` if setting. - */ - public static function finder($name, $finder = null) - { - $self = static::_object(); - - if (!$finder) { - return isset($self->_finders[$name]) ? $self->_finders[$name] : null; - } - $self->_finders[$name] = $finder; - } - - /** - * Gets or sets the default query for the model. - * - * @param array $query . Possible options are: - * - `'conditions'`: The conditional query elements, e.g. - * `'conditions' => array('published' => true)` - * - `'fields'`: The fields that should be retrieved. When set to `null`, defaults to - * all fields. - * - `'order'`: The order in which the data will be returned, e.g. `'order' => 'ASC'`. - * - `'limit'`: The maximum number of records to return. - * - `'page'`: For pagination of data. - * - `'with'`: An array of relationship names to be included in the query. - * - * @return mixed Returns the query definition if querying, or `null` if setting. - */ - public static function query($query = null) - { - $self = static::_object(); - - if (!$query) { - return $self->_query; - } - $self->_query += $query; - } - - /** - * Gets or sets Model's metadata. - * - * @see lithium\data\Model::$_meta - * - * @param string $key Model metadata key. - * @param string $value Model metadata value. - * - * @return mixed Metadata value for a given key. - */ - public static function meta($key = null, $value = null) - { - $self = static::_object(); - $isArray = is_array($key); - - if ($value || $isArray) { - $value ? $self->_meta[$key] = $value : $self->_meta = $key + $self->_meta; - return; - } - return $self->_getMetaKey($isArray ? null : $key); - } - - /** - * Helper method used by `meta()` to generate and cache metadata values. - * - * @param string $key The name of the meta value to return, or `null`, to return all values. - * - * @return mixed Returns the value of the meta key specified by `$key`, or an array of all meta - * values if `$key` is `null`. - */ - protected function _getMetaKey($key = null) - { - if (!$key) { - $all = array_keys($this->_initializers); - $call = [&$this, '_getMetaKey']; - return $all ? array_combine($all, array_map($call, $all)) + $this->_meta : $this->_meta; - } - - if (isset($this->_meta[$key])) { - return $this->_meta[$key]; - } - if (isset($this->_initializers[$key]) && $initializer = $this->_initializers[$key]) { - unset($this->_initializers[$key]); - return ($this->_meta[$key] = $initializer(get_called_class())); - } - } - - /** - * The `title()` method is invoked whenever an `Entity` object is cast or coerced - * to a string. This method can also be called on the entity directly, i.e. `$post->title()`. - * - * By default, when generating the title for an object, it uses the the field specified in - * the `'title'` key of the model's meta data definition. Override this method to generate - * custom titles for objects of this model's type. - * - * @see lithium\data\Model::$_meta - * @see lithium\data\Entity::__toString() - * - * @param object $entity The `Entity` instance on which the title method is called. - * - * @return string Returns the title representation of the entity on which this method is called. - */ - public function title($entity) - { - $field = static::meta('title'); - return $entity->{$field}; - } - - /** - * If no values supplied, returns the name of the `Model` key. If values - * are supplied, returns the key value. - * - * @param mixed $values An array of values or object with values. If `$values` is `null`, - * the meta `'key'` of the model is returned. - * - * @return mixed Key value. - */ - public static function key($values = null) - { - $key = static::meta('key'); - - if ($values === null) { - return $key; - } - - $self = static::_object(); - $entity = $self->_classes['entity']; - if (is_object($values) && is_string($key)) { - return static::_key($key, $values, $entity); - } elseif ($values instanceof $entity) { - $values = $values->to('array'); - } - - if (!is_array($values) && !is_array($key)) { - return [$key => $values]; - } - - $key = (array)$key; - $result = []; - foreach ($key as $value) { - if (!isset($values[$value])) { - return null; - } - $result[$value] = $values[$value]; - } - return $result; - } - - /** - * Helper for the `Model::key()` function - * - * @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, $entity) - { - if (isset($values->$key)) { - return [$key => $values->$key]; - } elseif (!$values instanceof $entity) { - return [$key => $values]; - } - return null; - } - - /** - * Returns a list of models related to `Model`, or a list of models related - * to this model, but of a certain type. - * - * @param string $type A type of model relation. - * - * @return mixed An array of relation instances or an instance of relation. - */ - public static function relations($type = null) - { - $self = static::_object(); - - if ($type === null) { - return static::_relations(); - } - - if (isset($self->_relationFieldNames[$type])) { - $type = $self->_relationFieldNames[$type]; - } - - if (isset($self->_relations[$type])) { - return $self->_relations[$type]; - } - - if (isset($self->_relationsToLoad[$type])) { - return static::_relations(null, $type); - } - - if (in_array($type, $self->_relationTypes, true)) { - return array_keys(static::_relations($type)); - } - return null; - } - - /** - * This method automagically bind in the fly unloaded relations. - * - * @see lithium\data\model::relations() - * - * @param $type A type of model relation. - * @param $name A relation name. - * - * @return An array of relation instances or an instance of relation. - */ - protected static function _relations($type = null, $name = null) - { - $self = static::_object(); - - if ($name) { - if (isset($self->_relationsToLoad[$name])) { - $t = $self->_relationsToLoad[$name]; - unset($self->_relationsToLoad[$name]); - return static::bind($t, $name, (array)$self->{$t}[$name]); - } - return isset($self->_relations[$name]) ? $self->_relations[$name] : null; - } - if (!$type) { - foreach ($self->_relationsToLoad as $name => $t) { - static::bind($t, $name, (array)$self->{$t}[$name]); - } - $self->_relationsToLoad = []; - return $self->_relations; - } - foreach ($self->_relationsToLoad as $name => $t) { - if ($type === $t) { - static::bind($t, $name, (array)$self->{$t}[$name]); - unset($self->_relationsToLoad[$name]); - } - } - return array_filter($self->_relations, - function($i) use ($type) { - return $i->data('type') === $type; - }); - } - - /** - * Creates a relationship binding between this model and another. - * - * @see lithium\data\model\Relationship - * - * @param string $type The type of relationship to create. Must be one of `'hasOne'`, - * `'hasMany'` or `'belongsTo'`. - * @param string $name The name of the relationship. If this is also the name of the model, - * the model must be in the same namespace as this model. Otherwise, the - * fully-namespaced path to the model class must be specified in `$config`. - * @param array $config Any other configuration that should be specified in the relationship. - * See the `Relationship` class for more information. - * - * @throws ConfigException - * - * @return object Returns an instance of the `Relationship` class that defines the connection. - */ - public static function bind($type, $name, array $config = []) - { - $self = static::_object(); - if (!isset($config['fieldName'])) { - $config['fieldName'] = $self->_relationFieldName($type, $name); - } - - if (!in_array($type, $self->_relationTypes)) { - throw new ConfigException("Invalid relationship type `{$type}` specified."); - } - $self->_relationFieldNames[$config['fieldName']] = $name; - $rel = static::connection() - ->relationship(get_called_class(), - $type, - $name, - $config); - return $self->_relations[$name] = $rel; - } - - /** - * Lazy-initialize the schema for this Model object, if it is not already manually set in the - * object. You can declare `protected $_schema = array(...)` to define the schema manually. - * - * @param mixed $field Optional. You may pass a field name to get schema information for just - * one field. Otherwise, an array containing all fields is returned. If `false`, the - * schema is reset to an empty value. If an array, field definitions contained are - * appended to the schema. - * - * @throws ConfigException - * - * @return array - */ - public static function schema($field = null) - { - $self = static::_object(); - - if (!is_object($self->_schema)) { - $self->_schema = static::connection()->describe( - $self::meta('source'), - $self->_schema, - $self->_meta - ); - if (!is_object($self->_schema)) { - $class = get_called_class(); - throw new ConfigException("Could not load schema object for model `{$class}`."); - } - $key = (array)$self::meta('key'); - if ($self->_schema && $self->_schema->fields() && !$self->_schema->has($key)) { - $key = implode('`, `', $key); - throw new ConfigException("Missing key `{$key}` from schema."); - } - } - if ($field === false) { - return $self->_schema->reset(); - } - if (is_array($field)) { - return $self->_schema->append($field); - } - return $field ? $self->_schema->fields($field) : $self->_schema; - } - - /** - * Checks to see if a particular field exists in a model's schema. Can check a single field, or - * return the first field found in an array of multiple options. - * - * @param mixed $field A single field (string) or list of fields (array) to check the existence - * of. - * - * @return mixed If `$field` is a string, returns a boolean indicating whether or not that field - * exists. If `$field` is an array, returns the first field found, or `false` if none of - * the fields in the list are found. - */ - public static function hasField($field) - { - if (!is_array($field)) { - return static::schema()->fields($field); - } - foreach ($field as $f) { - if (static::hasField($f)) { - return $f; - } - } - return false; - } - - /** - * Instantiates a new record or document object, initialized with any data passed in. For - * example: - * - * {{{ - * $post = Posts::create(array('title' => 'New post')); - * echo $post->title; // echoes 'New post' - * $success = $post->save(); - * }}} - * - * Note that while this method creates a new object, there is no effect on the database until - * the `save()` method is called. - * - * In addition, this method can be used to simulate loading a pre-existing object from the - * database, without actually querying the database: - * - * {{{ - * $post = Posts::create(array('id' => $id, 'moreData' => 'foo'), array('exists' => true)); - * $post->title = 'New title'; - * $success = $post->save(); - * }}} - * - * This will create an update query against the object with an ID matching `$id`. Also note that - * only the `title` field will be updated. - * - * @param array $data Any data that this object should be populated with initially. - * @param array $options Options to be passed to item. - * - * @return object Returns a new, _un-saved_ record or document object. In addition to the values - * passed to `$data`, the object will also contain any values assigned to the - * `'default'` key of each field defined in `$_schema`. - * @filter - */ - public static function create(array $data = [], array $options = []) - { - $defaults = ['defaults' => true, 'class' => 'entity']; - $options += $defaults; - return static::_filter(__FUNCTION__, - compact('data', 'options'), - function($self, $params) { - $class = $params['options']['class']; - unset($params['options']['class']); - if ($class === 'entity' && $params['options']['defaults']) { - $data = Set::merge(Set::expand($self::schema()->defaults()), $params['data']); - } else { - $data = $params['data']; - } - $options = ['model' => $self, 'data' => $data] + $params['options']; - return $self::invokeMethod('_instance', [$class, $options]); - }); - } - - /** - * Getter and setter for custom instance methods. This is used in `Entity::__call()`. - * - * {{{ - * Model::instanceMethods(array( - * 'methodName' => array('Class', 'method'), - * 'anotherMethod' => array($object, 'method'), - * 'closureCallback' => function($entity) {} - * )); - * }}} - * - * @see lithium\data\Entity::__call() - * - * @param array $methods - * - * @return array - */ - public static function instanceMethods(array $methods = null) - { - $class = get_called_class(); - - if (!isset(static::$_instanceMethods[$class])) { - static::$_instanceMethods[$class] = []; - } - if ($methods === []) { - return static::$_instanceMethods[$class] = []; - } - if (!is_null($methods)) { - static::$_instanceMethods[$class] = $methods + static::$_instanceMethods[$class]; - } - return static::$_instanceMethods[$class]; - } - - /** - * An instance method (called on record and document objects) to create or update the record or - * document in the database that corresponds to `$entity`. - * - * For example, to create a new record or document: - * {{{ - * $post = Posts::create(); // Creates a new object, which doesn't exist in the database yet - * $post->title = "My post"; - * $success = $post->save(); - * }}} - * - * It is also used to update existing database objects, as in the following: - * {{{ - * $post = Posts::first($id); - * $post->title = "Revised title"; - * $success = $post->save(); - * }}} - * - * By default, an object's data will be checked against the validation rules of the model it is - * bound to. Any validation errors that result can then be accessed through the `errors()` - * method. - * - * {{{ - * if (!$post->save($someData)) { - * return array('errors' => $post->errors()); - * } - * }}} - * - * To override the validation checks and save anyway, you can pass the `'validate'` option: - * - * {{{ - * $post->title = "We Don't Need No Stinkin' Validation"; - * $post->body = "I know what I'm doing."; - * $post->save(null, array('validate' => false)); - * }}} - * - * @see lithium\data\Model::$validates - * @see lithium\data\Model::validates() - * @see lithium\data\Entity::errors() - * - * @param object $entity The record or document object to be saved in the database. This - * parameter is implicit and should not be passed under normal circumstances. - * In the above example, the call to `save()` on the `$post` object is - * transparently proxied through to the `Posts` model class, and `$post` is passed - * in as the `$entity` parameter. - * @param array $data Any data that should be assigned to the record before it is saved. - * @param array $options Options: - * - `'callbacks'` _boolean_: If `false`, all callbacks will be disabled before - * executing. Defaults to `true`. - * - `'validate'` _mixed_: If `false`, validation will be skipped, and the record will - * be immediately saved. Defaults to `true`. May also be specified as an array, in - * which case it will replace the default validation rules specified in the - * `$validates` property of the model. - * - `'events'` _mixed_: A string or array defining one or more validation _events_. - * Events are different contexts in which data events can occur, and correspond to the - * optional `'on'` key in validation rules. They will be passed to the validates() - * method if `'validate'` is not `false`. - * - `'whitelist'` _array_: An array of fields that are allowed to be saved to this - * record. - * - * @return boolean Returns `true` on a successful save operation, `false` on failure. - * @filter - */ - public function save($entity, $data = null, array $options = []) - { - $self = static::_object(); - $_meta = ['model' => get_called_class()] + $self->_meta; - $_schema = $self->schema(); - - $defaults = [ - 'validate' => true, - 'events' => $entity->exists() ? 'update' : 'create', - 'whitelist' => null, - 'callbacks' => true, - 'locked' => $self->_meta['locked'] - ]; - $options += $defaults; - $params = compact('entity', 'data', 'options'); - - $filter = function($self, $params) use ($_meta, $_schema) { - $entity = $params['entity']; - $options = $params['options']; - - if ($params['data']) { - $entity->set($params['data']); - } - if ($rules = $options['validate']) { - $events = $options['events']; - $validateOpts = is_array($rules) ? compact('rules', 'events') : compact('events'); - - if (!$entity->validates($validateOpts)) { - return false; - } - } - if (($whitelist = $options['whitelist']) || $options['locked']) { - $whitelist = $whitelist ?: array_keys($_schema->fields()); - } - - $type = $entity->exists() ? 'update' : 'create'; - $queryOpts = compact('type', 'whitelist', 'entity') + $options + $_meta; - $query = $self::invokeMethod('_instance', ['query', $queryOpts]); - return $self::connection()->{$type}($query, $options); - }; - - if (!$options['callbacks']) { - return $filter(get_called_class(), $params); - } - return static::_filter(__FUNCTION__, $params, $filter); - } - - /** - * An important part of describing the business logic of a model class is defining the - * validation rules. In Lithium models, rules are defined through the `$validates` class - * property, and are used by this method before saving to verify the correctness of the data - * being sent to the backend data source. - * - * Note that these are application-level validation rules, and do not - * interact with any rules or constraints defined in your data source. If such constraints fail, - * an exception will be thrown by the database layer. The `validates()` method only checks - * against the rules defined in application code. - * - * This method uses the `Validator` class to perform data validation. An array representation of - * the entity object to be tested is passed to the `check()` method, along with the model's - * validation rules. Any rules defined in the `Validator` class can be used to validate fields. - * See the `Validator` class to add custom rules, or override built-in rules. - * - * @see lithium\data\Model::$validates - * @see lithium\util\Validator::check() - * @see lithium\data\Entity::errors() - * - * @param string $entity Model entity to validate. Typically either a `Record` or `Document` - * object. In the following example: - * {{{ - * $post = Posts::create($data); - * $success = $post->validates(); - * }}} - * The `$entity` parameter is equal to the `$post` object instance. - * @param array $options Available options: - * - `'rules'` _array_: If specified, this array will _replace_ the default - * validation rules defined in `$validates`. - * - `'events'` _mixed_: A string or array defining one or more validation - * _events_. Events are different contexts in which data events can occur, and - * correspond to the optional `'on'` key in validation rules. For example, by - * default, `'events'` is set to either `'create'` or `'update'`, depending on - * whether `$entity` already exists. Then, individual rules can specify - * `'on' => 'create'` or `'on' => 'update'` to only be applied at certain times. - * Using this parameter, you can set up custom events in your rules as well, such - * as `'on' => 'login'`. Note that when defining validation rules, the `'on'` key - * can also be an array of multiple events. - * - * @return boolean Returns `true` if all validation rules on all fields succeed, otherwise - * `false`. After validation, the messages for any validation failures are assigned to - * the entity, and accessible through the `errors()` method of the entity object. - * @filter - */ - public function validates($entity, array $options = []) - { - $defaults = [ - 'rules' => $this->validates, - 'events' => $entity->exists() ? 'update' : 'create', - 'model' => get_called_class() - ]; - $options += $defaults; - $self = static::_object(); - $validator = $self->_classes['validator']; - $entity->errors(false); - $params = compact('entity', 'options'); - - $filter = function($parent, $params) use ($validator) { - $entity = $params['entity']; - $options = $params['options']; - $rules = $options['rules']; - unset($options['rules']); - - if ($errors = $validator::check($entity->data(), $rules, $options)) { - $entity->errors($errors); - } - return empty($errors); - }; - return static::_filter(__FUNCTION__, $params, $filter); - } - - /** - * Deletes the data associated with the current `Model`. - * - * @param object $entity Entity to delete. - * @param array $options Options. - * - * @return boolean Success. - * @filter - */ - public function delete($entity, array $options = []) - { - $params = compact('entity', 'options'); - - return static::_filter(__FUNCTION__, - $params, - function($self, $params) { - $options = - $params + $params['options'] + ['model' => $self, 'type' => 'delete']; - unset($options['options']); - - $query = $self::invokeMethod('_instance', ['query', $options]); - return $self::connection()->delete($query, $options); - }); - } - - /** - * Update multiple records or documents with the given data, restricted by the given set of - * criteria (optional). - * - * @param mixed $data Typically an array of key/value pairs that specify the new data with which - * the records will be updated. For SQL databases, this can optionally be an SQL - * fragment representing the `SET` clause of an `UPDATE` query. - * @param mixed $conditions An array of key/value pairs representing the scope of the records - * to be updated. - * @param array $options Any database-specific options to use when performing the operation. See - * the `delete()` method of the corresponding backend database for available - * options. - * - * @return boolean Returns `true` if the update operation succeeded, otherwise `false`. - * @filter - */ - public static function update($data, $conditions = [], array $options = []) - { - $params = compact('data', 'conditions', 'options'); - - return static::_filter(__FUNCTION__, - $params, - function($self, $params) { - $options = - $params + $params['options'] + ['model' => $self, 'type' => 'update']; - unset($options['options']); - - $query = $self::invokeMethod('_instance', ['query', $options]); - return $self::connection()->update($query, $options); - }); - } - - /** - * Remove multiple documents or records based on a given set of criteria. **WARNING**: If no - * criteria are specified, or if the criteria (`$conditions`) is an empty value (i.e. an empty - * array or `null`), all the data in the backend data source (i.e. table or collection) _will_ - * be deleted. - * - * @param mixed $conditions An array of key/value pairs representing the scope of the records or - * documents to be deleted. - * @param array $options Any database-specific options to use when performing the operation. See - * the `delete()` method of the corresponding backend database for available - * options. - * - * @return boolean Returns `true` if the remove operation succeeded, otherwise `false`. - * @filter - */ - public static function remove($conditions = [], array $options = []) - { - $params = compact('conditions', 'options'); - - return static::_filter(__FUNCTION__, - $params, - function($self, $params) { - $options = - $params['options'] + $params + ['model' => $self, 'type' => 'delete']; - unset($options['options']); - - $query = $self::invokeMethod('_instance', ['query', $options]); - return $self::connection()->delete($query, $options); - }); - } - - /** - * Gets the connection object to which this model is bound. Throws exceptions if a connection - * isn't set, or if the connection named isn't configured. - * - * @return object Returns an instance of `lithium\data\Source` from the connection configuration - * to which this model is bound. - * - * @throws ConfigException - */ - public static function &connection() - { - $self = static::_object(); - $connections = $self->_classes['connections']; - $name = isset($self->_meta['connection']) ? $self->_meta['connection'] : null; - - if ($conn = $connections::get($name)) { - return $conn; - } - $class = get_called_class(); - $msg = "The data connection `{$name}` is not configured for model `{$class}`."; - throw new ConfigException($msg); - } - - /** - * Wraps `StaticObject::applyFilter()` to account for object instances. - * - * @see lithium\core\StaticObject::applyFilter() - * - * @param string $method - * @param mixed $closure - */ - public static function applyFilter($method, $closure = null) - { - $instance = static::_object(); - - if ($method === false) { - $instance->_instanceFilters = []; - return; - } - $methods = (array)$method; - - foreach ($methods as $method) { - if (!isset($instance->_instanceFilters[$method]) || $closure === false) { - $instance->_instanceFilters[$method] = []; - } - if ($closure !== false) { - $instance->_instanceFilters[$method][] = $closure; - } - } - } - - /** - * Wraps `StaticObject::_filter()` to account for object instances. - * - * @see lithium\core\StaticObject::_filter() - * - * @param string $method - * @param array $params - * @param \Closure $callback - * @param array $filters Defaults to empty array. - * - * @return object - */ - protected static function _filter($method, $params, $callback, $filters = []) - { - if (!strpos($method, '::')) { - $method = get_called_class() . '::' . $method; - } - list($class, $method) = explode('::', $method, 2); - $instance = static::_object(); - - if (isset($instance->_instanceFilters[$method])) { - $filters = array_merge($instance->_instanceFilters[$method], $filters); - } - return parent::_filter($method, $params, $callback, $filters); - } - - protected static function &_object() - { - $class = get_called_class(); - - if (!isset(static::$_instances[$class])) { - static::$_instances[$class] = new $class(); - static::config(); - } - $object = static::_initialize($class); - return $object; - } - - /** - * Iterates through relationship types to construct relation map. - * - * @return void - * @todo See if this can be rewritten to be lazy. - */ - protected static function _relationsToLoad() - { - try { - if (!$connection = static::connection()) { - return; - } - } catch (ConfigExcepton $e) { - return; - } - - if (!$connection::enabled('relationships')) { - return; - } - - $self = static::_object(); - - foreach ($self->_relationTypes as $type) { - $self->$type = Set::normalize($self->$type); - foreach ($self->$type as $name => $config) { - $self->_relationsToLoad[$name] = $type; - $fieldName = $self->_relationFieldName($type, $name); - $self->_relationFieldNames[$fieldName] = $name; - } - } - } - - protected function _relationFieldName($type, $name) - { - if (!isset($this->{$type}[$name]['fieldName'])) { - $fieldName = static::connection() - ->relationFieldName($type, $name); - $this->{$type}[$name]['fieldName'] = $fieldName; - } - return $this->{$type}[$name]['fieldName']; - } - - /** - * Exports an array of custom finders which use the filter system to wrap around `find()`. - * - * @return void - */ - protected static function _findFilters() - { - $self = static::_object(); - $_query = $self->_query; - - return [ - 'first' => function($self, $params, $chain) { - $options =& $params['options']; - $options['limit'] = 1; - $data = $chain->next($self, $params, $chain); - - if (isset($options['return']) && $options['return'] === 'array') { - $data = is_array($data) ? reset($data) : $data; - } else { - $data = is_object($data) ? $data->rewind() : $data; - } - - return $data ?: null; - }, - 'list' => function($self, $params, $chain) { - $result = []; - $meta = $self::meta(); - $name = $meta['key']; - - foreach ($chain->next($self, $params, $chain) as $entity) { - $key = $entity->{$name}; - $result[is_scalar($key) ? $key : (string)$key] = $entity->title(); - } - return $result; - }, - 'count' => function($self, $params) use ($_query) { - $model = $self; - $type = $params['type']; - $options = array_diff_key($params['options'], $_query); - - if ($options && !isset($params['options']['conditions'])) { - $options = ['conditions' => $options]; - } else { - $options = $params['options']; - } - $options += ['type' => 'read'] + compact('model'); - $query = $self::invokeMethod('_instance', ['query', $options]); - return $self::connection()->calculation('count', $query, $options); - } - ]; - } - - /** - * Reseting the model - */ - public static function reset() - { - $class = get_called_class(); - unset(static::$_instances[$class]); - } -} - -?> diff --git a/nntmux/data/Source.php b/nntmux/data/Source.php deleted file mode 100755 index 36c9ea28c..000000000 --- a/nntmux/data/Source.php +++ /dev/null @@ -1,197 +0,0 @@ -. - * @author niel - */ -namespace nntmux\data; - -/** - * This is the base class for a data abstraction layer. - */ -abstract class Source -{ - /** - * Stores the status of this object's connection. Updated when `connect()` or `disconnect()` are - * called, or if an error occurs that closes the object's connection. - * - * @var boolean - */ - protected $_isConnected = false; - - /** - * Constructor. Sets defaults and returns object. - * - * Options defined: - * - 'autoConnect' `boolean` If true, a connection is made on initialization. Defaults to true. - * - * @param array $config - * - * @return Source object - */ - public function __construct(array $config = []) - { - $defaults = ['autoConnect' => true]; - $this->_config = $config + $defaults; - $this->_init(); - } - - /** - * Ensures the connection is closed, before the object is destroyed. - * - * @return void - */ - public function __destruct() - { - if ($this->isConnected()) { - $this->disconnect(); - } - } - - protected function _init() - { - if ($this->_config['autoConnect']) { - $this->connect(); - } - } - - /** - * Checks the connection status of this data source. If the `'autoConnect'` option is set to - * true and the source connection is not currently active, a connection attempt will be made - * before returning the result of the connection status. - * - * @param array $options - * - * @return bool - */ - public function isConnected(array $options = []) - { - $defaults = ['autoConnect' => false]; - $options += $defaults; - - if (!$this->_isConnected && $options['autoConnect']) { - try { - $this->connect(); - } catch (\NetworkException $e) { - $this->_isConnected = false; - } - } - return $this->_isConnected; - } - - /** - * Quotes data-source-native identifiers, where applicable. - * - * @param string $name Identifier name. - * - * @return string Returns `$name`, quoted if applicable. - */ - public function name($name) - { - return $name; - } - - /** - * Abstract. Must be defined by child classes. - */ - abstract public function connect(); - - /** - * Abstract. Must be defined by child classes. - */ - abstract public function disconnect(); - - /** - * Returns a list of objects (sources) that models can bind to, i.e. a list of tables in the - * case of a database, or REST collections, in the case of a web service. - * - * @param string $class The fully-name-spaced class name of the object making the request. - * - * @return array Returns an array of objects to which models can connect. - */ - abstract public function sources($class = null); - - /** - * Gets the column schema for a given entity (such as a database table). - * - * @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 - * @param array $meta The meta-information for the model class, which this method may use in - * introspecting the schema. - * - * @return array Returns a `Schema` object describing the given model's schema, where the - * array keys are the available fields, and the values are arrays describing each - * field, containing the following keys: - * - `'type'`: The field type name - */ - abstract public function describe($entity, $schema = [], array $meta = []); - - /** - * Create a record. This is the abstract method that is implemented by specific data sources. - * This method should take a query object and use it to create a record in the data source. - * - * @param mixed $query An object which defines the update operation(s) that should be performed - * against the data store. This can be a `Query`, a `RecordSet`, a `Record`, or a - * subclass of one of the three. Alternatively, `$query` can be an adapter-specific - * query string. - * @param array $options The options from Model include, - * - `validate` _boolean_ default: true - * - `events` _string_ default: create - * - `whitelist` _array_ default: null - * - `callbacks` _boolean_ default: true - * - `locked` _boolean_ default: true - * - * @return boolean Returns true if the operation was a success, otherwise false. - */ - abstract public function create($query, array $options = []); - - /** - * Abstract. Must be defined by child classes. - * - * @param mixed $query - * @param array $options - * - * @return boolean Returns true if the operation was a success, otherwise false. - */ - abstract public function delete($query, array $options = []); - - /** - * Abstract. Must be defined by child classes. - * - * @param mixed $query - * @param array $options - * - * @return boolean Returns true if the operation was a success, otherwise false. - */ - abstract public function read($query, array $options = []); - - /** - * Updates a set of records in a concrete data store. - * - * @param mixed $query An object which defines the update operation(s) that should be performed - * against the data store. This can be a `Query`, a `RecordSet`, a `Record`, or a - * subclass of one of the three. Alternatively, `$query` can be an adapter-specific - * query string. - * @param array $options Options to execute, which are defined by the concrete implementation. - * - * @return boolean Returns true if the update operation was a success, otherwise false. - */ - abstract public function update($query, array $options = []); -} - -?> diff --git a/nntmux/data/model/Anidb.php b/nntmux/data/model/Anidb.php deleted file mode 100755 index f8258d6c9..000000000 --- a/nntmux/data/model/Anidb.php +++ /dev/null @@ -1,28 +0,0 @@ -. - * @author niel - * @copyright 2014 nZEDb - */ -namespace nntmux\data\model; - -use nntmux\data\Model; - -class Anidb extends Model -{ - -} diff --git a/nntmux/data/source/Database.php b/nntmux/data/source/Database.php deleted file mode 100755 index 2fd9aa8b7..000000000 --- a/nntmux/data/source/Database.php +++ /dev/null @@ -1,1608 +0,0 @@ -. - * @author niel - */ -namespace nntmux\data\model\source; - -use \PDO; -use \PDOException; -use \InvalidArgumentException; -use \UnexpectedValueException; - -use nntmux\data\Source; -use nntmux\NetworkException; - -/** - * The `Database` class provides the base-level abstraction for SQL-oriented relational databases. - */ -abstract class Database extends Source -{ - /** - * @var \PDO - */ - public $connection; - - /** - * Creates the database object and set default values for it. - * - * Options defined: - * - 'database' _string_ Name of the database to use. Defaults to `null`. - * - 'host' _string_ Name/address of server to connect to. Defaults to 'localhost'. - * - 'login' _string_ Username to use when connecting to server. Defaults to 'root'. - * - 'password' _string_ Password to use when connecting to server. Defaults to `''`. - * - 'persistent' _boolean_ If true a persistent connection will be attempted, provided the - * adapter supports it. Defaults to `true`. - * - * @param $config array Array of configuration options. - * - * @return Database object. - */ - public function __construct(array $config = []) - { - $defaults = [ - 'persistent' => true, - 'host' => 'localhost', - 'login' => 'root', - 'password' => '', - 'database' => null, - 'encoding' => 'utf8', - 'dsn' => null, - 'options' => [] - ]; - parent::__construct($config + $defaults); - } - - public function connect() - { - $this->_isConnected = false; - $config = $this->_config; - - if (!$config['database']) { - throw new \RuntimeException('No Database configured'); - } - if (!$config['dsn']) { - throw new \RuntimeException('No DSN setup for DB Connection'); - } - $dsn = $config['dsn']; - - $options = $config['options'] + [ - PDO::ATTR_PERSISTENT => $config['persistent'], - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION - ]; - - try { - $this->connection = new PDO($dsn, $config['login'], $config['password'], $options); - } catch (PDOException $e) { - preg_match('/SQLSTATE\[(.+?)\]/', $e->getMessage(), $code); - $code = $code[1] ?: 0; - switch (true) { - case $code === 'HY000' || substr($code, 0, 2) === '08': - $msg = "Unable to connect to host `{$config['host']}`."; - throw new NetworkException($msg, null, $e); - break; - case in_array($code, ['28000', '42000']): - $msg = "Host connected, but could not access database `{$config['database']}`."; - throw new \RuntimeException($msg, null, $e); - break; - } - throw new \RuntimeException("An unknown configuration error has occured.", null, $e); - } - $this->_isConnected = true; - - if ($this->_config['encoding']) { - $this->encoding($this->_config['encoding']); - } - return $this->_isConnected; - } - - /** - * Inserts a new record into the database based on a the `Query`. The record is updated - * with the id of the insert. - * - * @see lithium\util\String::insert() - * - * @param object $query An SQL query string,. - * @param array $options If $query is a string, $options contains an array of bind values to be - * escaped, quoted, and inserted into `$query`. - * - * @return boolean|null Returns `true` if the query succeeded, otherwise `false`. - * @filter - */ - public function create($query, array $options = []) - { - } - - /** - * Disconnects the adapter from the database. - * - * @return boolean Returns `true` on success, else `false`. - */ - public function disconnect() - { - if ($this->_isConnected) { - unset($this->connection); - $this->_isConnected = false; - } - return true; - } - - /** - * Field name handler to ensure proper escaping. - * - * @param string $name Field or identifier name. - * - * @return string Returns `$name` quoted according to the rules and quote characters of the - * database adapter subclass. - */ - public function name($name) - { - $open = reset($this->_quotes); - $close = next($this->_quotes); - - list($first, $second) = $this->_splitFieldname($name); - if ($first) { - return "{$open}{$first}{$close}.{$open}{$second}{$close}"; - } - return preg_match('/^[a-z0-9_-]+$/i', $name) ? "{$open}{$name}{$close}" : $name; - } - - /** - * Reads records from a database using a `lithium\data\model\Query` object or raw SQL string. - * - * @param string|object $query `lithium\data\model\Query` object or SQL string. - * @param array $options If `$query` is a raw string, contains the values that will be escaped - * and quoted. Other options: - * - `'return'` _string_: switch return between `'array'`, `'item'`, or - * `'resource'` _string_: Defaults to `'item'`. - * - * @return mixed Determined by `$options['return']`. - * @filter - */ - public function read($query, array $options = []) - { - $defaults = [ - 'return' => is_string($query) ? 'array' : 'item', - 'schema' => null, - 'quotes' => $this->_quotes - ]; - $options += $defaults; - - return $this->_filter(__METHOD__, - compact('query', 'options'), - function($self, $params) { - $query = $params['query']; - $args = $params['options']; - $return = $args['return']; - unset($args['return']); - - $model = is_object($query) ? $query->model() : null; - - if (is_string($query)) { - $sql = String::insert($query, $self->value($args)); - } else { - if (!$data = $self->invokeMethod('_queryExport', [$query])) { - return false; - } - $sql = $self->renderCommand($data['type'], $data); - } - $result = $self->invokeMethod('_execute', [$sql]); - - switch ($return) { - case 'resource': - return $result; - case 'array': - $columns = $args['schema'] ?: $self->schema($query, $result); - - if (!is_array(reset($columns))) { - $columns = ['' => $columns]; - } - - $i = 0; - $records = []; - foreach ($result as $data) { - $offset = 0; - $records[$i] = []; - foreach ($columns as $path => $cols) { - $len = count($cols); - $values = array_combine($cols, array_slice($data, $offset, $len)); - if ($path) { - $records[$i][$path] = $values; - } else { - $records[$i] += $values; - } - $offset += $len; - } - $i++; - } - return Set::expand($records); - case 'item': - return $model::create([], - compact('query', 'result') + [ - 'class' => 'set', 'defaults' => false - ]); - } - }); - } - - /** - * Converts a given value into the proper type based on a given schema definition. - * - * @see lithium\data\source\Database::schema() - * - * @param mixed $value The value to be converted. Arrays will be recursively converted. - * @param array $schema Formatted array from `lithium\data\source\Database::schema()` - * - * @return mixed value with converted type - */ - public function value($value, array $schema = []) - { - if (is_array($value)) { - foreach ($value as $key => $val) { - $value[$key] = $this->value($val, isset($schema[$key]) ? $schema[$key] : $schema); - } - return $value; - } - - if (is_object($value) && isset($value->scalar)) { - return $value->scalar; - } - - if ($value === null) { - return 'NULL'; - } - - $type = isset($schema['type']) ? $schema['type'] : $this->_introspectType($value); - $column = isset($this->_columns[$type]) ? $this->_columns[$type] : null; - - return $this->_cast($type, $value, $column, $schema); - } - - /** - * Cast a value according to a column type, used by `Database::value()` - * - * @param string $type Name of the column type - * @param string $value Value to cast - * @param array $column The column definition - * @param array $schema - * - * @return mixed Casted value - */ - protected function _cast($type, $value, $column, $schema = []) - { - $column += ['formatter' => null, 'format' => null]; - $schema += ['default' => null, 'null' => false]; - - if (is_object($value)) { - return $value; - } - if ($formatter = $column['formatter']) { - $format = $column['format']; - return $format ? $formatter($format, $value) : $formatter($value); - } - return $this->connection->quote($value); - } - - /** - * Return the field name from a conditions key. - * - * @param string $field Field or identifier name. - * - * @return string Returns the field name without the table alias, if applicable. - * @todo Eventually, this should be refactored and moved to the Query or Schema - * class. Also, by handling field resolution in this way we are not handling - * cases where query conditions use the same field name in multiple tables. - * e.g. Foos.bar and Bars.bar will both return bar. - */ - protected function _fieldName($field) - { - if (is_string($field)) { - if (preg_match('/^[a-z0-9_-]+\.[a-z0-9_-]+$/i', $field)) { - list($first, $second) = explode('.', $field, 2); - return $second; - } - } - return $field; - } - - /** - * Provide an associative array of Closures to be used as the "formatter" key inside of the - * `Database::$_columns` specification. Each Closure should return the appropriately quoted - * or unquoted value and accept one or two parameters: - * - * @see lithium\data\source\Database::$_columns - * @see lithium\data\source\Database::_init() - * @return array of column types to Closure formatter - */ - protected function _formatters() - { - $self = $this; - - $datetime = $timestamp = $date = $time = function($format, $value) use ($self) { - if ($format && (($time = strtotime($value)) !== false)) { - $value = date($format, $time); - } - return $self->connection->quote($value); - }; - - return compact('datetime', 'timestamp', 'date', 'time') + [ - 'boolean' => function($value) { - return $value ? 1 : 0; - } - ]; - } - - /** - * Initialize `Database::$_strategies` because Closures cannot be created within the class - * definition. - * - * @see lithium\data\source\Database::$_strategies - */ - protected function _init() - { - parent::_init(); - } - - /** - * Return the alias and the field name from an identifier name. - * - * @param string $field Field name or identifier name. - * - * @return array Returns an array with the alias (or `null` if not applicable) as first value - * and the field name as second value. - */ - protected function _splitFieldname($field) - { - if (is_string($field)) { - if (preg_match('/^[a-z0-9_-]+\.([a-z 0-9_-]+|\*)$/i', $field)) { - return explode('.', $field, 2); - } - } - return [null, $field]; - } - - /** - * Helper which export the query export - * - * @param object $query The query object - * - * @return array The export array - */ - protected function &_queryExport($query) - { - $data = $query->export($this); - if ($query->limit() && ($model = $query->model())) { - foreach ($query->relationships() as $relation) { - if ($relation['type'] === 'hasMany') { - $name = $model::meta('name'); - $key = $model::key(); - $fields = $data['fields']; - $fieldname = $this->name("{$name}.{$key}"); - $data['fields'] = "DISTINCT({$fieldname}) AS _ID_"; - $sql = $this->renderCommand('read', $data); - $result = $this->_execute($sql); - - $ids = []; - while ($row = $result->next()) { - $ids[] = $row[0]; - } - - if (!$ids) { - $return = null; - return $return; - } - $data['fields'] = $fields; - $data['limit'] = ''; - $data['conditions'] = $this->conditions([ - "{$name}.{$key}" => $ids - ], - $query); - return $data; - } - } - } - return $data; - } - - /** - * Updates a record in the database based on the given `Query`. - * - * @param object $query A `lithium\data\model\Query` object - * @param array $options none - * - * @return boolean - * @filter - */ - public function update($query, array $options = []) - { - return $this->_filter(__METHOD__, - compact('query', 'options'), - function($self, $params) { - $query = $params['query']; - $params = $query->export($self); - $sql = $self->renderCommand('update', $params, $query); - $result = (boolean)$self->invokeMethod('_execute', [$sql]); - - if ($result && is_object($query) && $query->entity()) { - $query->entity()->sync(); - } - return $result; - }); - } - - /** - * Deletes a record in the database based on the given `Query`. - * - * @param object $query An SQL string, or `lithium\data\model\Query` object instance. - * @param array $options If `$query` is a string, `$options` is the array of quoted/escaped - * parameter values to be inserted into the query. - * - * @return boolean Returns `true` on successful query execution (not necessarily if records are - * deleted), otherwise `false`. - * @filter - */ - public function delete($query, array $options = []) - { - return $this->_filter(__METHOD__, - compact('query', 'options'), - function($self, $params) { - $query = $params['query']; - $isObject = is_object($query); - - if ($isObject) { - $sql = $self->renderCommand('delete', $query->export($self), $query); - } else { - $sql = String::insert($query, $self->value($params['options'])); - } - $result = (boolean)$self->invokeMethod('_execute', [$sql]); - - if ($result && $isObject && $query->entity()) { - $query->entity()->sync(null, [], ['dematerialize' => true]); - } - return $result; - }); - } - - /** - * Builds an array of keyed on the fully-namespaced `Model` with array of fields as values - * for the given `Query` - * - * @param data\model\Query $query A Query instance. - * @param object $resource - * @param object $context - * - * @return array of fields. - */ - public function schema($query, $resource = null, $context = null) - { - if (is_object($query)) { - $query->applyStrategy($this); - return $this->_schema($query, $this->_fields($query->fields(), $query)); - } - - $result = []; - $count = $resource->resource()->columnCount(); - - for ($i = 0; $i < $count; $i++) { - $meta = $resource->resource()->getColumnMeta($i); - $result[] = $meta['name']; - } - return $result; - } - - /** - * Helper method for `data\model\Database::shema()` - * - * @param data\model\Query $query A Query instance. - * @param array $fields Array of formatted fields. - * - * @return array - */ - protected function _schema($query, $fields = null) - { - $model = $query->model(); - $paths = $query->paths($this); - $models = $query->models($this); - $alias = $query->alias(); - $result = []; - - if (!$model) { - foreach ($fields as $field => $value) { - if (is_array($value)) { - $result[$field] = array_keys($value); - } else { - $result[''][] = $field; - } - } - return $result; - } - if (!$fields) { - foreach ($paths as $alias => $relation) { - $model = $models[$alias]; - $result[$relation] = $model::schema()->names(); - } - return $result; - } - - $unalias = function($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; - }; - - if (isset($fields[0])) { - $raw = array_map($unalias, $fields[0]); - unset($fields[0]); - } - - $fields = isset($fields[$alias]) ? [$alias => $fields[$alias]] + $fields : $fields; - - foreach ($fields as $field => $value) { - if (is_array($value)) { - if (isset($value['*'])) { - $relModel = $models[$field]; - $result[$paths[$field]] = $relModel::schema()->names(); - } else { - $result[$paths[$field]] = array_map($unalias, array_keys($value)); - } - } - } - - if (isset($raw)) { - $result[''] = isset($result['']) ? array_merge($raw, $result['']) : $raw; - } - return $result; - } - - /** - * Returns a string of formatted conditions to be inserted into the query statement. If the - * query conditions are defined as an array, key pairs are converted to SQL strings. - * - * Conversion rules are as follows: - * - * - If `$key` is numeric and `$value` is a string, `$value` is treated as a literal SQL - * fragment and returned. - * - * @param string|array $conditions The conditions for this query. - * @param object $context The current `lithium\data\model\Query` instance. - * @param array $options - * - `prepend` _boolean_: Whether the return string should be prepended with the - * `WHERE` keyword. - * - * @return string Returns the `WHERE` clause of an SQL query. - */ - public function conditions($conditions, $context, array $options = []) - { - $defaults = ['prepend' => 'WHERE']; - $options += $defaults; - return $this->_conditions($conditions, $context, $options); - } - - /** - * Returns a string of formatted havings to be inserted into the query statement. If the - * query havings are defined as an array, key pairs are converted to SQL strings. - * - * Conversion rules are as follows: - * - * - If `$key` is numeric and `$value` is a string, `$value` is treated as a literal SQL - * fragment and returned. - * - * @param string|array $conditions The havings for this query. - * @param object $context The current `lithium\data\model\Query` instance. - * @param array $options - * - `prepend` _boolean_: Whether the return string should be prepended with the - * `HAVING` keyword. - * - * @return string Returns the `HAVING` clause of an SQL query. - */ - public function having($conditions, $context, array $options = []) - { - $defaults = ['prepend' => 'HAVING']; - $options += $defaults; - return $this->_conditions($conditions, $context, $options); - } - - /** - * Returns a string of formatted conditions to be inserted into the query statement. If the - * query conditions are defined as an array, key pairs are converted to SQL strings. - * - * Conversion rules are as follows: - * - * - If `$key` is numeric and `$value` is a string, `$value` is treated as a literal SQL - * fragment and returned. - * - * @param string|array $conditions The conditions for this query. - * @param object $context The current `lithium\data\model\Query` instance. - * @param array $options - * - `prepend` mixed: The string to prepend or false for no prepending - * - * @return string Returns an SQL conditions clause. - */ - protected function _conditions($conditions, $context, array $options = []) - { - $defaults = ['prepend' => false]; -// $ops = $this->_operators; - $options += $defaults; - - switch (true) { - case empty($conditions): - return ''; - case is_string($conditions): - return $options['prepend'] ? $options['prepend'] . " {$conditions}" : $conditions; - case !is_array($conditions): - return ''; - } - $result = []; - - foreach ($conditions as $key => $value) { - $return = $this->_processConditions($key, $value, $context); - - if ($return) { - $result[] = $return; - } - } - $result = join(" AND ", $result); - return ($options['prepend'] && $result) ? $options['prepend'] . " {$result}" : $result; - } - - protected function _processConditions($key, $value, $context, $schema = null, $glue = 'AND') - { - $constraintTypes =& $this->_constraintTypes; - $model = $context->model(); - $models = $context->models(); - - list($first, $second) = $this->_splitFieldname($key); - if ($first && isset($models[$first]) && $class = $models[$first]) { - $schema = $class::schema(); - } elseif ($model) { - $schema = $model::schema(); - } - $fieldMeta = $schema ? (array)$schema->fields($second) : []; - - switch (true) { - case (is_numeric($key) && is_string($value)): - return $value; - case is_object($value) && isset($value->scalar): - if (is_numeric($key)) { - return $this->value($value); - } - case is_scalar($value) || is_null($value): - if ($context && ($context->type() === 'read') && ($alias = $context->alias())) { - $key = $this->_aliasing($key, $alias); - } - if (isset($value)) { - return $this->name($key) . ' = ' . $this->value($value, $fieldMeta); - } - return $this->name($key) . ' IS NULL'; - case is_numeric($key) && is_array($value): - $result = []; - foreach ($value as $cKey => $cValue) { - $result[] = $this->_processConditions($cKey, $cValue, $context, $schema, $glue); - } - return '(' . implode(' ' . $glue . ' ', $result) . ')'; - case (is_string($key) && is_object($value)): - $value = trim(rtrim($this->renderCommand($value), ';')); - return "{$this->name($key)} IN ({$value})"; - case is_array($value) && isset($constraintTypes[strtoupper($key)]): - $result = []; - $glue = strtoupper($key); - - foreach ($value as $cKey => $cValue) { - $result[] = $this->_processConditions($cKey, $cValue, $context, $schema, $glue); - } - return '(' . implode(' ' . $glue . ' ', $result) . ')'; - case $result = $this->_processOperator($key, $value, $fieldMeta, $glue): - return $result; - case is_array($value): - $value = join(', ', $this->value($value, $fieldMeta)); - return "{$this->name($key)} IN ({$value})"; - } - } - - /** - * Helper method used by `_processConditions`. - * - * @param string $key The field name string. - * @param array $value The operator to parse. - * @param array $fieldMeta The schema of the field. - * @param string $glue The glue operator (e.g `'AND'` or '`OR`'. - * - * @return string|false Returns the operator expression string or `false` if no operator - * is applicable. - * @throws QueryException if the operator is not supported. - */ - protected function _processOperator($key, $value, $fieldMeta, $glue) - { - if (!is_string($key) || !is_array($value)) { - return false; - } - $operator = strtoupper(key($value)); - if (!is_numeric($operator)) { - if (!isset($this->_operators[$operator])) { - throw new QueryException("Unsupported operator `{$operator}`."); - } - - $result = []; - foreach ($value as $op => $val) { - $result[] = $this->_operator($key, [$op => $val], $fieldMeta); - } - return '(' . implode(' ' . $glue . ' ', $result) . ')'; - } - return false; - } - - /** - * Returns a string of formatted fields to be inserted into the query statement. - * - * @param array $fields Array of fields. - * @param data\model\Query $context Generally a `data\model\Query` instance. - * - * @return string A SQL formatted string - */ - 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); - } - - $context->applyStrategy($this); - $fields = $this->_fields($fields ?: $context->fields(), $context); - $context->map($this->_schema($context, $fields)); - $toMerge = []; - - if (isset($fields[0])) { - foreach ($fields[0] as $val) { - $toMerge[] = (is_object($val) && isset($val->scalar)) ? $val->scalar : $val; - } - unset($fields[0]); - } - - $fields = isset($fields[$alias]) ? [$alias => $fields[$alias]] + $fields : $fields; - - foreach ($fields as $field => $value) { - if (is_array($value)) { - if (isset($value['*'])) { - $toMerge[] = $this->name($field) . '.*'; - continue; - } - foreach ($value as $fieldname => $mode) { - $toMerge[] = $this->_fieldsQuote($field, $fieldname); - } - } - } - - return $this->_fieldsReturn($type, $context, $toMerge, $schema); - } - - /** - * Helper for `Database::fields()` && `Database::schema()`. - * Reformat fields to be alias based. - * - * @param array $fields Array of fields. - * @param object $context Generally a `data\model\Query` instance. - * - * @return array Reformatted fields - */ - protected function _fields($fields, $context) - { - $alias = $context->alias(); - $models = $context->models($this); - $list = []; - foreach ($fields as $key => $field) { - if (!is_string($field)) { - if (isset($models[$key])) { - $field = array_fill_keys($field, true); - $list[$key] = isset($list[$key]) ? array_merge($list[$key], $field) : $field; - } else { - $list[0][] = is_array($field) ? reset($field) : $field; - } - continue; - } - if (preg_match('/^([a-z0-9_-]+|\*)$/i', $field)) { - isset($models[$field]) ? $list[$field]['*'] = true : $list[$alias][$field] = true; - } elseif (preg_match('/^([a-z0-9_-]+)\.(.*)$/i', $field, $matches)) { - $list[$matches[1]][$matches[2]] = true; - } else { - $list[0][] = $field; - } - } - return $list; - } - - /** - * @param $alias - * @param string $field - * - * @return string - */ - protected function _fieldsQuote($alias, $field) - { - $open = $this->_quotes[0]; - $close = $this->_quotes[1]; - $aliasing = preg_split("/\s+as\s+/i", $field); - if (isset($aliasing[1])) { - list($aliasname, $fieldname) = $this->_splitFieldname($aliasing[0]); - $alias = $aliasname ?: $alias; - return "{$open}{$alias}{$close}.{$open}{$fieldname}{$close} as {$aliasing[1]}"; - } 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)); - } - $method = "_{$type}Fields"; - return $this->{$method}($data, $schema, $context); - } - return empty($fields) ? '*' : join(', ', $fields); - } - - /** - * Returns a LIMIT statement from the given limit and the offset of the context object. - * - * @param integer $limit An - * @param object $context The `lithium\data\model\Query` object - * - * @return string - */ - public function limit($limit, $context) - { - if (!$limit) { - return; - } - if ($offset = $context->offset() ?: '') { - $offset = " OFFSET {$offset}"; - } - return "LIMIT {$limit}{$offset}"; - } - - /** - * Returns a join statement for given array of query objects - * - * @param object|array $joins A single or array of `lithium\data\model\Query` objects - * @param object $context The parent `lithium\data\model\Query` object - * - * @return string - */ - public function joins(array $joins, $context) - { - $result = null; - - $options = []; - foreach ($joins as $key => $join) { - if ($result) { - $result .= ' '; - } - $join = is_array($join) ? $this->_instance('query', $join) : $join; - $options['keys'] = ['mode', 'source', 'alias', 'constraints']; - $result .= $this->renderCommand('join', $join->export($this, $options)); - } - return $result; - } - - /** - * Returns a string of formatted constraints to be inserted into the query statement. If the - * query constraints are defined as an array, key pairs are converted to SQL strings. - * - * Conversion rules are as follows: - * - * - If `$key` is numeric and `$value` is a string, `$value` is treated as a literal SQL - * fragment and returned. - * - * @param string|array $constraints The constraints for a `ON` clause. - * @param object $context The current `lithium\data\model\Query` instance. - * @param array $options - * - `prepend` _boolean_: Whether the return string should be prepended with the - * `ON` keyword. - * - * @return string Returns the `ON` clause of an SQL query. - */ - public function constraints($constraints, $context, array $options = []) - { - $defaults = ['prepend' => 'ON']; - $options += $defaults; - if (is_array($constraints)) { - $constraints = $this->_constraints($constraints); - } - return $this->_conditions($constraints, $context, $options); - } - - /** - * Auto escape string value to a field name value - * - * @param array $constraints The constraints array - * - * @return array The escaped constraints array - */ - protected function _constraints(array $constraints) - { - foreach ($constraints as &$value) { - if (is_string($value)) { - $value = (object)$this->name($value); - } elseif (is_array($value)) { - $value = $this->_constraints($value); - } - } - return $constraints; - } - - /** - * Return formatted clause for `ORDER BY`. - * - * @param mixed $order The clause to be formatted - * @param object $context - * - * @return string Formatted clause. - */ - public function order($order, $context) - { - return $this->_sort($order, $context); - } - - /** - * Return formatted clause for `GROUP BY`. - * - * @param mixed $group The clause to be formatted - * @param object $context - * - * @return string Formatted clause. - */ - public function group($group, $context) - { - return $this->_sort($group, $context, 'GROUP BY', false); - } - - /** - * Helper method - * - * @see lithium\data\source\Database::order() - * @see lithium\data\source\Database::group() - * - * @param mixed $field The field - * @param object $context - * @param string $clause - * @param boolean $direction - * - * @return string Formatted clause. - */ - protected function _sort($field, $context, $clause = 'ORDER BY', $direction = true) - { - $direction = $direction ? ' ASC' : ''; - $model = $context->model(); - - if (is_string($field)) { - if (preg_match('/^(.*?)\s+((?:A|DE)SC)$/i', $field, $match)) { - $field = $match[1]; - $direction = $match[2]; - } - $field = [$field => $direction]; - } - - if (!is_array($field) || empty($field)) { - return; - } - $result = []; - - foreach ($field as $column => $dir) { - if (is_int($column)) { - $column = $dir; - $dir = $direction; - } - $dir = in_array($dir, ['ASC', 'asc', 'DESC', 'desc']) ? " {$dir}" : $direction; - - if ($model && $field = $model::schema($column)) { - $column = $this->name($column); - $name = $this->name($context->alias()) . '.' . $column; - $result[] = "{$name}{$dir}"; - continue; - } - $column = $this->name($column); - $result[] = "{$column}{$dir}"; - } - $fields = join(', ', $result); - return "$clause {$fields}"; - } - - /** - * Adds formatting to SQL comments before they're embedded in queries. - * - * @param string $comment - * - * @return string - */ - public function comment($comment) - { - return $comment ? "/* {$comment} */" : null; - } - - public function alias($alias, $context) - { - if (!$alias && ($model = $context->model())) { - $alias = $model::meta('name'); - } - return $alias ? "AS " . $this->name($alias) : null; - } - - public function cast($entity, array $data, array $options = []) - { - return $data; - } - - protected function _createFields($data, $schema, $context) - { - $fields = $values = []; - - foreach ($data as $field => $value) { - $fields[] = $this->name($field); - $values[] = $this->value($value, isset($schema[$field]) ? $schema[$field] : []); - } - $fields = join(', ', $fields); - $values = join(', ', $values); - return compact('fields', 'values'); - } - - protected function _updateFields($data, $schema, $context) - { - $fields = []; - - foreach ($data as $field => $value) { - $schema += [$field => ['default' => null]]; - $fields[] = $this->name($field) . ' = ' . $this->value($value, $schema[$field]); - } - return join(', ', $fields); - } - - /** - * Handles conversion of SQL operator keys to SQL statements. - * - * @param string $key Key in a conditions array. Usually a field name. - * @param mixed $value An SQL operator or comparison value. - * @param array $schema An array defining the schema of the field used in the criteria. - * @param array $options - * - * @return string Returns an SQL string representing part of a `WHERE` clause of a query. - */ - protected function _operator($key, $value, array $schema = [], array $options = []) - { - $defaults = ['boolean' => 'AND']; - $options += $defaults; - - list($op, $value) = each($value); - $op = strtoupper($op); - $config = $this->_operators[$op]; - $key = $this->name($key); - $values = []; - - if (!is_object($value)) { - if ($value === null) { - $value = [null]; - } - foreach ((array)$value as $val) { - $values[] = $this->value($val, $schema); - } - } elseif (isset($value->scalar)) { - return "{$key} {$op} {$value->scalar}"; - } - - switch (true) { - case (isset($config['format'])): - return $key . ' ' . String::insert($config['format'], $values); - case (is_object($value) && isset($config['multiple'])): - $op = $config['multiple']; - $value = trim(rtrim($this->renderCommand($value), ';')); - return "{$key} {$op} ({$value})"; - case (count($values) > 1 && isset($config['multiple'])): - $op = $config['multiple']; - $values = join(', ', $values); - return "{$key} {$op} ({$values})"; - case (count($values) > 1): - return join(" {$options['boolean']} ", - array_map( - function($v) use ($key, $op) { - return "{$key} {$op} {$v}"; - }, - $values - )); - } - return "{$key} {$op} {$values[0]}"; - } - - /** - * Returns a fully-qualified table name (i.e. with prefix), quoted. - * - * @param string $entity A table name or fully-namespaced model class name. - * @param array $options Available options: - * - `'quoted'` _boolean_: Indicates whether the name should be quoted. - * - * @return string Returns a quoted table name. - */ - protected function _entityName($entity, array $options = []) - { - $defaults = ['quoted' => false]; - $options += $defaults; - - if (class_exists($entity, false) && method_exists($entity, 'meta')) { - $entity = $entity::meta('source'); - } - return $options['quoted'] ? $this->name($entity) : $entity; - } - - /** - * Attempts to automatically determine the column type of a value. Used by the `value()` method - * of various database adapters to determine how to prepare a value if the schema is not - * specified. - * - * @param mixed $value The value to be prepared for an SQL query. - * - * @return string Returns the name of the column type which `$value` most likely belongs to. - */ - protected function _introspectType($value) - { - switch (true) { - case (is_bool($value)): - return 'boolean'; - case (is_float($value) || preg_match('/^\d+\.\d+$/', $value)): - return 'float'; - case (is_int($value) || preg_match('/^\d+$/', $value)): - return 'integer'; - case (is_string($value) && strlen($value) <= $this->_columns['string']['length']): - return 'string'; - default: - return 'text'; - } - } - - /** - * Casts a value which is being written or compared to a boolean-type database column. - * - * @param mixed $value A value of unknown type to be cast to boolean. Numeric values not equal - * to zero evaluate to `true`, otherwise `false`. String values equal to `'true'`, - * `'t'` or `'T'` evaluate to `true`, all others to `false`. In all other cases, - * uses PHP's default casting. - * - * @return boolean Returns a boolean representation of `$value`, based on the comparison rules - * specified above. Database adapters may override this method if boolean type coercion - * is required and falls outside the rules defined. - */ - protected function _toBoolean($value) - { - if (is_bool($value)) { - return $value; - } - if (is_int($value) || is_float($value)) { - return ($value !== 0); - } - if (is_string($value)) { - return ($value === 't' || $value === 'T' || $value === 'true'); - } - return (boolean)$value; - } - - /** - * Throw a `QueryException` error - * - * @param string $sql The offending SQL string - * - * @filter - */ - protected function _error($sql) - { - $params = compact('sql'); - return $this->_filter(__METHOD__, - $params, - function($self, $params) { - $sql = $params['sql']; - list($code, $error) = $self->error(); - throw new QueryException("{$sql}: {$error}", $code); - }); - } - - /** - * Applying a strategy to a `lithium\data\model\Query` object - * - * @param array $options The option array - * @param object $context A find query object to configure - * - * @throws QueryException - * @throws \RuntimeException - */ - public function applyStrategy($options, $context) - { - if ($context->type() !== 'read') { - return; - } - - $options += ['strategy' => 'joined']; - if (!$model = $context->model()) { - throw new \RuntimeException('The `\'with\'` option need a valid `\'model\'` option.'); - } - - $strategy = $options['strategy']; - if (isset($this->_strategies[$strategy])) { - $strategy = $this->_strategies[$strategy]; - $strategy($this, $model, $context); - } else { - throw new QueryException("Undefined query strategy `{$strategy}`."); - } - } - - /** - * Set a query's join according a Relationship. - * - * @param object $context A Query instance - * @param object $rel A Relationship instance - * @param string $fromAlias Set a specific alias for the `'from'` `Model`. - * @param string $toAlias Set a specific alias for `'to'` `Model`. - * @param mixed $constraints If `$constraints` is an array, it will be merged to defaults - * constraints. If `$constraints` is an object, defaults won't be merged. - */ - public function join($context, $rel, $fromAlias = null, $toAlias = null, $constraints = []) - { - $model = $rel->to(); - - if ($fromAlias === null) { - $from = $rel->from(); - $fromAlias = $context->alias(); - } - if ($toAlias === null) { - $toAlias = $context->alias(null, $rel->name()); - } - if (!is_object($constraints)) { - $constraints = $this->on($rel, $fromAlias, $toAlias, $constraints); - } else { - $constraints = (array)$constraints; - } - - $context->joins($toAlias, - compact('constraints', 'model') + [ - 'mode' => 'LEFT', - 'alias' => $toAlias - ]); - } - - /** - * Helper which add an alias basename to a field name if necessary - * - * @param string $name The field name. - * @param string $alias The alias name - * @param array $map An array of `'modelname' => 'aliasname'` mapping - * - * @return string - */ - protected function _aliasing($name, $alias, $map = []) - { - list($first, $second) = $this->_splitFieldname($name); - if (!$first && preg_match('/^[a-z0-9_-]+$/i', $second)) { - return $alias . "." . $second; - } elseif (isset($map[$first])) { - return $map[$first] . "." . $second; - } - return $name; - } - - /** - * Build the `ON` constraints from a `Relationship` instance - * - * @param object $rel A Relationship instance - * @param string $aliasFrom Set a specific alias for the `'from'` `Model`. - * @param string $aliasTo Set a specific alias for `'to'` `Model`. - * @param array $constraints Array of additionnal $constraints. - * - * @return array A constraints array. - */ - public function on($rel, $aliasFrom = null, $aliasTo = null, $constraints = []) - { - $model = $rel->from(); - - $aliasFrom = $aliasFrom ?: $model::meta('name'); - $aliasTo = $aliasTo ?: $rel->name(); - - $keyConstraints = []; - foreach ($rel->key() as $from => $to) { - $keyConstraints["{$aliasFrom}.{$from}"] = "{$aliasTo}.{$to}"; - } - - $mapAlias = [$model::meta('name') => $aliasFrom, $rel->name() => $aliasTo]; - - $relConstraints = $this->_on((array)$rel->constraints(), $aliasFrom, $aliasTo, $mapAlias); - $constraints = $this->_on($constraints, $aliasFrom, $aliasTo, []); - - return $constraints + $relConstraints + $keyConstraints; - } - - protected function _on(array $constraints, $aliasFrom, $aliasTo, $mapAlias = []) - { - $result = []; - foreach ($constraints as $key => $value) { - $isAliasable = ( - !is_numeric($key) && - !isset($this->_constraintTypes[$key]) && - !isset($this->_operators[$key]) - ); - if ($isAliasable) { - $key = $this->_aliasing($key, $aliasFrom, $mapAlias); - } - if (is_string($value)) { - $result[$key] = $this->_aliasing($value, $aliasTo, $mapAlias); - } elseif (is_array($value)) { - $result[$key] = $this->_on($value, $aliasFrom, $aliasTo, $mapAlias); - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Build a SQL column/table meta - * - * @param string $type The type of the meta to build (possible values: 'table' or 'column') - * @param string $name The name of the meta to build - * @param mixed $value The value used for building the meta - * - * @return string The SQL meta string - */ - protected function _meta($type, $name, $value) - { - $meta = isset($this->_metas[$type][$name]) ? $this->_metas[$type][$name] : null; - if (!$meta || (isset($meta['options']) && !in_array($value, $meta['options']))) { - return; - } - $meta += ['keyword' => '', 'escape' => false, 'join' => ' ']; - extract($meta); - if ($escape === true) { - $value = $this->value($value, ['type' => 'string']); - } - $result = $keyword . $join . $value; - return $result !== ' ' ? $result : ''; - } - - /** - * Build a SQL column constraint - * - * @param string $name The name of the meta to build - * @param mixed $value The value used for building the meta - * @param object $schema A `Schema` instance. - * - * @return string The SQL meta string - */ - protected function _constraint($name, $value, $schema = null) - { - $value += ['options' => []]; - $meta = isset($this->_constraints[$name]) ? $this->_constraints[$name] : null; - $template = isset($meta['template']) ? $meta['template'] : null; - if (!$template) { - return; - } - - $data = []; - foreach ($value as $name => $value) { - switch ($name) { - case 'key': - case 'index': - if (isset($meta[$name])) { - $data['index'] = $meta[$name]; - } - break; - case 'to': - $data[$name] = $this->name($value); - break; - case 'on': - $data[$name] = "ON {$value}"; - break; - case 'expr': - if (is_array($value)) { - $result = []; - $context = new Query(['type' => 'none']); - foreach ($value as $key => $val) { - $return = $this->_processConditions($key, $val, $context, $schema); - if ($return) { - $result[] = $return; - } - } - $data[$name] = join(" AND ", $result); - } else { - $data[$name] = $value; - } - break; - case 'toColumn': - case 'column': - $data[$name] = join(', ', array_map([$this, 'name'], (array)$value)); - break; - } - } - - return trim(String::insert($template, $data, ['clean' => ['method' => 'text']])); - } - - /** - * Create a database-native schema - * - * @param string $source A table name. - * @param object $schema A `Schema` instance. - * - * @return boolean `true` on success, `true` otherwise - */ - public function createSchema($source, $schema) - { - - if (!$schema instanceof $this->_classes['schema']) { - throw new InvalidArgumentException("Passed schema is not a valid `{$class}` instance."); - } - - $columns = []; - $primary = null; - - $source = $this->name($source); - - foreach ($schema->fields() as $name => $field) { - $field['name'] = $name; - if ($field['type'] === 'id') { - $primary = $name; - } - $columns[] = $this->column($field); - } - $columns = join(",\n", array_filter($columns)); - - $metas = $schema->meta() + ['table' => [], 'constraints' => []]; - - $constraints = $this->_buildConstraints($metas['constraints'], $schema, ",\n", $primary); - $table = $this->_buildMetas('table', $metas['table']); - - $params = compact('source', 'columns', 'constraints', 'table'); - return $this->_execute($this->renderCommand('schema', $params)); - } - - /** - * Helper for building columns metas - * - * @see DatabaseSchema::createSchema() - * @see DatabaseSchema::_column() - * - * @param string $type - * @param array $metas The array of column metas. - * @param array $names If `$names` is not `null` only build meta present in `$names` - * @param string $joiner The join character - * - * @return string The SQL constraints - */ - protected function _buildMetas($type, array $metas, $names = null, $joiner = ' ') - { - $result = ''; - $names = $names ? (array)$names : array_keys($metas); - foreach ($names as $name) { - $value = isset($metas[$name]) ? $metas[$name] : null; - if ($value && $meta = $this->_meta($type, $name, $value)) { - $result .= $joiner . $meta; - } - } - return $result; - } - - /** - * Helper for building columns constraints - * - * @see DatabaseSchema::createSchema() - * - * @param array $constraints The array of constraints - * @param string $schema The schema of the table - * @param string $joiner The join character - * @param boolean $primary - * - * @return string The SQL constraints - */ - protected function _buildconstraints(array $constraints, $schema = null, $joiner = ' ', - $primary = false) - { - $result = ''; - foreach ($constraints as $constraint) { - if (isset($constraint['type'])) { - $name = $constraint['type']; - if ($meta = $this->_constraint($name, $constraint, $schema)) { - $result .= $joiner . $meta; - } - if ($name === 'primary') { - $primary = false; - } - } - } - if ($primary) { - $result .= $joiner . $this->_constraint('primary', ['column' => $primary]); - } - return $result; - } - - /** - * Drop a table - * - * @param string $source The table name to drop. - * @param boolean $soft With "soft dropping", the function will retrun `true` even if the - * table doesn't exists. - * - * @return boolean `true` on success, `false` otherwise - */ - public function dropSchema($source, $soft = true) - { - if ($source) { - $source = $this->name($source); - $exists = $soft ? 'IF EXISTS ' : ''; - return $this->_execute($this->renderCommand('drop', compact('exists', 'source'))); - } - return false; - } - - /** - * Generate a database-native column schema string - * - * @param array $field A field array structured like the following: - * `array('name' => 'value', 'type' => 'value' [, options])`, where options can - * be `'default'`, `'null'`, `'length'` or `'precision'`. - * - * @return string SQL string - */ - public function column($field) - { - if (!isset($field['type'])) { - $field['type'] = 'string'; - } - - if (!isset($field['name'])) { - throw new InvalidArgumentException("Column name not defined."); - } - - if (!isset($this->_columns[$field['type']])) { - throw new UnexpectedValueException("Column type `{$field['type']}` does not exist."); - } - - $field += $this->_columns[$field['type']]; - - $field += [ - 'name' => null, - 'type' => null, - 'length' => null, - 'precision' => null, - 'default' => null, - 'null' => null - ]; - - $isNumeric = preg_match('/^(integer|float|boolean)$/', $field['type']); - if ($isNumeric && $field['default'] === '') { - $field['default'] = null; - } - $field['use'] = strtolower($field['use']); - return $this->_buildColumn($field); - } -} - -?> diff --git a/nntmux/data/source/Result.php b/nntmux/data/source/Result.php deleted file mode 100755 index 9d5eb7bd9..000000000 --- a/nntmux/data/source/Result.php +++ /dev/null @@ -1,222 +0,0 @@ -. - * @author niel - */ -namespace nntmux\data\model\source; - -use nntmux\Object; - -abstract class Result extends Object implements \Iterator -{ - /** - * Autoconfig. - */ - protected $_autoConfig = ['resource']; - - /** - * Contains the current element of the result set. - */ - protected $_current = false; - - /** - * Contains the cached result set. - */ - protected $_cache = null; - - /** - * If the result resource has been initialized - */ - protected $_init = false; - - /** - * The current position of the iterator. - */ - protected $_iterator = 0; - - /** - * If the result resource has been initialized - */ - protected $_key = null; - - /** - * The bound resource. - */ - protected $_resource = null; - - /** - * Setted to `true` when the collection has begun iterating. - * - * @var integer - */ - protected $_started = false; - - /** - * Indicates whether the current position is valid or not. - * - * @var boolean - * @see lithium\data\source\Result::valid() - */ - protected $_valid = false; - - - /** - * Close the resource. - */ - public function close() - { - unset($this->_resource); - $this->_resource = null; - } - - /** - * Contains the current result. - * - * @return array|null The current result (or `null` if there is none). - */ - public function current() - { - if (!$this->_init) { - $this->_fetch(); - } - $this->_started = true; - return $this->_current; - } - - /** - * Returns the current key position on the result. - * - * @return integer The current iterator position. - */ - public function key() - { - if (!$this->_init) { - $this->_fetch(); - } - $this->_started = true; - return $this->_key; - } - - /** - * Fetches the next element from the resource. - * - * @return array|false The next result (or `false` if there is none). - */ - public function next() - { - if ($this->_started === false) { - return $this->current(); - } - $this->_valid = $this->_fetch(); - if (!$this->_valid) { - $this->_key = null; - $this->_current = false; - } - return $this->current(); - } - - /** - * Fetches the previous element from the cache. - * - * @return mixed The previous result (or `false` if there is none). - */ - public function prev() - { - if (!$this->_cache) { - return null; - } - if (isset($this->_cache[--$this->_iterator - 1])) { - $this->_key = $this->_iterator - 1; - return $this->_current = $this->_cache[$this->_iterator - 1]; - } - return false; - } - - /** - * Returns the used resource. - */ - public function resource() - { - return $this->_resource; - } - - /** - * Rewinds the result set to the first position. - */ - public function rewind() - { - $this->_iterator = 0; - $this->_started = false; - $this->_key = null; - $this->_current = false; - $this->_init = false; - } - - /** - * Checks if current position is valid. - * - * @return boolean `true` if valid, `false` otherwise. - */ - public function valid() - { - if (!$this->_init) { - $this->_valid = $this->_fetch(); - } - return $this->_valid; - } - - /** - * Fetches the current element from the resource. - * - * @return boolean Return `true` on success or `false` otherwise. - */ - protected function _fetch() - { - $this->_init = true; - if ($this->_fetchFromCache() || $this->_fetchFromResource()) { - return true; - } - return false; - } - - abstract protected function _fetchFromResource(); - - /** - * Returns the result from the primed cache. - * - * @return boolean Return `true` on success or `false` if it has not been cached yet. - */ - protected function _fetchFromCache() - { - if ($this->_iterator < count($this->_cache)) { - $this->_key = $this->_iterator; - $this->_current = $this->_cache[$this->_iterator++]; - return true; - } - return false; - } - - /** - * The destructor. - */ - public function __destruct() - { - $this->close(); - } -} - -?> diff --git a/nntmux/data/source/database/MySQL.php b/nntmux/data/source/database/MySQL.php deleted file mode 100755 index 75975d178..000000000 --- a/nntmux/data/source/database/MySQL.php +++ /dev/null @@ -1,491 +0,0 @@ -. - * @author niel - */ -namespace nntmux\data\model\source\database; - -use \PDO; -use \PDOException; - -use nntmux\data\model\source\Database; - -/** - * Extends the `Database` class to implement the necessary SQL-formatting and resultset-fetching - * features for working with MySQL databases. - * - * For more information on configuring the database connection, see the `__construct()` method. - * - * @see lithium\data\source\database\adapter\MySql::__construct() - */ -class MySql extends Database -{ - - /** - * MySQL column type definitions. - * - * @var array - */ - protected $_columns = [ - 'id' => ['use' => 'int', 'length' => 11, 'increment' => true], - 'string' => ['use' => 'varchar', 'length' => 255], - 'text' => ['use' => 'text'], - 'integer' => ['use' => 'int', 'length' => 11, 'formatter' => 'intval'], - 'float' => ['use' => 'float', 'formatter' => 'floatval'], - 'datetime' => ['use' => 'datetime', 'format' => 'Y-m-d H:i:s'], - 'timestamp' => ['use' => 'timestamp', 'format' => 'Y-m-d H:i:s'], - 'time' => ['use' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'], - 'date' => ['use' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'], - 'binary' => ['use' => 'blob'], - 'boolean' => ['use' => 'tinyint', 'length' => 1] - ]; - /** - * Meta atrribute syntax - * By default `'escape'` is false and 'join' is `' '` - * - * @var array - */ - protected $_metas = [ - 'column' => [ - 'charset' => ['keyword' => 'CHARACTER SET'], - 'collate' => ['keyword' => 'COLLATE'], - 'comment' => ['keyword' => 'COMMENT', 'escape' => true] - ], - 'table' => [ - 'charset' => ['keyword' => 'DEFAULT CHARSET'], - 'collate' => ['keyword' => 'COLLATE'], - 'engine' => ['keyword' => 'ENGINE'], - 'tablespace' => ['keyword' => 'TABLESPACE'] - ] - ]; - /** - * Column contraints - * - * @var array - */ - protected $_constraints = [ - 'primary' => ['template' => 'PRIMARY KEY ({:column})'], - 'foreign_key' => [ - 'template' => 'FOREIGN KEY ({:column}) REFERENCES {:to} ({:toColumn}) {:on}' - ], - 'index' => ['template' => 'INDEX ({:column})'], - 'unique' => [ - 'template' => 'UNIQUE {:index} ({:column})', - 'key' => 'KEY', - 'index' => 'INDEX' - ], - 'check' => ['template' => 'CHECK ({:expr})'] - ]; - /** - * Pair of opening and closing quote characters used for quoting identifiers in queries. - * - * @var array - */ - protected $_quotes = ['`', '`']; - - /** - * MySQL-specific value denoting whether or not table aliases should be used in DELETE and - * UPDATE queries. - * - * @var boolean - */ - protected $_useAlias = true; - - /** - * Constructs the MySQL adapter and sets the default port to 3306. - * - * @see lithium\data\source\Database::__construct() - * @see lithium\data\Source::__construct() - * @see lithium\data\Connections::add() - * - * @param array $config Configuration options for this class. For additional configuration, - * see `lithium\data\source\Database` and `lithium\data\Source`. Available options - * defined by this class: - * - `'database'`: The name of the database to connect to. Defaults to 'lithium'. - * - `'host'`: The IP or machine name where MySQL is running, followed by a colon, - * followed by a port number or socket. Defaults to `'localhost:3306'`. - * - `'persistent'`: If a persistent connection (if available) should be made. - * Defaults to true. - * Typically, these parameters are set in `Connections::add()`, when adding the - * adapter to the list of active connections. - */ - public function __construct(array $config = []) - { - $defaults = ['host' => 'localhost:3306', 'encoding' => null]; - parent::__construct($config + $defaults); - } - - /** - * Check for required PHP extension, or supported database feature. - * - * @param string $feature Test for support for a specific feature, i.e. `"transactions"` or - * `"arrays"`. - * - * @return boolean|null Returns `true` if the particular feature (or if MySQL) support is enabled, - * otherwise `false`. - */ - public static function enabled($feature = null) - { - if (!$feature) { - return extension_loaded('pdo_mysql'); - } - $features = [ - 'arrays' => false, - 'transactions' => false, - 'booleans' => true, - 'schema' => true, - 'relationships' => true, - 'sources' => true - ]; - return isset($features[$feature]) ? $features[$feature] : null; - } - - /** - * Connects to the database using the options provided to the class constructor. - * - * @return boolean Returns `true` if a database connection could be established, otherwise - * `false`. - */ - public function connect() - { - if (!$this->_config['dsn']) { - $host = $this->_config['host']; - list($host, $port) = explode(':', $host) + [1 => "3306"]; - $dsn = "mysql:host=%s;port=%s;dbname=%s"; - $this->_config['dsn'] = sprintf($dsn, $host, $port, $this->_config['database']); - } - - if (!parent::connect()) { - return false; - } - - $info = $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); - $this->_useAlias = (boolean)version_compare($info, "4.1", ">="); - return true; - } - - /** - * Returns the list of tables in the currently-connected database. - * - * @param string $model The fully-name-spaced class name of the model object making the request. - * - * @return array Returns an array of sources to which models can connect. - * @filter This method can be filtered. - */ - public function sources($model = null) - { - $_config = $this->_config; - $params = compact('model'); - - return $this->_filter(__METHOD__, - $params, - function($self) use ($_config) { - $name = $self->name($_config['database']); - - if (!$result = $self->invokeMethod('_execute', - ["SHOW TABLES FROM {$name};"]) - ) { - return null; - } - $sources = []; - - while ($data = $result->next()) { - $sources[] = array_shift($data); - } - return $sources; - }); - } - - /** - * Gets the column schema for a given MySQL table. - * - * @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 $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 - * field, containing the following keys: - * - `'type'`: The field type name - * @filter This method can be filtered. - */ - public function describe($entity, $fields = [], array $meta = []) - { - $params = compact('entity', 'meta', 'fields'); - return $this->_filter(__METHOD__, - $params, - function($self, $params) { - extract($params); - - if ($fields) { - return $self->invokeMethod('_instance', ['schema', compact('fields')]); - } - $name = $self->invokeMethod('_entityName', - [$entity, ['quoted' => true]]); - $columns = $self->read("DESCRIBE {$name}", - [ - 'return' => 'array', 'schema' => [ - 'field', 'type', 'null', 'key', 'default', 'extra' - ] - ]); - $fields = []; - - foreach ($columns as $column) { - $schema = $self->invokeMethod('_column', [$column['type']]); - $default = $column['default']; - - if ($default === 'CURRENT_TIMESTAMP') { - $default = null; - } elseif ($schema['type'] === 'boolean') { - $default = !!$default; - } - $fields[$column['field']] = $schema + [ - 'null' => ($column['null'] === 'YES' ? true : false), - 'default' => $default - ]; - } - return $self->invokeMethod('_instance', ['schema', compact('fields')]); - }); - } - - /** - * Gets or sets the encoding for the connection. - * - * @param $encoding - * - * @return mixed If setting the encoding; returns true on success, else false. - * When getting, returns the encoding. - */ - public function encoding($encoding = null) - { - $encodingMap = ['UTF-8' => 'utf8']; - - if (empty($encoding)) { - $query = $this->connection->query("SHOW VARIABLES LIKE 'character_set_client'"); - $encoding = $query->fetchColumn(1); - return ($key = array_search($encoding, $encodingMap)) ? $key : $encoding; - } - $encoding = isset($encodingMap[$encoding]) ? $encodingMap[$encoding] : $encoding; - - try { - $this->connection->exec("SET NAMES '{$encoding}'"); - return true; - } catch (PDOException $e) { - return false; - } - } - - /** - * Converts a given value into the proper type based on a given schema definition. - * - * @see lithium\data\source\Database::schema() - * - * @param mixed $value The value to be converted. Arrays will be recursively converted. - * @param array $schema Formatted array from `lithium\data\source\Database::schema()` - * - * @return mixed Value with converted type. - */ - public function value($value, array $schema = []) - { - if (($result = parent::value($value, $schema)) !== null) { - return $result; - } - return $this->connection->quote((string)$value); - } - - /** - * Retrieves database error message and error code. - * - * @return array - */ - public function error() - { - if ($error = $this->connection->errorInfo()) { - return [$error[1], $error[2]]; - } - } - - public function alias($alias, $context) - { - if ($context->type() === 'update' || $context->type() === 'delete') { - return; - } - return parent::alias($alias, $context); - } - - /** - * @todo Eventually, this will need to rewrite aliases for DELETE and UPDATE queries, same with - * order(). - * - * @param string $conditions - * @param string $context - * @param array $options - * - * @return void - */ - public function conditions($conditions, $context, array $options = []) - { - return parent::conditions($conditions, $context, $options); - } - - /** - * Execute a given query. - * - * @see lithium\data\source\Database::renderCommand() - * - * @param string $sql The sql string to execute - * @param array $options Available options: - * - 'buffered': If set to `false` uses mysql_unbuffered_query which - * sends the SQL query query to MySQL without automatically fetching and buffering the - * result rows as `mysql_query()` does (for less memory usage). - * - * @return resource Returns the result resource handle if the query is successful. - * @filter - */ - protected function _execute($sql, array $options = []) - { - $defaults = ['buffered' => true]; - $options += $defaults; - $this->connection->exec("USE `{$this->_config['database']}`"); - - $conn = $this->connection; - - $params = compact('sql', 'options'); - - return $this->_filter(__METHOD__, - $params, - function($self, $params) use ($conn) { - $sql = $params['sql']; - $options = $params['options']; - $conn->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $options['buffered']); - - try { - $resource = $conn->query($sql); - } catch (PDOException $e) { - $self->invokeMethod('_error', [$sql]); - }; - return $self->invokeMethod('_instance', ['result', compact('resource')]); - }); - } - - /** - * Gets the last auto-generated ID from the query that inserted a new record. - * - * @param object $query The `Query` object associated with the query which generated - * - * @return mixed Returns the last inserted ID key for an auto-increment column or a column - * bound to a sequence. - */ - protected function _insertId($query) - { - $resource = $this->_execute('SELECT LAST_INSERT_ID() AS insertID'); - list($id) = $resource->next(); - return ($id && $id !== '0') ? $id : null; - } - - /** - * Converts database-layer column types to basic types. - * - * @param string $real Real database-layer column type (i.e. `"varchar(255)"`) - * - * @return array Column type (i.e. "string") plus 'length' when appropriate. - */ - protected function _column($real) - { - if (is_array($real)) { - return $real['type'] . (isset($real['length']) ? "({$real['length']})" : ''); - } - - if (!preg_match('/(?P\w+)(?:\((?P[\d,]+)\))?/', $real, $column)) { - return $real; - } - $column = array_intersect_key($column, ['type' => null, 'length' => null]); - - if (isset($column['length']) && $column['length']) { - $length = explode(',', $column['length']) + [null, null]; - $column['length'] = $length[0] ? intval($length[0]) : null; - $length[1] ? $column['precision'] = intval($length[1]) : null; - } - - switch (true) { - case in_array($column['type'], ['date', 'time', 'datetime', 'timestamp']): - return $column; - case ($column['type'] === 'tinyint' && $column['length'] == '1'): - case ($column['type'] === 'boolean'): - return ['type' => 'boolean']; - break; - case (strpos($column['type'], 'int') !== false): - $column['type'] = 'integer'; - break; - case (strpos($column['type'], 'char') !== false || $column['type'] === 'tinytext'): - $column['type'] = 'string'; - break; - case (strpos($column['type'], 'text') !== false): - $column['type'] = 'text'; - break; - case (strpos($column['type'], 'blob') !== false || $column['type'] === 'binary'): - $column['type'] = 'binary'; - break; - case preg_match('/float|double|decimal/', $column['type']): - $column['type'] = 'float'; - break; - default: - $column['type'] = 'text'; - break; - } - return $column; - } - - /** - * Helper for `DatabaseSchema::_column()` - * - * @param array $field A field array - * - * @return string The SQL column string - */ - protected function _buildColumn($field) - { - extract($field); - if ($type === 'float' && $precision) { - $use = 'decimal'; - } - - $out = $this->name($name) . ' ' . $use; - - $allowPrecision = preg_match('/^(decimal|float|double|real|numeric)$/', $use); - $precision = ($precision && $allowPrecision) ? ",{$precision}" : ''; - - if ($length && ($allowPrecision || preg_match('/(char|binary|int|year)/', $use))) { - $out .= "({$length}{$precision})"; - } - - $out .= $this->_buildMetas('column', $field, ['charset', 'collate']); - - if (isset($increment) && $increment) { - $out .= ' NOT NULL AUTO_INCREMENT'; - } else { - $out .= is_bool($null) ? ($null ? ' NULL' : ' NOT NULL') : ''; - $out .= $default ? ' DEFAULT ' . $this->value($default, $field) : ''; - } - - return $out . $this->_buildMetas('column', $field, ['comment']); - } -} - -?> diff --git a/nntmux/data/source/database/adapter/pdo/Result.php b/nntmux/data/source/database/adapter/pdo/Result.php deleted file mode 100755 index 19a0dada6..000000000 --- a/nntmux/data/source/database/adapter/pdo/Result.php +++ /dev/null @@ -1,71 +0,0 @@ -. - * @author niel - */ -namespace nntmux\data\model\source\database\adapter\pdo; - -use \PDO; -use \PDOStatement; -use \PDOException; - -/** - * This class is a wrapper around the MySQL result returned and can be used to iterate over it. - * - * It also provides a simple caching mechanism which stores the result after the first load. - * You are then free to iterate over the result back and forth through the provided methods - * and don't have to think about hitting the database too often. - * - * On initialization, it needs a `PDOStatement` to operate on. You are then free to use all - * methods provided by the `Iterator` interface. - * - * @link http://php.net/manual/de/class.pdostatement.php The PDOStatement class. - * @link http://php.net/manual/de/class.iterator.php The Iterator interface. - */ -class Result extends \nntmux\data\source\Result -{ - public $named = false; - - /** - * Fetches the result from the resource and caches it. - * - * @return boolean Return `true` on success or `false` if it is not valid. - */ - protected function _fetchFromResource() - { - if ($this->_resource instanceof PDOStatement) { - try { - $mode = $this->named ? PDO::FETCH_NAMED : PDO::FETCH_NUM; - if ($result = $this->_resource->fetch($mode)) { - $this->_key = $this->_iterator; - $this->_current = $this->_cache[$this->_iterator++] = $result; - return true; - } - } catch (PDOException $e) { - } - } - $this->_resource = null; - return false; - } - - public function __destruct() - { - $this->close(); - } -} - -?>