PageRenderTime 64ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/data/source/MongoDb.php

https://github.com/ifunk/lithium
PHP | 815 lines | 462 code | 79 blank | 274 comment | 69 complexity | 66c013a05d057c57f93fc6e4be9dc2d5 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\data\source;
  9. use Mongo;
  10. use MongoId;
  11. use MongoCode;
  12. use MongoDate;
  13. use MongoRegex;
  14. use MongoBinData;
  15. use lithium\util\Inflector;
  16. use lithium\core\NetworkException;
  17. use Exception;
  18. /**
  19. * A data source adapter which allows you to connect to the MongoDB database engine. MongoDB is an
  20. * Open Source distributed document database which bridges the gap between key/value stores and
  21. * relational databases. To learn more about MongoDB, see here:
  22. * [http://www.mongodb.org/](http://www.mongodb.org/).
  23. *
  24. * Rather than operating on records and record sets, queries against MongoDB will return nested sets
  25. * of `Document` objects. A `Document`'s fields can contain both simple and complex data types
  26. * (i.e. arrays) including other `Document` objects.
  27. *
  28. * After installing MongoDB, you can connect to it as follows:
  29. * {{{
  30. * // config/bootstrap/connections.php:
  31. * Connections::add('default', array('type' => 'MongoDb', 'database' => 'myDb'));
  32. * }}}
  33. *
  34. * By default, it will attempt to connect to a Mongo instance running on `localhost` on port
  35. * 27017. See `__construct()` for details on the accepted configuration settings.
  36. *
  37. * @see lithium\data\entity\Document
  38. * @see lithium\data\Connections::add()
  39. * @see lithium\data\source\MongoDb::__construct()
  40. */
  41. class MongoDb extends \lithium\data\Source {
  42. /**
  43. * The Mongo class instance.
  44. *
  45. * @var object
  46. */
  47. public $server = null;
  48. /**
  49. * The MongoDB object instance.
  50. *
  51. * @var object
  52. */
  53. public $connection = null;
  54. /**
  55. * Classes used by this class.
  56. *
  57. * @var array
  58. */
  59. protected $_classes = array(
  60. 'entity' => 'lithium\data\entity\Document',
  61. 'array' => 'lithium\data\collection\DocumentArray',
  62. 'set' => 'lithium\data\collection\DocumentSet',
  63. 'result' => 'lithium\data\source\mongo_db\Result',
  64. 'exporter' => 'lithium\data\source\mongo_db\Exporter',
  65. 'relationship' => 'lithium\data\model\Relationship'
  66. );
  67. /**
  68. * Map of typical SQL-like operators to their MongoDB equivalents.
  69. *
  70. * @var array Keys are SQL-like operators, value is the MongoDB equivalent.
  71. */
  72. protected $_operators = array(
  73. '<' => '$lt',
  74. '>' => '$gt',
  75. '<=' => '$lte',
  76. '>=' => '$gte',
  77. '!=' => array('single' => '$ne', 'multiple' => '$nin'),
  78. '<>' => array('single' => '$ne', 'multiple' => '$nin'),
  79. 'or' => '$or',
  80. '||' => '$or',
  81. 'not' => '$not',
  82. '!' => '$not'
  83. );
  84. /**
  85. * A closure or anonymous function which receives an instance of this class, a collection name
  86. * and associated meta information, and returns an array defining the schema for an associated
  87. * model, where the keys are field names, and the values are arrays defining the type
  88. * information for each field. At a minimum, type arrays must contain a `'type'` key. For more
  89. * information on schema definitions, and an example schema callback implementation, see the
  90. * `$_schema` property of the `Model` class.
  91. *
  92. * @see lithium\data\Model::$_schema
  93. * @var Closure
  94. */
  95. protected $_schema = null;
  96. /**
  97. * An array of closures that handle casting values to specific types.
  98. *
  99. * @var array
  100. */
  101. protected $_handlers = array();
  102. /**
  103. * List of configuration keys which will be automatically assigned to their corresponding
  104. * protected class properties.
  105. *
  106. * @var array
  107. */
  108. protected $_autoConfig = array('schema', 'handlers', 'classes' => 'merge');
  109. /**
  110. * Instantiates the MongoDB adapter with the default connection information.
  111. *
  112. * @see lithium\data\Connections::add()
  113. * @see lithium\data\source\MongoDb::$_schema
  114. * @link http://php.net/manual/en/mongo.construct.php PHP Manual: Mongo::__construct()
  115. * @param array $config All information required to connect to the database, including:
  116. * - `'database'` _string_: The name of the database to connect to. Defaults to `null`.
  117. * - `'host'` _string_: The IP or machine name where Mongo is running, followed by a
  118. * colon, and the port number. Defaults to `'localhost:27017'`.
  119. * - `'persistent'` _mixed_: Determines a persistent connection to attach to. See the
  120. * `$options` parameter of
  121. * [`Mongo::__construct()`](http://www.php.net/manual/en/mongo.construct.php) for
  122. * more information. Defaults to `false`, meaning no persistent connection is made.
  123. * - `'timeout'` _integer_: The number of milliseconds a connection attempt will wait
  124. * before timing out and throwing an exception. Defaults to `100`.
  125. * - `'schema'` _closure_: A closure or anonymous function which returns the schema
  126. * information for a model class. See the `$_schema` property for more information.
  127. * - `'gridPrefix'` _string_: The default prefix for MongoDB's `chunks` and `files`
  128. * collections. Defaults to `'fs'`.
  129. * - `'replicaSet'` _boolean_: See the documentation for `Mongo::__construct()`. Defaults
  130. * to `false`.
  131. *
  132. * Typically, these parameters are set in `Connections::add()`, when adding the adapter to the
  133. * list of active connections.
  134. */
  135. public function __construct(array $config = array()) {
  136. $defaults = array(
  137. 'persistent' => false,
  138. 'login' => null,
  139. 'password' => null,
  140. 'host' => Mongo::DEFAULT_HOST . ':' . Mongo::DEFAULT_PORT,
  141. 'database' => null,
  142. 'timeout' => 100,
  143. 'replicaSet' => false,
  144. 'schema' => null,
  145. 'gridPrefix' => 'fs'
  146. );
  147. parent::__construct($config + $defaults);
  148. }
  149. protected function _init() {
  150. parent::_init();
  151. $this->_operators += array(
  152. 'like' => function($key, $value) { return new MongoRegex($value); }
  153. );
  154. $this->_handlers += array(
  155. 'id' => function($v) {
  156. return is_string($v) && preg_match('/^[0-9a-f]{24}$/', $v) ? new MongoId($v) : $v;
  157. },
  158. 'date' => function($v) {
  159. $v = is_numeric($v) ? intval($v) : strtotime($v);
  160. return (!$v || time() == $v) ? new MongoDate() : new MongoDate($v);
  161. },
  162. 'regex' => function($v) { return new MongoRegex($v); },
  163. 'integer' => function($v) { return (integer) $v; },
  164. 'float' => function($v) { return (float) $v; },
  165. 'boolean' => function($v) { return (boolean) $v; },
  166. 'code' => function($v) { return new MongoCode($v); },
  167. 'binary' => function($v) { return new MongoBinData($v); }
  168. );
  169. }
  170. /**
  171. * Ensures that the server connection is closed and resources are freed when the adapter
  172. * instance is destroyed.
  173. *
  174. * @return void
  175. */
  176. public function __destruct() {
  177. if ($this->_isConnected) {
  178. $this->disconnect();
  179. }
  180. }
  181. /**
  182. * With no parameter, checks to see if the `mongo` extension is installed. With a parameter,
  183. * queries for a specific supported feature.
  184. *
  185. * @param string $feature Test for support for a specific feature, i.e. `"transactions"` or
  186. * `"arrays"`.
  187. * @return boolean Returns `true` if the particular feature (or if MongoDB) support is enabled,
  188. * otherwise `false`.
  189. */
  190. public static function enabled($feature = null) {
  191. if (!$feature) {
  192. return extension_loaded('mongo');
  193. }
  194. $features = array(
  195. 'arrays' => true,
  196. 'transactions' => false,
  197. 'booleans' => true,
  198. 'relationships' => true
  199. );
  200. return isset($features[$feature]) ? $features[$feature] : null;
  201. }
  202. /**
  203. * Configures a model class by overriding the default dependencies for `'set'` and
  204. * `'entity'` , and sets the primary key to `'_id'`, in keeping with Mongo's conventions.
  205. *
  206. * @see lithium\data\Model::$_meta
  207. * @see lithium\data\Model::$_classes
  208. * @param string $class The fully-namespaced model class name to be configured.
  209. * @return Returns an array containing keys `'classes'` and `'meta'`, which will be merged with
  210. * their respective properties in `Model`.
  211. */
  212. public function configureClass($class) {
  213. return array(
  214. 'meta' => array('key' => '_id', 'locked' => false),
  215. 'schema' => array()
  216. );
  217. }
  218. /**
  219. * Connects to the Mongo server. Matches up parameters from the constructor to create a Mongo
  220. * database connection.
  221. *
  222. * @see lithium\data\source\MongoDb::__construct()
  223. * @link http://php.net/manual/en/mongo.construct.php PHP Manual: Mongo::__construct()
  224. * @return boolean Returns `true` the connection attempt was successful, otherwise `false`.
  225. */
  226. public function connect() {
  227. $cfg = $this->_config;
  228. $this->_isConnected = false;
  229. $host = is_array($cfg['host']) ? join(',', $cfg['host']) : $cfg['host'];
  230. $login = $cfg['login'] ? "{$cfg['login']}:{$cfg['password']}@" : '';
  231. $connection = "mongodb://{$login}{$host}" . ($login ? "/{$cfg['database']}" : '');
  232. $options = array(
  233. 'connect' => true, 'timeout' => $cfg['timeout'], 'replicaSet' => $cfg['replicaSet']
  234. );
  235. try {
  236. if ($persist = $cfg['persistent']) {
  237. $options['persist'] = $persist === true ? 'default' : $persist;
  238. }
  239. $this->server = new Mongo($connection, $options);
  240. if ($this->connection = $this->server->{$cfg['database']}) {
  241. $this->_isConnected = true;
  242. }
  243. } catch (Exception $e) {
  244. throw new NetworkException("Could not connect to the database.", 503, $e);
  245. }
  246. return $this->_isConnected;
  247. }
  248. /**
  249. * Disconnect from the Mongo server.
  250. *
  251. * @return boolean True on successful disconnect, false otherwise.
  252. */
  253. public function disconnect() {
  254. if ($this->server && $this->server->connected) {
  255. try {
  256. $this->_isConnected = !$this->server->close();
  257. } catch (Exception $e) {}
  258. unset($this->connection, $this->server);
  259. return !$this->_isConnected;
  260. }
  261. return true;
  262. }
  263. /**
  264. * Returns the list of collections in the currently-connected database.
  265. *
  266. * @param string $class The fully-name-spaced class name of the model object making the request.
  267. * @return array Returns an array of objects to which models can connect.
  268. */
  269. public function sources($class = null) {
  270. $this->_checkConnection();
  271. $conn = $this->connection;
  272. return array_map(function($col) { return $col->getName(); }, $conn->listCollections());
  273. }
  274. /**
  275. * Gets the column 'schema' for a given MongoDB collection. Only returns a schema if the
  276. * `'schema'` configuration flag has been set in the constructor.
  277. *
  278. * @see lithium\data\source\MongoDb::$_schema
  279. * @param mixed $entity Would normally specify a collection name.
  280. * @param array $meta
  281. * @return array Returns an associative array describing the given collection's schema.
  282. */
  283. public function describe($entity, array $meta = array()) {
  284. if (!$schema = $this->_schema) {
  285. return array();
  286. }
  287. return $schema($this, $entity, $meta);
  288. }
  289. /**
  290. * Quotes identifiers.
  291. *
  292. * MongoDb does not need identifiers quoted, so this method simply returns the identifier.
  293. *
  294. * @param string $name The identifier to quote.
  295. * @return string The quoted identifier.
  296. */
  297. public function name($name) {
  298. return $name;
  299. }
  300. /**
  301. * A method dispatcher that allows direct calls to native methods in PHP's `Mongo` object. Read
  302. * more here: http://php.net/manual/class.mongo.php
  303. *
  304. * For example (assuming this instance is stored in `Connections` as `'mongo'`):
  305. * {{{// Manually repairs a MongoDB instance
  306. * Connections::get('mongo')->repairDB($db); // returns null
  307. * }}}
  308. *
  309. * @param string $method The name of native method to call. See the link above for available
  310. * class methods.
  311. * @param array $params A list of parameters to be passed to the native method.
  312. * @return mixed The return value of the native method specified in `$method`.
  313. */
  314. public function __call($method, $params) {
  315. if ((!$this->server) && !$this->connect()) {
  316. return null;
  317. }
  318. return call_user_func_array(array(&$this->server, $method), $params);
  319. }
  320. /**
  321. * Normally used in cases where the query is a raw string (as opposed to a `Query` object),
  322. * to database must determine the correct column names from the result resource. Not
  323. * applicable to this data source.
  324. *
  325. * @param mixed $query
  326. * @param resource $resource
  327. * @param object $context
  328. * @return array
  329. */
  330. public function schema($query, $resource = null, $context = null) {
  331. return array();
  332. }
  333. /**
  334. * Create new document
  335. *
  336. * @param string $query
  337. * @param array $options
  338. * @return boolean
  339. * @filter
  340. */
  341. public function create($query, array $options = array()) {
  342. $defaults = array('safe' => false, 'fsync' => false);
  343. $options += $defaults;
  344. $this->_checkConnection();
  345. $params = compact('query', 'options');
  346. $_config = $this->_config;
  347. $_exp = $this->_classes['exporter'];
  348. return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config, $_exp) {
  349. $query = $params['query'];
  350. $options = $params['options'];
  351. $args = $query->export($self, array('keys' => array('source', 'data')));
  352. $data = $_exp::get('create', $args['data']);
  353. $source = $args['source'];
  354. if ($source == "{$_config['gridPrefix']}.files" && isset($data['create']['file'])) {
  355. $result = array('ok' => true);
  356. $data['create']['_id'] = $self->invokeMethod('_saveFile', array($data['create']));
  357. } else {
  358. $result = $self->connection->{$source}->insert($data['create'], $options);
  359. }
  360. if ($result === true || isset($result['ok']) && (boolean) $result['ok'] === true) {
  361. if ($query->entity()) {
  362. $query->entity()->sync($data['create']['_id']);
  363. }
  364. return true;
  365. }
  366. return false;
  367. });
  368. }
  369. protected function _saveFile($data) {
  370. $uploadKeys = array('name', 'type', 'tmp_name', 'error', 'size');
  371. $grid = $this->connection->getGridFS();
  372. $file = null;
  373. $method = null;
  374. switch (true) {
  375. case (is_array($data['file']) && array_keys($data['file']) == $uploadKeys):
  376. if (!$data['file']['error'] && is_uploaded_file($data['file']['tmp_name'])) {
  377. $method = 'storeFile';
  378. $file = $data['file']['tmp_name'];
  379. $data['filename'] = $data['file']['name'];
  380. }
  381. break;
  382. case (is_string($data['file']) && file_exists($data['file'])):
  383. $method = 'storeFile';
  384. $file = $data['file'];
  385. break;
  386. case $data['file']:
  387. $method = 'storeBytes';
  388. $file = $data['file'];
  389. break;
  390. }
  391. if (!$method || !$file) {
  392. return;
  393. }
  394. if (isset($data['_id'])) {
  395. $data += (array) get_object_vars($grid->get($data['_id']));
  396. $grid->delete($data['_id']);
  397. }
  398. unset($data['file']);
  399. return $grid->{$method}($file, $data);
  400. }
  401. /**
  402. * Read from document
  403. *
  404. * @param string $query
  405. * @param array $options
  406. * @return object
  407. * @filter
  408. */
  409. public function read($query, array $options = array()) {
  410. $this->_checkConnection();
  411. $defaults = array('return' => 'resource');
  412. $options += $defaults;
  413. $params = compact('query', 'options');
  414. $_config = $this->_config;
  415. return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config) {
  416. $query = $params['query'];
  417. $options = $params['options'];
  418. $args = $query->export($self);
  419. $source = $args['source'];
  420. if ($group = $args['group']) {
  421. $result = $self->invokeMethod('_group', array($group, $args, $options));
  422. $config = array('class' => 'set') + compact('query') + $result;
  423. return $self->item($query->model(), $config['data'], $config);
  424. }
  425. $collection = $self->connection->{$source};
  426. if ($source == "{$_config['gridPrefix']}.files") {
  427. $collection = $self->connection->getGridFS();
  428. }
  429. $result = $collection->find($args['conditions'], $args['fields']);
  430. if ($query->calculate()) {
  431. return $result;
  432. }
  433. $resource = $result->sort($args['order'])->limit($args['limit'])->skip($args['offset']);
  434. $result = $self->invokeMethod('_instance', array('result', compact('resource')));
  435. $config = compact('result', 'query') + array('class' => 'set');
  436. return $self->item($query->model(), array(), $config);
  437. });
  438. }
  439. protected function _group($group, $args, $options) {
  440. $conditions = $args['conditions'];
  441. $group += array('$reduce' => $args['reduce'], 'initial' => $args['initial']);
  442. $command = array('group' => $group + array('ns' => $args['source'], 'cond' => $conditions));
  443. $stats = $this->connection->command($command);
  444. $data = isset($stats['retval']) ? $stats['retval'] : null;
  445. unset($stats['retval']);
  446. return compact('data', 'stats');
  447. }
  448. /**
  449. * Update document
  450. *
  451. * @param string $query
  452. * @param array $options
  453. * @return boolean
  454. * @filter
  455. */
  456. public function update($query, array $options = array()) {
  457. $defaults = array('upsert' => false, 'multiple' => true, 'safe' => false, 'fsync' => false);
  458. $options += $defaults;
  459. $this->_checkConnection();
  460. $params = compact('query', 'options');
  461. $_config = $this->_config;
  462. $_exp = $this->_classes['exporter'];
  463. return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config, $_exp) {
  464. $options = $params['options'];
  465. $query = $params['query'];
  466. $args = $query->export($self, array('keys' => array('conditions', 'source', 'data')));
  467. $source = $args['source'];
  468. $data = $args['data'];
  469. if ($query->entity()) {
  470. $data = $_exp::get('update', $data);
  471. }
  472. if ($source == "{$_config['gridPrefix']}.files" && isset($data['update']['file'])) {
  473. $args['data']['_id'] = $self->invokeMethod('_saveFile', array($data['update']));
  474. }
  475. $update = $query->entity() ? $_exp::toCommand($data) : $data;
  476. if ($options['multiple'] && !preg_grep('/^\$/', array_keys($update))) {
  477. $update = array('$set' => $update);
  478. }
  479. if ($self->connection->{$source}->update($args['conditions'], $update, $options)) {
  480. $query->entity() ? $query->entity()->sync() : null;
  481. return true;
  482. }
  483. return false;
  484. });
  485. }
  486. /**
  487. * Delete document
  488. *
  489. * @param string $query
  490. * @param array $options
  491. * @return boolean
  492. * @filter
  493. */
  494. public function delete($query, array $options = array()) {
  495. $this->_checkConnection();
  496. $defaults = array('justOne' => false, 'safe' => false, 'fsync' => false);
  497. $options = array_intersect_key($options + $defaults, $defaults);
  498. return $this->_filter(__METHOD__, compact('query', 'options'), function($self, $params) {
  499. $query = $params['query'];
  500. $options = $params['options'];
  501. $args = $query->export($self, array('keys' => array('source', 'conditions')));
  502. return $self->connection->{$args['source']}->remove($args['conditions'], $options);
  503. });
  504. }
  505. /**
  506. * Executes calculation-related queries, such as those required for `count`.
  507. *
  508. * @param string $type Only accepts `count`.
  509. * @param mixed $query The query to be executed.
  510. * @param array $options Optional arguments for the `read()` query that will be executed
  511. * to obtain the calculation result.
  512. * @return integer Result of the calculation.
  513. */
  514. public function calculation($type, $query, array $options = array()) {
  515. $query->calculate($type);
  516. switch ($type) {
  517. case 'count':
  518. return $this->read($query, $options)->count();
  519. }
  520. }
  521. /**
  522. * Document relationships.
  523. *
  524. * @param string $class
  525. * @param string $type Relationship type, e.g. `belongsTo`.
  526. * @param string $name
  527. * @param array $config
  528. * @return array
  529. */
  530. public function relationship($class, $type, $name, array $config = array()) {
  531. $key = Inflector::camelize($type == 'belongsTo' ? $class::meta('name') : $name, false);
  532. $config += compact('name', 'type', 'key');
  533. $config['from'] = $class;
  534. $relationship = $this->_classes['relationship'];
  535. $defaultLinks = array(
  536. 'hasOne' => $relationship::LINK_EMBEDDED,
  537. 'hasMany' => $relationship::LINK_EMBEDDED,
  538. 'belongsTo' => $relationship::LINK_CONTAINED
  539. );
  540. $config += array('link' => $defaultLinks[$type]);
  541. return new $relationship($config);
  542. }
  543. /**
  544. * Formats `group` clauses for MongoDB.
  545. *
  546. * @param string|array $group The group clause.
  547. * @param object $context
  548. * @return array Formatted `group` clause.
  549. */
  550. public function group($group, $context) {
  551. if (!$group) {
  552. return;
  553. }
  554. if (is_string($group) && strpos($group, 'function') === 0) {
  555. return array('$keyf' => new MongoCode($group));
  556. }
  557. $group = (array) $group;
  558. foreach ($group as $i => $field) {
  559. if (is_int($i)) {
  560. $group[$field] = true;
  561. unset($group[$i]);
  562. }
  563. }
  564. return array('key' => $group);
  565. }
  566. /**
  567. * Maps incoming conditions with their corresponding MongoDB-native operators.
  568. *
  569. * @param array $conditions Array of conditions
  570. * @param object $context Context with which this method was called; currently
  571. * inspects the return value of `$context->type()`.
  572. * @return array Transformed conditions
  573. */
  574. public function conditions($conditions, $context) {
  575. $schema = array();
  576. $model = null;
  577. if (!$conditions) {
  578. return array();
  579. }
  580. if ($code = $this->_isMongoCode($conditions)) {
  581. return $code;
  582. }
  583. if ($context) {
  584. $model = $context->model();
  585. $schema = $context->schema();
  586. }
  587. return $this->_conditions($conditions, $model, $schema, $context);
  588. }
  589. protected function _conditions($conditions, $model, $schema, $context) {
  590. $castOpts = compact('schema') + array('first' => true, 'arrays' => false);
  591. foreach ($conditions as $key => $value) {
  592. if ($key === '$or' || $key === 'or' || $key === '||') {
  593. foreach ($value as $i => $or) {
  594. $value[$i] = $this->_conditions($or, $model, $schema, $context);
  595. }
  596. unset($conditions[$key]);
  597. $conditions['$or'] = $value;
  598. continue;
  599. }
  600. if (is_object($value)) {
  601. continue;
  602. }
  603. if (!is_array($value)) {
  604. $conditions[$key] = $this->cast(null, array($key => $value), $castOpts);
  605. continue;
  606. }
  607. $current = key($value);
  608. $isOpArray = (isset($this->_operators[$current]) || $current[0] === '$');
  609. if (!$isOpArray) {
  610. $data = array($key => $value);
  611. $conditions[$key] = array('$in' => $this->cast($model, $data, $castOpts));
  612. continue;
  613. }
  614. $operations = array();
  615. foreach ($value as $op => $val) {
  616. if (is_object($result = $this->_operator($model, $key, $op, $val, $schema))) {
  617. $operations = $result;
  618. break;
  619. }
  620. $operations += $this->_operator($model, $key, $op, $val, $schema);
  621. }
  622. $conditions[$key] = $operations;
  623. }
  624. return $conditions;
  625. }
  626. protected function _isMongoCode($conditions) {
  627. if ($conditions instanceof MongoCode) {
  628. return array('$where' => $conditions);
  629. }
  630. if (is_string($conditions)) {
  631. return array('$where' => new MongoCode($conditions));
  632. }
  633. }
  634. protected function _operator($model, $key, $op, $value, $schema) {
  635. $castOpts = compact('schema') + array('first' => true, 'arrays' => false);
  636. switch (true) {
  637. case !isset($this->_operators[$op]):
  638. return array($op => $this->cast($model, array($key => $value), $castOpts));
  639. case is_callable($this->_operators[$op]):
  640. return $this->_operators[$op]($key, $value);
  641. case is_array($this->_operators[$op]):
  642. $format = (is_array($value)) ? 'multiple' : 'single';
  643. $operator = $this->_operators[$op][$format];
  644. break;
  645. default:
  646. $operator = $this->_operators[$op];
  647. break;
  648. }
  649. return array($operator => $value);
  650. }
  651. /**
  652. * Return formatted identifiers for fields.
  653. *
  654. * MongoDB does nt require field identifer escaping; as a result,
  655. * this method is not implemented.
  656. *
  657. * @param array $fields Fields to be parsed
  658. * @param object $context
  659. * @return array Parsed fields array
  660. */
  661. public function fields($fields, $context) {
  662. return $fields ?: array();
  663. }
  664. /**
  665. * Return formatted clause for limit.
  666. *
  667. * MongoDB does nt require limit identifer formatting; as a result,
  668. * this method is not implemented.
  669. *
  670. * @param mixed $limit The `limit` clause to be formatted
  671. * @param object $context
  672. * @return mixed Formatted `limit` clause.
  673. */
  674. public function limit($limit, $context) {
  675. return $limit ?: 0;
  676. }
  677. /**
  678. * Return formatted clause for order.
  679. *
  680. * @param mixed $order The `order` clause to be formatted
  681. * @param object $context
  682. * @return mixed Formatted `order` clause.
  683. */
  684. public function order($order, $context) {
  685. switch (true) {
  686. case !$order:
  687. return array();
  688. case is_string($order):
  689. return array($order => 1);
  690. case is_array($order):
  691. foreach ($order as $key => $value) {
  692. if (!is_string($key)) {
  693. unset($order[$key]);
  694. $order[$value] = 1;
  695. continue;
  696. }
  697. if (is_string($value)) {
  698. $order[$key] = strtoupper($value) == 'ASC' ? 1 : -1;
  699. }
  700. }
  701. break;
  702. }
  703. return $order ?: array();
  704. }
  705. public function cast($entity, array $data, array $options = array()) {
  706. $defaults = array('schema' => null, 'first' => false);
  707. $options += $defaults;
  708. $model = null;
  709. $exists = false;
  710. if (!$data) {
  711. return $data;
  712. }
  713. if (is_string($entity)) {
  714. $model = $entity;
  715. $entity = null;
  716. $options['schema'] = $options['schema'] ?: $model::schema();
  717. } elseif ($entity) {
  718. $options['schema'] = $options['schema'] ?: $entity->schema();
  719. $model = $entity->model();
  720. if (is_a($entity, $this->_classes['entity'])) {
  721. $exists = $entity->exists();
  722. }
  723. }
  724. $schema = $options['schema'] ?: array('_id' => array('type' => 'id'));
  725. unset($options['schema']);
  726. $exporter = $this->_classes['exporter'];
  727. $options += compact('model', 'exists') + array('handlers' => $this->_handlers);
  728. return parent::cast($entity, $exporter::cast($data, $schema, $this, $options), $options);
  729. }
  730. protected function _checkConnection() {
  731. if (!$this->_isConnected && !$this->connect()) {
  732. throw new NetworkException("Could not connect to the database.");
  733. }
  734. }
  735. }
  736. ?>