├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Database │ └── MongoConnection.php ├── Logger │ └── MongoLogger.php ├── MongoCollection │ ├── BaseCollection.php │ ├── CollectionRegistry.php │ ├── MongoBehavior.php │ └── MongoBehaviorRegistry.php └── MongoEntity │ ├── EntityConverter.php │ └── RecursiveArrayObject.php └── tests ├── TestCase ├── Database │ └── MongoConnectionTest.php ├── Logger │ └── MongoLoggerIntegrationTest.php └── MongoCollection │ ├── BaseCollectionTest.php │ └── CollectionRegistryTest.php ├── TestCollection ├── DeleteEventCollection.php ├── FindEventCollection.php ├── InsertEventCollection.php ├── SaveEventCollection.php ├── StopEventCollection.php ├── TestBehavior.php ├── TestsCollection.php └── UpdateEventCollection.php ├── bootstrap.php └── testconfig.ini /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | vendor 3 | vendor/ 4 | .idea 5 | composer.lock 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | #This Travis config template file was taken from https://github.com/FriendsOfCake/travis 2 | language: php 3 | 4 | php: 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | 9 | sudo: false 10 | 11 | env: 12 | global: 13 | - DEFAULT=1 14 | 15 | matrix: 16 | fast_finish: true 17 | 18 | include: 19 | - php: 5.4 20 | env: PHPCS=1 DEFAULT=0 21 | 22 | - php: 5.5 23 | env: COVERALLS=1 DEFAULT=0 24 | 25 | - php: 5.6 26 | env: COVERALLS=1 DEFAULT=0 27 | 28 | before_install: phpenv config-add tests/testconfig.ini 29 | 30 | install: 31 | - composer self-update 32 | - composer install --prefer-dist --no-interaction --dev 33 | - composer update 34 | 35 | services: 36 | - mongodb 37 | 38 | before_script: 39 | - printf "\n" | pecl install mongo 40 | - sh -c "if [ '$COVERALLS' = '1' ]; then composer require --dev satooshi/php-coveralls:dev-master; fi" 41 | - mkdir -p build/logs 42 | 43 | - phpenv rehash 44 | - set +H 45 | - sleep 15 46 | - mongo mydb_test --eval 'db.addUser("travis", "test");' 47 | 48 | script: 49 | - sh -c "if [ '$DEFAULT' = '1' ]; then phpunit --configuration phpunit.xml; fi" 50 | - sh -c "if [ '$COVERALLS' = '1' ]; then phpunit --stderr --coverage-clover build/logs/clover.xml; fi" 51 | - sh -c "if [ '$COVERALLS' = '1' ]; then php vendor/bin/coveralls -c .coveralls.yml -v; fi" 52 | 53 | notifications: 54 | email: false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ### Version 0.1.0 4 | 5 | This is the initial release version of this plugin. The plugin as it is currently provides access to a MongoDB Datasource configured within the config/app.php file. 6 | 7 | ### Version 0.2.0 8 | 9 | * Collection level object support added. Developers can now define models inside of `src\Model\MongoCollection` that extend 10 | the `BaseCollection` class so that business logic can be abstracted and encapsulated. This class wraps all of the main data access methods 11 | provided by the Monga API's collection object. 12 | * A `CollectionRegistry` singleton has been added for constructing and retrieving custom Collections that extends the `BaseCollection` class. 13 | 14 | ### Version 0.3.0 15 | 16 | * CakeMonga now has query logging support. To enable query logging, define a custom logger class that extends `CakeMonga\Logger\MongoLogger` to define logging callbacks used by the MongoDB stream context. 17 | 18 | ### Version 0.4.0 19 | 20 | * Added an`initialize()` method to the BaseCollection class to fall more in line with the structure of the Table class. 21 | * Collections are no longer tied exclusively to the class name of the Collection. Now you can pass in a `collection` key to the $config array to `CollectionRegistry` to define the collection that should be accessed in MongoDB. 22 | * This version ties in the ability to declare events on your Collection classes such as `beforeSave()`, `beforeFind()` and the like. To find out more, check the wiki. 23 | 24 | ### Version 0.5.0 25 | 26 | * Custom Behavior support is now available on classes extended from the BaseCollection object. 27 | * The `MongoBehavior` and `MongoBehaviorRegistry` classes were added to bridge the gap between Table behaviors and Mongo Collection behaviors. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Wes King 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cakephp-monga 2 | 3 | [![Framework](https://img.shields.io/badge/Framework-CakePHP%203.x-orange.svg)](http://cakephp.org) 4 | [![Database](https://img.shields.io/badge/Database-MongoDB-green.svg)](https://www.mongodb.com) 5 | [![license](https://img.shields.io/github/license/LeWestopher/cakephp-monga.svg?maxAge=2592000)](https://github.com/LeWestopher/cakephp-monga/blob/master/LICENSE) 6 | [![Github All Releases](https://img.shields.io/packagist/dt/lewestopher/cakephp-monga.svg?maxAge=2592000)](https://packagist.org/packages/lewestopher/cakephp-monga) 7 | [![Travis](https://img.shields.io/travis/LeWestopher/cakephp-monga.svg?maxAge=2592000)](https://travis-ci.org/LeWestopher/cakephp-monga) 8 | [![Coverage Status](https://coveralls.io/repos/github/LeWestopher/cakephp-monga/badge.svg)](https://coveralls.io/github/LeWestopher/cakephp-monga) 9 | 10 | 11 | 12 | A plugin for accessing MongoDB NoSQL data stores in CakePHP 3.x. 13 | 14 | ### Requirements 15 | 16 | * Composer 17 | * CakePHP 3.x 18 | * PHP 5.4+ 19 | * MongoDB 20 | * Pecl Mongo extension 21 | 22 | ### Installation 23 | 24 | In your CakePHP root directory: run the following command: 25 | 26 | ``` 27 | composer require lewestopher/cakephp-monga 28 | ``` 29 | 30 | Then in your config/bootstrap.php in your project root, add the following snippet: 31 | 32 | ```php 33 | // In project_root/config/bootstrap.php: 34 | 35 | Plugin::load('CakeMonga'); 36 | ``` 37 | 38 | or you can use the following shell command to enable to plugin in your bootstrap.php automatically: 39 | 40 | ``` 41 | bin/cake plugin load CakeMonga 42 | ``` 43 | 44 | ### Extended Documentation 45 | 46 | For the extended docs, please visit our [Wiki](https://github.com/LeWestopher/cakephp-monga/wiki). 47 | 48 | ### Usage 49 | 50 | First, we define a new Datasource in our config/app.php file with our namespaced Connection class name: 51 | 52 | ```php 53 | // In project_root/config/app.php: 54 | 55 | 'Datasources' => [ 56 | 57 | 'default' => [ 58 | // ... Default SQL Datasource 59 | ], 60 | 61 | 'mongo_db' => [ 62 | 'className' => 'CakeMonga\Database\MongoConnection', 63 | ] 64 | ], 65 | ``` 66 | 67 | Then we can instantiate our MongoDB connection anywhere that we need in the application via the ConnectionManager class: 68 | 69 | ```php 70 | class ExampleController extends Controller 71 | { 72 | public function index() 73 | { 74 | $cake_monga = ConnectionManager::get('mongo_db'); 75 | } 76 | } 77 | ``` 78 | 79 | Then from there we can get our Monga instance by using the `connect()` method on the returned connection: 80 | 81 | ```php 82 | $cake_monga = ConnectionManager::get('mongo_db'); 83 | $mongodb = $cake_monga->connect(); // An instance of the Monga Connection object 84 | $database_list = $mongodb->listDatabases(); // We can call all of the methods on that Monga object provided by their API 85 | ``` 86 | 87 | Note that the $mongodb object instantiated above with the `connect()` method is the same object returned by Monga::connection() in the [Monga](https://github.com/thephpleague/monga) API: 88 | 89 | ```php 90 | $cake_monga = ConnectionManager::get('mongo_db'); 91 | $mongodb = $cake_monga->connect(); 92 | 93 | // Alternatively: 94 | 95 | $mongodb = Monga::connection($dns, $config_opts); 96 | ``` 97 | 98 | This information should help you make the bridge between instantiating the Datasource using CakePHP and utilizing the Monga API for data retrieval and saving. 99 | 100 | ### Configuration 101 | 102 | cakephp-monga accepts all of the same options in the Datasource configuration that can be passed into the MongoClient() object in PHP. Documentation for these options is defined [here](http://php.net/manual/en/mongoclient.construct.php). 103 | 104 | ```php 105 | // In project_root/config/app.php: 106 | 107 | 'Datasources' => [ 108 | 109 | 'default' => [ 110 | // ... Default SQL Datasource 111 | ], 112 | 113 | 'mongo_db' => [ 114 | 'className' => 'CakeMonga\Database\MongoConnection', 115 | 'logger' => null, 116 | 'authMechanism' => null, 117 | 'authSource' => null, 118 | 'connect' => true, 119 | 'connectTimeoutMS' => 60000, 120 | 'db' => null, 121 | 'dns' => 'mongodb://localhost:27017', 122 | 'fsync' => null, 123 | 'journal' => null, 124 | 'gssapiServiceName' => 'mongodb', 125 | 'username' => null, 126 | 'password' => null, 127 | 'readPreference' => null, 128 | 'readPreferenceTags' => null, 129 | 'replicaSet' => null, 130 | 'secondaryAcceptableLatencyMS' => 15, 131 | 'socketTimeoutMS' => 30000, 132 | 'ssl' => false, 133 | 'w' => 1, 134 | 'wTimeoutMS' => 10000 135 | ] 136 | ], 137 | ``` 138 | 139 | ### Connecting to a custom DNS using this library 140 | 141 | By default, this library connects to the `mongodb://localhost:27017` DNS string. You can specify a custom DNS to connect on by setting a 'dns' key on the connection's Datasource hash in the config/app.php file: 142 | 143 | ```php 144 | // In project_root/config/app.php: 145 | 146 | 'Datasources' => [ 147 | 148 | 'mongo_db' => [ 149 | 'className' => 'CakeMonga\Database\MongoConnection', 150 | 'dns' => 'mongodb://your.remote.host:27017' 151 | ] 152 | ], 153 | ``` 154 | 155 | ### API and Accessing your MongoDB Instance 156 | 157 | This plugin is a wrapper of the Mongo plugin by the League of Extraordinary Packages. To find out how to query, save, and update data within your Mongo collections, check out the [Monga documentation](https://github.com/thephpleague/monga). 158 | 159 | ### Defining a custom Collection class 160 | 161 | View the [Accessing Collections Extended Docs](https://github.com/LeWestopher/cakephp-monga/wiki/Accessing-Collections) page for more information on creating Collection classes. 162 | 163 | As of version 0.2.0, CakeMonga supports the usage of custom Collection classes. These 164 | custom classes are located with the `src/Model/MongoCollection` folder an extend the `CakeMonga\MongoCollection\BaseCollection` class. Now you can 165 | use these classes to encapsulate data layer logic into the appropriate class locations. These methods are direct abstractions of their Monga counterparts. 166 | You can view the docs for these methods on the [Monga API Docs](https://github.com/thephpleague/monga). The `BaseCollection` class provides the following methods for data access: 167 | 168 | ```php 169 | class BaseCollection { 170 | public function getCollection(); 171 | public function find($query = [], $fields = [], $findOne = false); 172 | public function findOne($query = [], $fields = []); 173 | public function drop(); 174 | public function listIndexes(); 175 | public function save($document, $options = []); 176 | public function update($values = [], $query = null, $options = []); 177 | public function insert(array $data, $options = []); 178 | public function remove($criteria, $options = []); 179 | public function truncate(); 180 | public function aggregate($aggregation = []); 181 | public function distinct($key, $query = []); 182 | public function count($query = []); 183 | } 184 | ```` 185 | 186 | Note that the MongoDB collection that is utilized by custom Collection classes is the tableized name of the Collection class itself. For example, 187 | `UsersCollection.php` would map to the `users` collection inside of your MongoDB instance. 188 | 189 | ### Retrieving a custom Collection model using CollectionRegistry 190 | 191 | As of 0.2.0, custom Collection models extended from `BaseCollection` can be retrieved using the `CollectionRegistry` singleton 192 | with the same syntax that `TableRegistry` employs: 193 | 194 | ```php 195 | // Define a custom User collection at src/Model/MongoCollection/UserCollection.php. 196 | use CakeMonga\MongoCollection\BaseCollection; 197 | 198 | class UsersCollection extends BaseCollection 199 | { 200 | public function getUsersNamedJohn() 201 | { 202 | return $this->find(['name' => 'John']); 203 | } 204 | } 205 | 206 | // We can retrieve this UsersCollection by using the static ::get() method on CollectionRegistry 207 | use CakeMonga\MongoCollection\CollectionRegistry; 208 | 209 | $users_collection = CollectionRegistry::get('Users'); 210 | ``` 211 | 212 | By default, the CollectionRegistry utilizes the default connection defined as 'mongo_db' in your app.php file. Want to use 213 | a different Mongo datasource? No problem, just pass in a datasource string to the 'connection' attribute of the config array for CollectionRegistry::get(): 214 | 215 | ```php 216 | $users_collection = CollectionRegistry::get('Users', [ 217 | 'connection' => 'secondary_mongo_datasource' 218 | ] 219 | ``` 220 | 221 | This would construct the UsersCollection class with a connection to the other datasource. 222 | 223 | ### Collection Event Hooks 224 | 225 | As of 0.4.0, you can now define the following events on your Collection classes: 226 | 227 | ```php 228 | use CakeMonga\MongoCollection\BaseCollection; 229 | 230 | class CustomCollection extends BaseCollection 231 | { 232 | public function beforeFind($event, $query, $fields, $findOne); 233 | public function beforeSave($event, $document); 234 | public function afterSave($event, $document) 235 | public function beforeInsert($event, $data); 236 | public function afterInsert($event, $results) 237 | public function beforeUpdate($event, $values, $query) 238 | public function afterUpdate($event, $document); 239 | public function beforeRemove($event, $criteria); 240 | public function afterRemove($event, $result, $criteria); 241 | } 242 | ``` 243 | 244 | You can find more information on Collection events on the [Accessing Collections Wiki Page](https://github.com/LeWestopher/cakephp-monga/wiki/Accessing-Collections#events) 245 | 246 | ### Custom Behaviors 247 | 248 | As of 0.5.0, you can attach Behaviors to classes extending the BaseCollection class in the same manner that you would to a Table object. To create a custom behavior for a BaseCollection, you should extend the `MongoBehavior` class instead of the regular `Behavior` class. 249 | 250 | You can find out more about Behaviors on collections on the [Accessing Collections Wiki Page](https://github.com/LeWestopher/cakephp-monga/wiki/Accessing-Collections#behaviors). 251 | 252 | ### Query Logging 253 | 254 | As of version 0.3.0, CakeMonga supports query logging via the Mongo logging context. To learn how to enable logging and create custom loggers, visit the [Query Logging Wiki Page](https://github.com/LeWestopher/cakephp-monga/wiki/Query-Logging). 255 | 256 | 257 | ### What is cakephp-monga? 258 | 259 | This plugin is a wrapper for the popular [Monga](https://github.com/thephpleague/monga) library provided by [The League of Extraordinary packages.](https://thephpleague.com/) In it's current form, this plugin is intended to get you quickly set up and running with access to a MongoDB instance so that you can access your data in your application. This plugin provides all of the same functionality that the Monga library provides in terms of building queries and retrieving your data. 260 | 261 | ### What is cakephp-monga not? 262 | 263 | This plugin is not currently intended as a drop in replacement for the SQL ORM provided by CakePHP core. While you could theoretically build an entire application using cakephp-monga as the data layer, this plugin does not have the kind of application level integration (Events, Logging, etc) that the current ORM does. Additionally, there is not abstraction layer for Database level, Collection level, or Entity level objects (EG - Defining methods on a supposed UserCollection.php, or creating a Mongo Entity at User.php), although this is on the roadmap for a future version very soon. 264 | 265 | Additionally, it's important to recognize that while certain relational features can be emulated within a MongoDB dataset, Mongo is still not an ACID compliant database. In the future, Collection level classes will be built for object abstraction that will implement CakePHP's Repository interface, but it should be noted that full ORM features will not be supported as Mongo is not a true object relational database. 266 | 267 | ### Roadmap 268 | 269 | Here are some of the features that I plan on integrating into this project very soon: 270 | 271 | - [X] Basic Connection object support for retrieving an instance of the Monga class for simple data retrieval. **Added in 0.1.0** 272 | - [X] Collection ~~and Entity level~~ abstraction layers (EG - UserCollection.php ~~and User.php~~ for Mongo) **Added in 0.2.0** 273 | - [ ] SSL Support via the stream context on the third argument of the MongoClient constructor 274 | - [X] Query logging via the stream context on the third argument of the MongoClient constructor **Added in 0.3.0** 275 | - [X] A CollectionRegistry class for retrieving Mongo collections with connection params already passed in. **Added in 0.2.0** 276 | - [X] Custom behavior support on the Collection level class **Added in 0.5.0** 277 | - [X] Events integration on the Collection level class **Added in 0.4.0** 278 | - [ ] Validation Support 279 | 280 | ### Support 281 | 282 | For bugs and feature requests, please use the [issues](https://github.com/LeWestopher/cakephp-monga/issues) section of this repository. 283 | 284 | ### Contributing 285 | 286 | To contribute to this plugin please follow a few basic rules. 287 | 288 | * Contributions must follow the [CakePHP coding standard](http://book.cakephp.org/3.0/en/contributing/cakephp-coding-conventions.html). 289 | * [Unit tests](http://book.cakephp.org/3.0/en/development/testing.html) are required. 290 | 291 | ### Change Log 292 | 293 | Yes, we have one of [those](https://github.com/LeWestopher/cakephp-monga/blob/master/CHANGELOG.md). 294 | 295 | ### Creators 296 | 297 | [Wes King](http://www.github.com/lewestopher) 298 | 299 | [Frank de Jonge](https://github.com/frankdejonge) - Creator of the Monga Dependency 300 | 301 | [Monga Contributors](https://github.com/thephpleague/monga/contributors) 302 | 303 | ### License 304 | 305 | Copyright 2016, Wes King 306 | 307 | Licensed under The MIT License Redistributions of files must retain the above copyright notice. 308 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lewestopher/cakephp-monga", 3 | "description": "CakeMonga plugin for CakePHP", 4 | "type": "cakephp-plugin", 5 | "license": "MIT", 6 | "require": { 7 | "php": ">=5.4.16", 8 | "cakephp/cakephp": "~3.0", 9 | "league/monga": "^1.2", 10 | "evert/phpdoc-md" : "~0.2.0", 11 | "mockery/mockery": "0.9.*" 12 | }, 13 | "require-dev": { 14 | "phpunit/phpunit": "*" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "CakeMonga\\": "src" 19 | } 20 | }, 21 | "autoload-dev": { 22 | "psr-4": { 23 | "CakeMonga\\Test\\": "tests", 24 | "Cake\\Test\\": "./vendor/cakephp/cakephp/tests" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ./tests/TestCase 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ./vendor/ 36 | ./vendor/ 37 | 38 | ./tests/ 39 | ./tests/ 40 | 41 | 42 | 43 | ./src/Database 44 | ./src/MongoCollection 45 | ./src/Logger 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/Database/MongoConnection.php: -------------------------------------------------------------------------------- 1 | getMongoConfig() method. Deprecated config options are not included. 66 | * 67 | * @var array 68 | */ 69 | protected $_mongoConfigOpts = ['authMechanism', 'authSource', 'connect', 'connectTimeoutMS', 'db', 'fsync', 70 | 'journal', 'gssapiServiceName', 'password', 'readPreference', 'readPreferenceTags', 'replicaSet', 71 | 'secondaryAcceptableLatencyMS', 'socketTimeoutMS', 'ssl', 'username', 'w', 'wTimeoutMS']; 72 | 73 | /** 74 | * Currently unused. The available context configuration closures used for logging queries. 75 | * 76 | * @var array 77 | */ 78 | protected $_mongoContextOpts = ['log_cmd_insertable', 'log_cmd_delete', 'log_cmd_update', 'log_write_batch', 79 | 'log_reply', 'log_getmore', 'log_killcursor']; 80 | 81 | /** 82 | * Define the SSL context options that are allowed for defining SSL options for our MongoDB connection 83 | * 84 | * @var array 85 | */ 86 | protected $_sslContextOpts = ['cafile', 'allow_self_signed', 'verify_peer', 'verify_peer_name', 'verify_expiry']; 87 | 88 | /** 89 | * MongoConnection constructor. 90 | * @param $config 91 | */ 92 | public function __construct($config = []) 93 | { 94 | $this->config($config); 95 | 96 | if (isset($config['logger'])) { 97 | $logger = new $config['logger']; 98 | $this->logger($logger); 99 | $this->logQueries(true); 100 | } 101 | } 102 | 103 | /** 104 | * Connects to our MongoDB instance and returns the connection object. If we have connected previously, returns the 105 | * old connection object that's already been established. 106 | * 107 | * @return Monga\Connection|null 108 | */ 109 | public function connect() 110 | { 111 | if ($this->_mongo) { 112 | return $this->_mongo; 113 | } 114 | 115 | if ($this->logger() && $this->logQueries()) { 116 | $logger = $this->buildStreamContext(); 117 | } else { 118 | $logger = []; 119 | } 120 | 121 | $this->_mongo = Monga::connection($this->dns(), $this->getMongoConfig(), $logger); 122 | $this->_connected = true; 123 | return $this->_mongo; 124 | } 125 | 126 | /** 127 | * Returns whether the current object has established a connection to the MongoDB instance or not. 128 | * 129 | * @return bool 130 | */ 131 | public function connected() 132 | { 133 | return $this->_connected; 134 | } 135 | 136 | /** 137 | * Returns the config name of this connection defined as the connection array key in the Datasources array in 138 | * our app.php file. 139 | * 140 | * @return string 141 | */ 142 | public function configName() 143 | { 144 | if (empty($this->_config['name'])) { 145 | return ''; 146 | } 147 | return $this->_config['name']; 148 | } 149 | 150 | /** 151 | * Gets and Sets our configuration array for our connection class. 152 | * 153 | * @param null $config 154 | * @return null 155 | */ 156 | public function config($config = null) 157 | { 158 | if ($this->_config) { 159 | return $this->_config; 160 | } 161 | $this->_config = $config; 162 | return $this->_config; 163 | } 164 | 165 | /** 166 | * Gets or Sets the DNS string for our connection in our configuration array. If no DNS string is provided, the 167 | * default localhost DNS is returned. 168 | * 169 | * @param null $dns 170 | * @return null|string 171 | */ 172 | public function dns($dns = null) 173 | { 174 | if ($dns) { 175 | $this->_config['dns'] = $dns; 176 | return $dns; 177 | } 178 | 179 | if (isset($this->_config['dns'])) { 180 | return $this->_config['dns']; 181 | } 182 | 183 | return 'mongodb://localhost:27017'; 184 | } 185 | 186 | /** 187 | * Helper method for returning an associative array with only the keys provided in the second argument. Used for 188 | * building our Monga/MongoClient configuration without passing in unneeded keys that will throw errors. 189 | * 190 | * @param $array 191 | * @param array $includedKeys 192 | * @return array 193 | */ 194 | protected function arrayInclude($array, Array $includedKeys) 195 | { 196 | $config = []; 197 | foreach($includedKeys as $key){ 198 | if (isset($array[$key])) { 199 | $config[$key] = $array[$key]; 200 | } 201 | } 202 | return $config; 203 | } 204 | 205 | /** 206 | * Wraps $this->arrayInclude(...) to provide our Monga/MongoClient configuration array. 207 | * 208 | * @return array 209 | */ 210 | public function getMongoConfig() 211 | { 212 | return $this->arrayInclude($this->config(), $this->_mongoConfigOpts); 213 | } 214 | 215 | public function getSSLConfig() 216 | { 217 | return $this->arrayInclude($this->config()['ssl_opts'], $this->_sslContextOpts); 218 | } 219 | 220 | /** 221 | * Mock method included to satisfy CakePHP connection requirements. 222 | * 223 | * @param callable $transaction 224 | * @return boolean 225 | */ 226 | public function transactional(callable $transaction) 227 | { 228 | return true; 229 | } 230 | 231 | /** 232 | * Mock method included to satisfy CakePHP connection requirements. 233 | * 234 | * @param callable $operation 235 | * @return boolean 236 | */ 237 | public function disableConstraints(callable $operation) 238 | { 239 | return true; 240 | } 241 | 242 | /** 243 | * Currently unused. Getter and Setter method for enabling or disabling query logging on the connection class. 244 | * @param null $enable 245 | * @return mixed 246 | */ 247 | public function logQueries($enable = null) 248 | { 249 | if ($enable === null) { 250 | return $this->_logQueries; 251 | } 252 | $this->_logQueries = $enable; 253 | } 254 | 255 | /** 256 | * Currently unused. Getter and Setter method for defining the Query Logger object on our connection class. In a 257 | * future version, this logger object will provide the stream context for logging queries to the Monga/MongoClient 258 | * constructor's third argument. 259 | * 260 | * @param null $instance 261 | * @return Logger 262 | */ 263 | public function logger($instance = null) 264 | { 265 | if ($instance) { 266 | $this->_logger = $instance; 267 | } 268 | return $this->_logger; 269 | } 270 | 271 | /** 272 | * Returns the default Database for a connection as defined by $config['name'] 273 | * 274 | * @return Monga\Database|\MongoDB 275 | */ 276 | public function getDefaultDatabase() 277 | { 278 | if (!isset($this->_config['database'])) { 279 | throw new Exception(sprintf('You have not configured a default database for Datasource %s yet.', $this->_config['name'])); 280 | } 281 | $db = $this->_config['database']; 282 | return $this->connect()->database($db); 283 | } 284 | 285 | /** 286 | * Builds our context object for passing in query logging options as well as the SSL context for HTTPS 287 | * 288 | * @return array 289 | */ 290 | protected function buildStreamContext() 291 | { 292 | $opts = []; 293 | 294 | // If we have a logger defined, merge the context options from our logger with the context array 295 | if ($this->logQueries() && $logger = $this->logger()) { 296 | $opts['mongodb'] = $logger->getContext(); 297 | } 298 | 299 | $context = stream_context_create($opts); 300 | return ['context' => $context]; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/Logger/MongoLogger.php: -------------------------------------------------------------------------------- 1 | [$this, 'onInsert'], 104 | 'log_cmd_delete' => [$this, 'onDelete'], 105 | 'log_cmd_update' => [$this, 'onUpdate'], 106 | 'log_insert' => [$this, 'onInsert'], 107 | 'log_delete' => [$this, 'onDelete'], 108 | 'log_update' => [$this, 'onUpdate'], 109 | 'log_batchinsert' => [$this, 'onBatchInsert'], 110 | 'log_write_batch' => [$this, 'onBatchInsert'], 111 | 'log_query' => [$this, 'onQuery'] 112 | ]; 113 | return $context; 114 | } 115 | } -------------------------------------------------------------------------------- /src/MongoCollection/BaseCollection.php: -------------------------------------------------------------------------------- 1 | setConnection($connection); 53 | $this->database = $connection->getDefaultDatabase(); 54 | $eventManager = $behaviors = $collection_name = null; 55 | 56 | if (!empty($config['eventManager'])) { 57 | $eventManager = $config['eventManager']; 58 | } 59 | 60 | if (!empty($config['behaviors'])) { 61 | $behaviors = $config['behaviors']; 62 | } 63 | 64 | if (!empty($config['collection'])) { 65 | $collection_name = $config['collection']; 66 | } else { 67 | $collection_name = $this->getMongoCollectionName(); 68 | } 69 | 70 | $this->_eventManager = $eventManager ?: new EventManager(); 71 | $this->_eventManager->on($this); 72 | 73 | $this->_behaviors = $behaviors ?: new MongoBehaviorRegistry(); 74 | $this->_behaviors->setCollection($this); 75 | 76 | $this->setMongaCollection($collection_name); 77 | $this->initialize($config); 78 | return $this; 79 | } 80 | 81 | /** 82 | * Initialize a Collection instance. Called after the constructor. Allows you to define any extra parameters 83 | * on the collection once it's been constructed. 84 | * 85 | * @param array $config 86 | */ 87 | public function initialize($config = []) 88 | { 89 | 90 | } 91 | /** 92 | * Returns the behavior registry for this table. 93 | * 94 | * @return \Cake\ORM\BehaviorRegistry The BehaviorRegistry instance. 95 | */ 96 | public function behaviors() 97 | { 98 | return $this->_behaviors; 99 | } 100 | /** 101 | * Add a behavior. 102 | * 103 | * Adds a behavior to this table's behavior collection. Behaviors 104 | * provide an easy way to create horizontally re-usable features 105 | * that can provide trait like functionality, and allow for events 106 | * to be listened to. 107 | * 108 | * Example: 109 | * 110 | * Load a behavior, with some settings. 111 | * 112 | * ``` 113 | * $this->addBehavior('Tree', ['parent' => 'parentId']); 114 | * ``` 115 | * 116 | * Behaviors are generally loaded during Table::initialize(). 117 | * 118 | * @param string $name The name of the behavior. Can be a short class reference. 119 | * @param array $options The options for the behavior to use. 120 | * @return void 121 | * @throws \RuntimeException If a behavior is being reloaded. 122 | * @see \Cake\ORM\Behavior 123 | */ 124 | public function addBehavior($name, array $options = []) 125 | { 126 | $this->_behaviors->load($name, $options); 127 | } 128 | /** 129 | * Removes a behavior from this table's behavior registry. 130 | * 131 | * Example: 132 | * 133 | * Remove a behavior from this table. 134 | * 135 | * ``` 136 | * $this->removeBehavior('Tree'); 137 | * ``` 138 | * 139 | * @param string $name The alias that the behavior was added with. 140 | * @return void 141 | * @see \Cake\ORM\Behavior 142 | */ 143 | public function removeBehavior($name) 144 | { 145 | $this->_behaviors->unload($name); 146 | } 147 | /** 148 | * Check if a behavior with the given alias has been loaded. 149 | * 150 | * @param string $name The behavior alias to check. 151 | * @return bool Whether or not the behavior exists. 152 | */ 153 | public function hasBehavior($name) 154 | { 155 | return $this->_behaviors->has($name); 156 | } 157 | 158 | /** 159 | * Defines a list of events implemented on the Collection class for usage by the Collection's EventManager. 160 | * 161 | * @return array 162 | */ 163 | public function implementedEvents() 164 | { 165 | $eventMap = [ 166 | 'Model.beforeFind' => 'beforeFind', // Done 167 | 'Model.beforeSave' => 'beforeSave', // Done 168 | 'Model.afterSave' => 'afterSave', // Done 169 | 'Model.beforeInsert' => 'beforeInsert', 170 | 'Model.afterInsert' => 'afterInsert', 171 | 'Model.beforeUpdate' => 'beforeUpdate', 172 | 'Model.afterUpdate' => 'afterUpdate', 173 | 'Model.beforeRemove' => 'beforeRemove', 174 | 'Model.afterRemove' => 'afterRemove', 175 | ]; 176 | $events = []; 177 | 178 | foreach ($eventMap as $event => $method) { 179 | if (!method_exists($this, $method)) { 180 | continue; 181 | } 182 | $events[$event] = $method; 183 | } 184 | 185 | return $events; 186 | } 187 | 188 | /** 189 | * Returns the current connection injected into this collection. 190 | * 191 | * @return mixed 192 | */ 193 | public function getConnection() 194 | { 195 | return $this->_connection; 196 | } 197 | 198 | /** 199 | * Allows you to set a new Connection object onto the current Collection. Note that if this collection has already 200 | * been instantiated, setting a new Connection object will not change the current Collection object that has been 201 | * set on `$this->_collection`. 202 | * 203 | * @param MongoConnection $connection 204 | * @return MongoConnection 205 | */ 206 | public function setConnection(MongoConnection $connection) 207 | { 208 | $this->_connection = $connection; 209 | return $this->_connection; 210 | } 211 | 212 | /** 213 | * Infers the name of the Mongo collection inside of the database based on the namespace of the current class. 214 | * 215 | * @return string 216 | */ 217 | public function getMongoCollectionName() 218 | { 219 | // Get full namespaced class name 220 | $class = get_class($this); 221 | $split_array = explode('\\', $class); 222 | // Split into namespaces and get the last namespace as the class name 223 | $final = $split_array[count($split_array) - 1]; 224 | // Cut the final 10 characters off of the class (Collection from UsersCollection) and return just the collection 225 | return Inflector::tableize(substr($final, 0, -10)); 226 | } 227 | 228 | /** 229 | * Sets a new collection property based on the lowercase, tableized collection name passed in as the first arg. 230 | * 231 | * @param string $collection_name 232 | * @return mixed 233 | */ 234 | public function setMongaCollection($collection_name) 235 | { 236 | $this->collection = $this->database->collection($collection_name); 237 | return $this->collection; 238 | } 239 | /** 240 | * Returns the MongaCollection object. 241 | * 242 | * @return mixed 243 | */ 244 | public function getCollection() 245 | { 246 | return $this->collection; 247 | } 248 | 249 | /** 250 | * Wraps Mongoa's native `find()` function on their Collection object. 251 | * 252 | * If the beforeFind() method is defined, calls that method with this methods arguments and allows direct 253 | * modification of the $query and $fields arguments before the find query is called. 254 | * 255 | * @param array $query 256 | * @param array $fields 257 | * @param bool $findOne 258 | * @return mixed 259 | */ 260 | public function find($query = [], $fields = [], $findOne = false) 261 | { 262 | $before_find_event = $this->dispatchEvent('Model.beforeFind', compact('query', 'fields', 'findOne')); 263 | 264 | if (!empty($before_find_event->result['query']) && $before_find_event->result['query'] !== $query) { 265 | $query = $before_find_event->result['query']; 266 | } 267 | 268 | if (!empty($before_find_event->result['fields']) && $before_find_event->result['fields'] !== $fields) { 269 | $fields = $before_find_event->result['fields']; 270 | } 271 | 272 | if ($before_find_event->isStopped()) { 273 | return false; 274 | } 275 | 276 | return $this->collection->find($query, $fields, $findOne); 277 | } 278 | 279 | /** 280 | * Wraps Monga's native `findOne()` method on their Collection object. 281 | * 282 | * If the beforeFind() method is defined, calls that method with this methods arguments and allows direct 283 | * modification of the $query and $fields arguments before the find query is called. 284 | * 285 | * @param array $query 286 | * @param array $fields 287 | * @return mixed 288 | */ 289 | public function findOne($query = [], $fields = []) 290 | { 291 | $findOne = true; 292 | $before_find_event = $this->dispatchEvent('Model.beforeFind', compact('query', 'fields', 'findOne')); 293 | 294 | if (!empty($before_find_event->result['query']) && $before_find_event->result['query'] !== $query) { 295 | $query = $before_find_event->result['query']; 296 | } 297 | 298 | if (!empty($before_find_event->result['fields']) && $before_find_event->result['fields'] !== $fields) { 299 | $fields = $before_find_event->result['fields']; 300 | } 301 | 302 | if ($before_find_event->isStopped()) { 303 | return false; 304 | } 305 | 306 | return $this->collection->findOne($query, $fields); 307 | } 308 | 309 | /** 310 | * Wraps Monga's native `indexes()` method on their Collection object. 311 | * 312 | * @param Closure $callback 313 | * @return mixed 314 | */ 315 | public function indexes(Closure $callback) 316 | { 317 | return $this->collection->indexes($callback); 318 | } 319 | 320 | /** 321 | * Wraps Monga's native `setMaxRetries()` method on their Collection object. 322 | * 323 | * @param int $amount 324 | * @return mixed 325 | */ 326 | public function setMaxRetries($amount) 327 | { 328 | return $this->collection->setMaxRetries($amount); 329 | } 330 | 331 | /** 332 | * Wraps Monga's native `drop()` method on their Collection object. 333 | * 334 | * @return mixed 335 | */ 336 | public function drop() 337 | { 338 | return $this->collection->drop(); 339 | } 340 | 341 | /** 342 | * Wraps Monga's native `listIndexes()` method on their Collection object. 343 | * 344 | * @return mixed 345 | */ 346 | public function listIndexes() 347 | { 348 | return $this->collection->listIndexes(); 349 | } 350 | 351 | /** 352 | * Beginnings of a `get()` method to satisfy CakePHP's RepositoryInterface abstraction requirements. 353 | * 354 | * @param $id 355 | * @param array $fields 356 | * @return mixed 357 | */ 358 | public function get($id, $fields = []) 359 | { 360 | return $this->collection->findOne([ 361 | '_id' => $id 362 | ], $fields); 363 | } 364 | 365 | /** 366 | * Wraps Monga's native `save()` method on their Collection object. 367 | * 368 | * If the beforeSave() method is defined, calls that method with this methods arguments and allows direct 369 | * modification of the $document to be saved before the save is committed to the MongoDB instance. 370 | * 371 | * If the afterSave() method is defined, it is called after the save is successfully committed to the database. 372 | * 373 | * @param $document 374 | * @param array $options 375 | * @return mixed 376 | */ 377 | public function save($document, $options = []) 378 | { 379 | $before_save_event = $this->dispatchEvent('Model.beforeSave', compact('document', 'options')); 380 | 381 | if (!empty($before_save_event->result['document']) && $before_save_event->result['document'] !== $document) { 382 | $document = $before_save_event->result['document']; 383 | } 384 | 385 | if ($before_save_event->isStopped()) { 386 | return false; 387 | } 388 | 389 | $results = $this->collection->save($document, $options); 390 | 391 | $after_save_event = $this->dispatchEvent('Model.afterSave', compact('results', 'document', 'options')); 392 | 393 | return $results; 394 | } 395 | 396 | /** 397 | * Wraps Monga's native 'update()' method on their Collection object. 398 | * 399 | * If the beforeUpdate() method is defined, calls that method with this methods arguments and allows direct 400 | * modification of the $values and $query arguments before the update query is called. 401 | * 402 | * If the afterUpdate() method is defined, it is called after the update is successfully committed to the database. 403 | * 404 | * @param array $values 405 | * @param null $query 406 | * @param array $options 407 | * @return mixed 408 | */ 409 | public function update($values = [], $query = null, $options = []) 410 | { 411 | $before_update_event = $this->dispatchEvent('Model.beforeUpdate', compact('values', 'query')); 412 | 413 | if (!empty($before_update_event->result['values']) && $before_update_event->result['values'] !== $values) { 414 | $values = $before_update_event->result['values']; 415 | } 416 | 417 | if (!empty($before_update_event->result['query']) && $before_update_event->result['query'] !== $query) { 418 | $query = $before_update_event->result['query']; 419 | } 420 | 421 | if ($before_update_event->isStopped()) { 422 | return false; 423 | } 424 | 425 | $results = $this->collection->update($values, $query, $options); 426 | 427 | $after_update_event = $this->dispatchEvent('Model.afterUpdate', compact('results', 'query', 'values')); 428 | 429 | return $results; 430 | } 431 | 432 | /** 433 | * Wraps Monga's native `insert()` method on their Collection object. 434 | * 435 | * If the beforeInsert() method is defined, calls that method with this methods arguments and allows direct 436 | * modification of the $data argument before the insert query is called. 437 | * 438 | * If the afterInsert() method is defined, it is called after the insert is successfully committed to the database. 439 | * 440 | * @param array $data 441 | * @param array $options 442 | * @return mixed 443 | */ 444 | public function insert(array $data, $options = []) 445 | { 446 | $before_insert_event = $this->dispatchEvent('Model.beforeInsert', compact('data', 'options')); 447 | 448 | if (!empty($before_insert_event->result['data']) && $before_insert_event->result['data'] !== $data) { 449 | $data = $before_insert_event->result['data']; 450 | } 451 | 452 | if ($before_insert_event->isStopped()) { 453 | return false; 454 | } 455 | 456 | $results = $this->collection->insert($data, $options); 457 | 458 | $after_insert_event = $this->dispatchEvent('Model.afterInsert', compact('results', 'data', 'options')); 459 | 460 | return $results; 461 | } 462 | 463 | /** 464 | * Wraps Monga's native `remove()` method on their Collection object. 465 | * 466 | * If the beforeRemove() method is defined, calls that method with this methods arguments and allows direct 467 | * modification of the $criteria argument before the remove query is called. 468 | * 469 | * If the afterRemove() method is defined, it is called after the remove is successfully committed to the database. 470 | * 471 | * @param $criteria 472 | * @param array $options 473 | * @return mixed 474 | */ 475 | public function remove($criteria, $options = []) 476 | { 477 | $before_remove_event = $this->dispatchEvent('Model.beforeRemove', compact('criteria')); 478 | 479 | if (!empty($before_remove_event->result['criteria']) && $before_remove_event->result['criteria'] !== $criteria) { 480 | $criteria = $before_remove_event->result['criteria']; 481 | } 482 | 483 | if ($before_remove_event->isStopped()) { 484 | return false; 485 | } 486 | 487 | $result = $this->collection->remove($criteria, $options); 488 | 489 | $after_insert_event = $this->dispatchEvent('Model.afterRemove', compact('result', 'criteria')); 490 | 491 | return $result; 492 | } 493 | 494 | /** 495 | * Wraps Monga's native `truncate()` method on their Collection object. 496 | * 497 | * @return mixed 498 | */ 499 | public function truncate() 500 | { 501 | return $this->collection->truncate(); 502 | } 503 | 504 | /** 505 | * Wraps Monga's native `aggregate()` method on their Collection object. 506 | * 507 | * @param array $aggregation 508 | * @return mixed 509 | */ 510 | public function aggregate($aggregation = []) 511 | { 512 | return $this->collection->aggregate($aggregation); 513 | } 514 | 515 | /** 516 | * Wraps Monga's native `distinct()` method on their Collection object. 517 | * 518 | * @param $key 519 | * @param array $query 520 | * @return mixed 521 | */ 522 | public function distinct($key, $query = []) 523 | { 524 | return $this->collection->distinct($key, $query); 525 | } 526 | 527 | /** 528 | * Wraps Monga's native `count()` method on their Collection object. 529 | * 530 | * @param array $query 531 | * @return mixed 532 | */ 533 | public function count($query = []) 534 | { 535 | return $this->collection->count($query); 536 | } 537 | 538 | /** 539 | * Wraps Monga's native `setCollection()` method on their Collection object. 540 | * 541 | * @param \MongoCollection $collection 542 | * @return mixed 543 | */ 544 | public function setCollection($collection) 545 | { 546 | return $this->collection->setCollection($collection); 547 | } 548 | 549 | public function __call($method, $args) 550 | { 551 | if ($this->_behaviors && $this->_behaviors->hasMethod($method)) { 552 | return $this->_behaviors->call($method, $args); 553 | } 554 | /* 555 | if (preg_match('/^find(?:\w+)?By/', $method) > 0) { 556 | return $this->_dynamicFinder($method, $args); 557 | }*/ 558 | 559 | throw new \BadMethodCallException( 560 | sprintf('Unknown method "%s"', $method) 561 | ); 562 | } 563 | } -------------------------------------------------------------------------------- /src/MongoCollection/CollectionRegistry.php: -------------------------------------------------------------------------------- 1 | _collection property to a BaseCollection instead of the traditional Behavior constructor 27 | * that sets $this->_table to a Table object. 28 | * 29 | * @param BaseCollection $collection 30 | * @param array $config 31 | */ 32 | public function __construct(BaseCollection $collection, array $config = []) 33 | { 34 | $config = $this->_resolveMethodAliases( 35 | 'implementedFinders', 36 | $this->_defaultConfig, 37 | $config 38 | ); 39 | $config = $this->_resolveMethodAliases( 40 | 'implementedMethods', 41 | $this->_defaultConfig, 42 | $config 43 | ); 44 | $this->_collection = $collection; 45 | $this->config($config); 46 | $this->initialize($config); 47 | } 48 | } -------------------------------------------------------------------------------- /src/MongoCollection/MongoBehaviorRegistry.php: -------------------------------------------------------------------------------- 1 | _collection instead of 27 | * $this->_table to a Table object. 28 | * 29 | * @param null $collection 30 | */ 31 | public function __construct($collection = null) 32 | { 33 | if ($collection !== null) { 34 | $this->setCollection($collection); 35 | } 36 | } 37 | /** 38 | * Sets the BaseCollection object for the registry and attaches the event manager from the collection to the current 39 | * event manager on the registry class. 40 | * 41 | * @param $collection 42 | */ 43 | public function setCollection($collection) 44 | { 45 | $this->_collection = $collection; 46 | $this->eventManager($collection->eventManager()); 47 | } 48 | 49 | /** 50 | * Overridden _create() method that injects a BaseCollection object into a MongoBehavior rather than a Behavior 51 | * object. 52 | * 53 | * @param string $class 54 | * @param string $alias 55 | * @param array $config 56 | * @return mixed 57 | */ 58 | protected function _create($class, $alias, $config) 59 | { 60 | $instance = new $class($this->_collection, $config); 61 | $enable = isset($config['enabled']) ? $config['enabled'] : true; 62 | if ($enable) { 63 | $this->eventManager()->on($instance); 64 | } 65 | $methods = $this->_getMethods($instance, $class, $alias); 66 | $this->_methodMap += $methods['methods']; 67 | $this->_finderMap += $methods['finders']; 68 | 69 | return $instance; 70 | } 71 | } -------------------------------------------------------------------------------- /src/MongoEntity/EntityConverter.php: -------------------------------------------------------------------------------- 1 | getArrayCopy(); 18 | } 19 | } -------------------------------------------------------------------------------- /src/MongoEntity/RecursiveArrayObject.php: -------------------------------------------------------------------------------- 1 | $val) { 18 | if (!is_object($val)) { 19 | continue; 20 | } 21 | $o = new RecursiveArrayObject($val); 22 | $resultArray[$key] = $o->getArrayCopy(); 23 | } 24 | return $resultArray; 25 | } 26 | } -------------------------------------------------------------------------------- /tests/TestCase/Database/MongoConnectionTest.php: -------------------------------------------------------------------------------- 1 | 4 | * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) 5 | * 6 | * Licensed under The Open Group Test Suite License 7 | * Redistributions of files must retain the above copyright notice. 8 | * 9 | * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) 10 | * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests 11 | * @since 2.2.0 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | */ 14 | namespace CakeMonga\Test\TestCase\Database; 15 | 16 | use Cake\TestSuite\TestCase; 17 | use CakeMonga\Database\MongoConnection; 18 | 19 | class MongoConnectionTest extends TestCase 20 | { 21 | public function setUp() 22 | { 23 | parent::setUp(); 24 | } 25 | 26 | public function tearDown() 27 | { 28 | parent::tearDown(); 29 | } 30 | 31 | public function testConfigName() 32 | { 33 | $mongo = new MongoConnection(['name' => 'mongo_db']); 34 | $this->assertEquals('mongo_db', $mongo->configName()); 35 | } 36 | 37 | public function testGetDns() 38 | { 39 | $mongo = new MongoConnection(['dns' => 'mongodb://some.remote.address:27015']); 40 | $this->assertEquals('mongodb://some.remote.address:27015', $mongo->dns()); 41 | } 42 | 43 | public function testSetDns() 44 | { 45 | $mongo = new MongoConnection(); 46 | $mongo->dns('mongodb://some.remote.address:27015'); 47 | $this->assertEquals('mongodb://some.remote.address:27015', $mongo->dns()); 48 | } 49 | 50 | public function testGetMongoConfig() 51 | { 52 | $mongo = new MongoConnection([ 53 | 'ssl' => true, 54 | 'password' => 'check', 55 | 'gssapiServiceName' => '11111', 56 | 'exclude_1' => 'a', 57 | 'exclude_2' => 'b', 58 | 'exclude_3' => 'c' 59 | ]); 60 | 61 | $expected = [ 62 | 'ssl' => true, 63 | 'password' => 'check', 64 | 'gssapiServiceName' => '11111' 65 | ]; 66 | 67 | $this->assertEquals($expected, $mongo->getMongoConfig()); 68 | 69 | } 70 | 71 | public function testRawConfig() 72 | { 73 | $mongo = new MongoConnection([ 74 | 'ssl' => true, 75 | 'password' => 'check', 76 | 'gssapiServiceName' => '11111', 77 | 'exclude_1' => 'a', 78 | 'exclude_2' => 'b', 79 | 'exclude_3' => 'c' 80 | ]); 81 | 82 | $expected = [ 83 | 'ssl' => true, 84 | 'password' => 'check', 85 | 'gssapiServiceName' => '11111', 86 | 'exclude_1' => 'a', 87 | 'exclude_2' => 'b', 88 | 'exclude_3' => 'c' 89 | ]; 90 | 91 | $this->assertEquals($expected, $mongo->config()); 92 | } 93 | 94 | public function testConnect() 95 | { 96 | $mongo = new MongoConnection(); 97 | $connection = $mongo->connect(); 98 | 99 | $this->assertEquals('League\Monga\Connection', get_class($connection)); 100 | } 101 | 102 | public function testConnected() 103 | { 104 | $mongo = new MongoConnection(); 105 | $mongo->connect(); 106 | 107 | $this->assertEquals(true, $mongo->connected()); 108 | } 109 | 110 | public function testNotConnected() 111 | { 112 | $mongo = new MongoConnection(); 113 | 114 | $this->assertEquals(false, $mongo->connected()); 115 | } 116 | 117 | public function testNoDefaultDatabase() 118 | { 119 | $mongo = new MongoConnection(['name' => 'mongo_db']); 120 | $expected = sprintf('You have not configured a default database for Datasource %s yet.', 'mongo_db'); 121 | try { 122 | $mongo->getDefaultDatabase(); 123 | } catch (\Exception $e) { 124 | $result = $e->getMessage(); 125 | } 126 | $this->assertEquals($expected, $result); 127 | } 128 | 129 | public function testWithLogger() 130 | { 131 | $mongo = new MongoConnection(['logger' => 'CakeMonga\Logger\MongoLogger']); 132 | $this->assertInstanceOf('CakeMonga\Logger\MongoLogger', $mongo->logger()); 133 | } 134 | 135 | public function testTransactionalNoOp() 136 | { 137 | $mongo = new MongoConnection(); 138 | $result = $mongo->transactional(function () {}); 139 | $this->assertEquals(true, $result); 140 | } 141 | 142 | public function testDisableConstraintsNoOp() 143 | { 144 | $mongo = new MongoConnection(); 145 | $result = $mongo->disableConstraints(function(){}); 146 | $this->assertEquals(true, $result); 147 | } 148 | 149 | public function testEnableQueryLogging() 150 | { 151 | $mongo = new MongoConnection(); 152 | $mongo->logQueries(true); 153 | $result = $mongo->logQueries(); 154 | $this->assertEquals(true, $result); 155 | } 156 | 157 | public function testDisableQueryLogging() 158 | { 159 | $mongo = new MongoConnection(); 160 | $mongo->logQueries(false); 161 | $result = $mongo->logQueries(); 162 | $this->assertEquals(false, $result); 163 | } 164 | 165 | public function testEmptyConfigNameString() 166 | { 167 | $mongo = new MongoConnection(); 168 | $this->assertEquals('', $mongo->configName()); 169 | } 170 | } -------------------------------------------------------------------------------- /tests/TestCase/Logger/MongoLoggerIntegrationTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('CakeMonga\Logger\MongoLogger') 25 | ->setMethods(['onInsert']) 26 | ->getMock(); 27 | 28 | $mock->expects($this->once()) 29 | ->method('onInsert') 30 | ->with( 31 | $this->anything(), 32 | $this->anything(), 33 | $this->anything() 34 | ); 35 | 36 | $connection->logger($mock); 37 | $connection->logQueries(true); 38 | $coll = $connection->connect()->database('local')->collection('test'); 39 | $coll->insert(['test' => 1]); 40 | $coll->truncate(); 41 | unset($connection); 42 | } 43 | 44 | public function testOnUpdate() 45 | { 46 | $connection = new MongoConnection(); 47 | 48 | $mock = $this->getMockBuilder('CakeMonga\Logger\MongoLogger') 49 | ->setMethods(['onUpdate']) 50 | ->getMock(); 51 | 52 | $mock->expects($this->once()) 53 | ->method('onUpdate') 54 | ->with( 55 | $this->anything(), 56 | $this->anything(), 57 | $this->anything(), 58 | $this->anything() 59 | ); 60 | 61 | $connection->logger($mock); 62 | $connection->logQueries(true); 63 | $coll = $connection->connect()->database('local')->collection('test'); 64 | $coll->insert(['test' => 'abcd', 'count' => 1]); 65 | $obj = $coll->findOne(function ($query) { 66 | $query->where('test', 'abcd'); 67 | }); 68 | $obj['count'] = 2; 69 | $coll->save($obj); 70 | $coll->truncate(); 71 | unset($connection); 72 | } 73 | 74 | public function testOnDelete() 75 | { 76 | $connection = new MongoConnection(); 77 | 78 | $mock = $this->getMockBuilder('CakeMonga\Logger\MongoLogger') 79 | ->setMethods(['onDelete']) 80 | ->getMock(); 81 | 82 | $mock->expects($this->exactly(2)) 83 | ->method('onDelete') 84 | /*->with( 85 | $this->anything(), 86 | $this->anything(), 87 | $this->anything(), 88 | $this->anything(), 89 | $this->anything() 90 | )*/; 91 | 92 | $connection->logger($mock); 93 | $connection->logQueries(true); 94 | $coll = $connection->connect()->database('local')->collection('test'); 95 | $coll->insert(['test' => 'abcd', 'count' => 1]); 96 | $coll->remove(['test' => 'abcd', 'count' => 1]); 97 | $coll->truncate(); 98 | unset($connection); 99 | } 100 | 101 | public function testOnBatchInsert() 102 | { 103 | $connection = new MongoConnection(); 104 | 105 | $mock = $this->getMockBuilder('CakeMonga\Logger\MongoLogger') 106 | ->setMethods(['onBatchInsert']) 107 | ->getMock(); 108 | 109 | $mock->expects($this->once()) 110 | ->method('onBatchInsert') 111 | ->with( 112 | $this->anything(), 113 | $this->anything(), 114 | $this->anything(), 115 | $this->anything() 116 | ); 117 | 118 | $connection->logger($mock); 119 | $connection->logQueries(true); 120 | $coll = $connection->connect()->database('local')->collection('test'); 121 | $coll->insert([ 122 | ['test' => 'abcd', 'count' => 1], 123 | ['test' => 'abcd', 'count' => 2] 124 | ]); 125 | $coll->truncate(); 126 | unset($connection); 127 | } 128 | 129 | public function testOnQuery() 130 | { 131 | $connection = new MongoConnection(); 132 | 133 | $mock = $this->getMockBuilder('CakeMonga\Logger\MongoLogger') 134 | ->setMethods(['onQuery']) 135 | ->getMock(); 136 | 137 | $mock->expects($this->once()) 138 | ->method('onQuery') 139 | ->with( 140 | $this->anything(), 141 | $this->anything(), 142 | $this->anything() 143 | ); 144 | 145 | 146 | $connection->logger($mock); 147 | $connection->logQueries(true); 148 | $coll = $connection->connect()->database('local')->collection('test'); 149 | $results = $coll->find()->toArray(); 150 | unset($connection); 151 | } 152 | 153 | } -------------------------------------------------------------------------------- /tests/TestCase/MongoCollection/BaseCollectionTest.php: -------------------------------------------------------------------------------- 1 | 'CakeMonga\Database\MongoConnection', 33 | 'database' => 'local' 34 | ]); 35 | } 36 | 37 | public function setUp() 38 | { 39 | parent::setUp(); 40 | $connection = ConnectionManager::get('testing'); 41 | $this->collection = new BaseCollection($connection); 42 | $this->database = $connection->connect()->database('__unit_testing__'); 43 | } 44 | 45 | public function tearDown() 46 | { 47 | parent::tearDown(); 48 | $this->collection->drop(); 49 | $this->database = null; 50 | } 51 | 52 | public function testGetCollectionName() 53 | { 54 | $this->assertEquals('bases', $this->collection->getMongoCollectionName()); 55 | } 56 | 57 | public function testSetConnection() 58 | { 59 | ConnectionManager::config('alt', [ 60 | 'className' => 'CakeMonga\Database\MongoConnection', 61 | 'database' => 'local' 62 | ]); 63 | 64 | $conn = ConnectionManager::get('alt'); 65 | $coll = new BaseCollection($conn); 66 | 67 | $coll->setConnection(ConnectionManager::get('testing')); 68 | $this->assertEquals('testing', $coll->getConnection()->configName()); 69 | 70 | } 71 | 72 | public function testSetMaxRetries() 73 | { 74 | $this->collection->setMaxRetries(5); 75 | $reflection = new \ReflectionObject($this->collection->collection); 76 | $property = $reflection->getProperty('maxRetries'); 77 | $property->setAccessible(true); 78 | $this->assertEquals(5, $property->getValue($this->collection->collection)); 79 | } 80 | public function testCount() 81 | { 82 | $result = $this->collection->count(); 83 | $this->assertEquals(0, $result); 84 | $this->collection->getCollection()->insert(['this' => 'value']); 85 | $result = $this->collection->count(); 86 | $this->assertEquals(1, $result); 87 | } 88 | /** 89 | * @expectedException InvalidArgumentException 90 | */ 91 | public function testCountException() 92 | { 93 | $this->collection->count(false); 94 | } 95 | 96 | public function testCountClosure() 97 | { 98 | $where = function ($query) { 99 | $query->where('name', 'Frank'); 100 | }; 101 | $result = $this->collection->count($where); 102 | $this->assertEquals(0, $result); 103 | $this->collection->getCollection()->insert(['name' => 'Frank']); 104 | $result = $this->collection->count($where); 105 | $this->assertEquals(1, $result); 106 | } 107 | /** 108 | * @test 109 | * @covers MongoCollection::distinct() 110 | */ 111 | public function testDistinct() 112 | { 113 | $collection = m::mock('MongoCollection'); 114 | $collection->shouldReceive('distinct') 115 | ->with('surname', ['age' => 25]) 116 | ->once() 117 | ->andReturn(['randomstring']); 118 | $expected = ['randomstring']; 119 | $c = new Collection($collection); 120 | $result = $c->distinct('surname', ['age' => 25]); 121 | $this->assertEquals($expected, $result); 122 | } 123 | 124 | public function testDistinctQuery() 125 | { 126 | $connection = ConnectionManager::get('testing'); 127 | $collection = new BaseCollection($connection); 128 | $collection->insert([ 129 | ['test' => true, 'check' => 'a'], 130 | ['test' => true, 'check' => 'a'], 131 | ['test' => false, 'check' => 'b'] 132 | ]); 133 | $results = $collection->distinct('check'); 134 | $this->assertEquals(2, count($results)); 135 | } 136 | 137 | /** 138 | * @covers MongoCollection::distinct() 139 | */ 140 | public function testDistinctClosure() 141 | { 142 | $collection = m::mock('MongoCollection'); 143 | $collection->shouldReceive('distinct') 144 | ->with('surname', ['age' => 25]) 145 | ->once() 146 | ->andReturn(['randomstring']); 147 | $expected = ['randomstring']; 148 | $c = new Collection($collection); 149 | $result = $c->distinct('surname', function ($w) { 150 | $w->where('age', 25); 151 | }); 152 | $this->assertEquals($expected, $result); 153 | } 154 | /** 155 | * @test 156 | * @covers MongoCollection::aggregate() 157 | */ 158 | public function testAggregation() 159 | { 160 | $collection = m::mock('MongoCollection'); 161 | $collection->shouldReceive('aggregate') 162 | ->with(['randomstring']) 163 | ->once() 164 | ->andReturn(['randomstring']); 165 | $expected = ['randomstring']; 166 | $c = new Collection($collection); 167 | $result = $c->aggregate(['randomstring']); 168 | $this->assertEquals($expected, $result); 169 | } 170 | 171 | /** 172 | * @covers MongoCollection::aggregate() 173 | */ 174 | public function testAggregationClosure() 175 | { 176 | $collection = m::mock('MongoCollection'); 177 | $collection->shouldReceive('aggregate') 178 | ->with([ 179 | ['$limit' => 1], 180 | ]) 181 | ->once() 182 | ->andReturn(['randomstring']); 183 | $expected = ['randomstring']; 184 | $c = new Collection($collection); 185 | $result = $c->aggregate(function ($a) { 186 | $a->limit(1); 187 | }); 188 | $this->assertEquals($expected, $result); 189 | } 190 | 191 | public function testIndexes() 192 | { 193 | $result = false; 194 | $callback = function () use (&$result) { 195 | $result = true; 196 | }; 197 | $this->collection->indexes($callback); 198 | $this->assertTrue($result); 199 | } 200 | 201 | public function testDrop() 202 | { 203 | $result = $this->collection->drop(); 204 | $this->assertFalse($result); 205 | $this->collection->insert(['name' => 'Frank']); 206 | $result = $this->collection->drop(); 207 | $this->assertTrue($result); 208 | } 209 | public function testTruncate() 210 | { 211 | $result = $this->collection->truncate(); 212 | $this->assertTrue($result); 213 | } 214 | public function testRemove() 215 | { 216 | $result = $this->collection->remove([]); 217 | $this->assertTrue($result); 218 | } 219 | public function testRemoveWhere() 220 | { 221 | $this->collection->getCollection()->insert(['name' => 'Frank']); 222 | $this->assertEquals(1, $this->collection->count()); 223 | $result = $this->collection->remove(['name' => 'Bert']); 224 | $this->assertTrue($result); 225 | $this->assertEquals(1, $this->collection->count()); 226 | $result = $this->collection->remove(['name' => 'Frank']); 227 | $this->assertTrue($result); 228 | $this->assertEquals(0, $this->collection->count()); 229 | } 230 | public function testRemoveWhereClosure() 231 | { 232 | $closure = function ($query) { 233 | $query->where('name', 'Frank'); 234 | }; 235 | $closure2 = function ($query) { 236 | $query->where('name', 'Bert'); 237 | }; 238 | $this->collection->getCollection()->insert(['name' => 'Frank']); 239 | $this->assertEquals(1, $this->collection->count()); 240 | $result = $this->collection->remove($closure2); 241 | $this->assertTrue($result); 242 | $this->assertEquals(1, $this->collection->count()); 243 | $result = $this->collection->remove($closure); 244 | $this->assertTrue($result); 245 | $this->assertEquals(0, $this->collection->count()); 246 | } 247 | 248 | /** 249 | * @expectedException InvalidArgumentException 250 | */ 251 | public function testInvalidRemove() 252 | { 253 | $this->collection->remove(false); 254 | } 255 | 256 | public function testListIndexes() 257 | { 258 | $this->assertInternalType('array', $this->collection->listIndexes()); 259 | } 260 | 261 | public function testFind() 262 | { 263 | $result = $this->collection->find(); 264 | } 265 | 266 | public function testFindOneEmpty() 267 | { 268 | $result = $this->collection->findOne(); 269 | $this->assertNull($result); 270 | } 271 | 272 | public function testFindOneNotEmpty() 273 | { 274 | $this->collection->insert(['some' => 'value']); 275 | $result = $this->collection->findOne(); 276 | $this->assertInternalType('array', $result); 277 | $this->assertEquals('value', $result['some']); 278 | } 279 | 280 | public function testFindOneWithPostFindAction() 281 | { 282 | $result = $this->collection->findOne(function ($query) { 283 | $query->where('some', 'value') 284 | ->orderBy('some', 'asc') 285 | ->skip(0) 286 | ->limit(1); 287 | }); 288 | $this->assertNull($result); 289 | } 290 | 291 | public function testFindOneWithPostFindActionWithResult() 292 | { 293 | $this->collection->insert(['some' => 'value']); 294 | $result = $this->collection->findOne(function ($query) { 295 | $query->where('some', 'value') 296 | ->orderBy('some', 'asc') 297 | ->skip(0) 298 | ->limit(1); 299 | }); 300 | $this->assertInternalType('array', $result); 301 | $this->assertEquals('value', $result['some']); 302 | } 303 | 304 | /** 305 | * @expectedException InvalidArgumentException 306 | */ 307 | public function testInvalidFind() 308 | { 309 | $this->collection->find(false); 310 | } 311 | 312 | public function testInsertOne() 313 | { 314 | $result = $this->collection->insert(['new' => 'entry']); 315 | $this->assertInstanceOf('MongoId', $result); 316 | } 317 | 318 | public function testInsertMultiple() 319 | { 320 | $result = $this->collection->insert([ 321 | ['number' => 'one'], 322 | ['number' => 'two'], 323 | ]); 324 | $this->assertCount(2, $result); 325 | $this->assertContainsOnlyInstancesOf('MongoId', $result); 326 | } 327 | 328 | public function testSave() 329 | { 330 | $item = ['name' => 'Frank']; 331 | $result = $this->collection->save($item); 332 | $this->assertTrue($result); 333 | } 334 | 335 | public function testUpdate() 336 | { 337 | $result = $this->collection->update(['name' => 'changed']); 338 | $this->assertTrue($result); 339 | } 340 | 341 | 342 | public function testUpdateClosure() 343 | { 344 | $result = $this->collection->update(function ($query) { 345 | $query->set('name', 'changed') 346 | ->increment('viewcount', 2); 347 | }); 348 | $this->assertTrue($result); 349 | } 350 | 351 | /** 352 | * @expectedException InvalidArgumentException 353 | */ 354 | public function testInvalidUpdate() 355 | { 356 | $result = $this->collection->update(false); 357 | } 358 | 359 | public function testGet() 360 | { 361 | $this->collection->insert(['alpha' => 'beta']); 362 | $result = $this->collection->findOne(['alpha' => 'beta']); 363 | $id = $result['_id']; 364 | $final = $this->collection->get($id); 365 | $this->assertEquals($id, $final['_id']); 366 | } 367 | 368 | public function testSetCollection() 369 | { 370 | $original = $this->collection->getCollection()->getCollection(); 371 | $originalHash = spl_object_hash($original); 372 | $new = $this->database->collection('__different__')->getCollection(); 373 | $newHash = spl_object_hash($new); 374 | $this->collection->setCollection($new); 375 | $reflection = new \ReflectionObject($this->collection->getCollection()); 376 | $property = $reflection->getProperty('collection'); 377 | $property->setAccessible(true); 378 | $this->assertInstanceOf('MongoCollection', $property->getValue($this->collection->getCollection())); 379 | $this->assertEquals($newHash, spl_object_hash($property->getValue($this->collection->getCollection()))); 380 | $this->assertNotEquals($originalHash, spl_object_hash($property->getValue($this->collection))); 381 | $this->collection->setCollection($original); 382 | } 383 | 384 | public function testBeforeSave() 385 | { 386 | $connection = ConnectionManager::get('testing'); 387 | $collection = new SaveEventCollection($connection); 388 | $results = $collection->save(['test' => true]); 389 | $this->assertTrue($collection->_beforeSave); 390 | $collection->truncate(); 391 | } 392 | 393 | public function testBeforeSaveDocumentAlter() 394 | { 395 | $connection = ConnectionManager::get('testing'); 396 | $collection = new SaveEventCollection($connection); 397 | $collection->save(['test' => true, 'check' => '1']); 398 | // Testing beforeSave by modifying $document's 'test' key to equal false instead 399 | $result = $collection->findOne(['test' => false]); 400 | $this->assertEquals(1, $result['check']); 401 | $collection->truncate(); 402 | } 403 | 404 | public function testAfterSave() 405 | { 406 | $connection = ConnectionManager::get('testing'); 407 | $collection = new SaveEventCollection($connection); 408 | $collection->save(['test' => true]); 409 | $this->assertTrue($collection->_afterSave); 410 | $collection->truncate(); 411 | } 412 | 413 | public function testBeforeFind() 414 | { 415 | $connection = ConnectionManager::get('testing'); 416 | $collection = new FindEventCollection($connection); 417 | $collection->save(['test' => true]); 418 | $results = $collection->find(['test' => true]); 419 | $this->assertTrue($collection->_beforeFind); 420 | $collection->truncate(); 421 | } 422 | 423 | public function testBeforeFindQueryAlter() 424 | { 425 | $connection = ConnectionManager::get('testing'); 426 | $collection = new FindEventCollection($connection); 427 | $collection->insert([ 428 | ['test' => true], 429 | ['test' => false] 430 | ]); 431 | // TestsCollection.php users beforeFind() to modify the $query array to search for ['test' => false] instead 432 | $results = $collection->findOne(['test' => true]); 433 | $this->assertFalse($results['test']); 434 | $collection->truncate(); 435 | } 436 | 437 | public function testBeforeFindFieldsAlter() 438 | { 439 | $connection = ConnectionManager::get('testing'); 440 | $collection = new FindEventCollection($connection); 441 | $collection->insert([ 442 | ['test' => true, 'excluded' => true] 443 | ]); 444 | $results = $collection->findOne(['test' => true], ['test', 'excluded']); 445 | // TestsCollection.php uses beforeFind() to modify the $fields array to only include the 'test' field 446 | $this->assertFalse(isset($results['excluded'])); 447 | $collection->truncate(); 448 | } 449 | 450 | public function testBeforeInsertDocumentAlter() 451 | { 452 | $connection = ConnectionManager::get('testing'); 453 | $collection = new InsertEventCollection($connection); 454 | $collection->insert([ 455 | ['test' => true, 'excluded' => true] 456 | ]); 457 | $results = $collection->findOne(['test' => true]); 458 | // TestsCollection.php uses beforeFind() to modify the $fields array to only include the 'test' field 459 | $this->assertFalse($results['excluded']); 460 | $collection->truncate(); 461 | } 462 | 463 | public function testBeforeAndAfterUpdate() 464 | { 465 | $connection = ConnectionManager::get('testing'); 466 | $collection = new UpdateEventCollection($connection); 467 | $collection->insert([ 468 | ['test' => true, 'check' => 1], 469 | ['test' => true, 'check' => 2] 470 | ]); 471 | $collection->update(['test' => false], ['check' => 2]); 472 | // Testing beforeSave by modifying $document's 'test' key to equal false instead 473 | $this->assertTrue($collection->_beforeUpdate); 474 | $this->assertTrue($collection->_afterUpdate); 475 | $collection->truncate(); 476 | } 477 | 478 | public function testBeforeUpdateDataAlter() 479 | { 480 | $connection = ConnectionManager::get('testing'); 481 | $collection = new UpdateEventCollection($connection); 482 | $collection->insert([ 483 | ['test' => true, 'check' => 1], 484 | ['test' => true, 'check' => 2], 485 | ['test' => true, 'check' => 3] 486 | ]); 487 | $collection->update(['test' => false], ['check' => 2]); 488 | $result = $collection->findOne(['check' => 3]); 489 | // Testing beforeSave by modifying $document's 'test' key to equal false instead 490 | $this->assertEquals(50, $result['test']); 491 | $collection->truncate(); 492 | } 493 | 494 | public function testBeforeAndAfterRemove() 495 | { 496 | $connection = ConnectionManager::get('testing'); 497 | $collection = new DeleteEventCollection($connection); 498 | $collection->insert([ 499 | ['test' => true, 'check' => 1], 500 | ['test' => true, 'check' => 2], 501 | ['test' => true, 'check' => 3] 502 | ]); 503 | $collection->remove(['test' => true]); 504 | $this->assertTrue($collection->_beforeRemove); 505 | $this->assertTrue($collection->_afterRemove); 506 | $collection->truncate(); 507 | } 508 | 509 | public function testBeforeRemoveCriteriaAlter() 510 | { 511 | $connection = ConnectionManager::get('testing'); 512 | $collection = new DeleteEventCollection($connection); 513 | $collection->insert([ 514 | ['test' => true, 'check' => 1], 515 | ['test' => true, 'check' => 2], 516 | ['test' => false, 'check' => 3] 517 | ]); 518 | $collection->remove(['test' => true]); 519 | $this->assertEquals(2, $collection->count()); 520 | } 521 | 522 | public function testBeforeFindStop() 523 | { 524 | $connection = ConnectionManager::get('testing'); 525 | $collection = new StopEventCollection($connection, ['stop_event' => 'find']); 526 | $collection->insert([ 527 | ['test' => true, 'check' => 1], 528 | ['test' => true, 'check' => 2], 529 | ['test' => false, 'check' => 3] 530 | ]); 531 | $results = $collection->find(); 532 | $this->assertFalse($results); 533 | $collection->truncate(); 534 | } 535 | 536 | public function testBeforeFindOneStop() 537 | { 538 | $connection = ConnectionManager::get('testing'); 539 | $collection = new StopEventCollection($connection, ['stop_event' => 'find']); 540 | $collection->insert([ 541 | ['test' => true, 'check' => 1], 542 | ['test' => true, 'check' => 2], 543 | ['test' => false, 'check' => 3] 544 | ]); 545 | $results = $collection->findOne(); 546 | $this->assertFalse($results); 547 | $collection->truncate(); 548 | } 549 | 550 | public function testBeforeSaveStop() 551 | { 552 | $connection = ConnectionManager::get('testing'); 553 | $collection = new StopEventCollection($connection, ['stop_event' => 'save']); 554 | $results = $collection->save(['test' => true]); 555 | $this->assertEquals(0, $collection->count()); 556 | $this->assertFalse($results); 557 | $collection->truncate(); 558 | } 559 | 560 | public function testBeforeInsertStop() 561 | { 562 | $connection = ConnectionManager::get('testing'); 563 | $collection = new StopEventCollection($connection, ['stop_event' => 'insert']); 564 | $results = $collection->insert(['test' => true]); 565 | $this->assertEquals(0, $collection->count()); 566 | $this->assertFalse($results); 567 | $collection->truncate(); 568 | } 569 | 570 | public function testBeforeDeleteStop() 571 | { 572 | $connection = ConnectionManager::get('testing'); 573 | $collection = new StopEventCollection($connection, ['stop_event' => 'remove']); 574 | $results = $collection->insert(['test' => true]); 575 | $collection->remove(['test' => true]); 576 | $this->assertEquals(1, $collection->count()); 577 | $collection->truncate(); 578 | } 579 | 580 | public function testBeforeUpdateStop() 581 | { 582 | $connection = ConnectionManager::get('testing'); 583 | $collection = new StopEventCollection($connection, ['stop_event' => 'update']); 584 | $results = $collection->insert(['test' => true]); 585 | $collection->update(['test' => false]); 586 | $result = $collection->findOne(['test' => true]); 587 | $this->assertTrue($result['test']); 588 | $collection->truncate(); 589 | } 590 | 591 | public function testHasBehaviors() 592 | { 593 | $connection = ConnectionManager::get('testing'); 594 | $collection = new BaseCollection($connection); 595 | $this->assertEquals('CakeMonga\MongoCollection\MongoBehaviorRegistry', get_class($collection->behaviors())); 596 | } 597 | 598 | /** 599 | * @expectedException \BadMethodCallException 600 | */ 601 | public function testThrowsBadMethodException() 602 | { 603 | $connection = ConnectionManager::get('testing'); 604 | $collection = new BaseCollection($connection); 605 | $collection->findThisMethodDoesntExist(); 606 | } 607 | 608 | public function testAddBehavior() 609 | { 610 | $connection = ConnectionManager::get('testing'); 611 | $collection = new BaseCollection($connection); 612 | $collection->addBehavior('CakeMonga\Test\TestCollection\TestBehavior'); 613 | $this->assertTrue($collection->hasBehavior('CakeMonga\Test\TestCollection\TestBehavior')); 614 | } 615 | 616 | public function testBehaviorAddsMethod() 617 | { 618 | $connection = ConnectionManager::get('testing'); 619 | $collection = new BaseCollection($connection); 620 | $collection->addBehavior('CakeMonga\Test\TestCollection\TestBehavior'); 621 | $this->assertEquals('Hello World!', $collection->getHelloWorld()); 622 | } 623 | 624 | public function testBehaviorGetsRemoved() 625 | { 626 | $connection = ConnectionManager::get('testing'); 627 | $collection = new BaseCollection($connection); 628 | $collection->addBehavior('CakeMonga\Test\TestCollection\TestBehavior'); 629 | $collection->removeBehavior('CakeMonga\Test\TestCollection\TestBehavior'); 630 | $this->assertFalse($collection->hasBehavior('CakeMonga\Test\TestCollection\TestBehavior')); 631 | } 632 | 633 | public function testBehaviorEventsWork() 634 | { 635 | $connection = ConnectionManager::get('testing'); 636 | $collection = new BaseCollection($connection, ['stop_event' => 'save']); 637 | $collection->addBehavior('CakeMonga\Test\TestCollection\TestBehavior'); 638 | $results = $collection->save(['test' => true]); 639 | $one = $collection->findOne(['test' => true]); 640 | $this->assertEquals(1, $one['check']); 641 | $collection->truncate(); 642 | } 643 | 644 | public function testInjectedCollectionIntoBehaviorRegistry() 645 | { 646 | $connection = ConnectionManager::get('testing'); 647 | $collection_2 = new BaseCollection($connection); 648 | $registry = new MongoBehaviorRegistry($collection_2); 649 | $collection = new BaseCollection($connection, ['behaviors' => $registry]); 650 | $collection->addBehavior('CakeMonga\Test\TestCollection\TestBehavior'); 651 | $results = $collection->save(['test' => true]); 652 | $one = $collection->findOne(['test' => true]); 653 | $this->assertEquals(1, $one['check']); 654 | $collection->truncate(); 655 | } 656 | } -------------------------------------------------------------------------------- /tests/TestCase/MongoCollection/CollectionRegistryTest.php: -------------------------------------------------------------------------------- 1 | 'CakeMonga\Database\MongoConnection', 26 | 'database' => 'local' 27 | ]); 28 | CollectionRegistry::clear(); 29 | CollectionRegistry::defaultNamespace(); 30 | CollectionRegistry::setDefaultConnection('mongo_db'); 31 | } 32 | 33 | public function tearDown() 34 | { 35 | parent::tearDown(); 36 | ConnectionManager::drop('testing'); 37 | } 38 | 39 | public function testSetNamespace() 40 | { 41 | CollectionRegistry::setNamespace("App\\Test\\Namespace\\"); 42 | $this->assertEquals("App\\Test\\Namespace\\", CollectionRegistry::getNamespace()); 43 | } 44 | 45 | public function testGetNamespace() 46 | { 47 | $namespace = CollectionRegistry::getNamespace(); 48 | $this->assertEquals("App\\Model\\MongoCollection\\", $namespace); 49 | } 50 | 51 | public function testGetInstances() 52 | { 53 | $this->assertEquals([], CollectionRegistry::getInstances()); 54 | } 55 | 56 | public function testDefaultNamespace() 57 | { 58 | $this->assertEquals("App\\Model\\MongoCollection\\", CollectionRegistry::getNamespace()); 59 | } 60 | 61 | public function testGet() 62 | { 63 | CollectionRegistry::setNamespace("CakeMonga\\Test\\TestCollection\\"); 64 | $test_collection = CollectionRegistry::get('Tests', ['connection' => 'testing']); 65 | $this->assertEquals('hello', $test_collection->world()); 66 | } 67 | 68 | public function testClear() 69 | { 70 | CollectionRegistry::setNamespace("CakeMonga\\Test\\TestCollection\\"); 71 | $test_collection = CollectionRegistry::get('Tests', ['connection' => 'testing']); 72 | CollectionRegistry::clear(); 73 | $this->assertEquals([], CollectionRegistry::getInstances()); 74 | } 75 | 76 | public function testDefaultConnectionString() 77 | { 78 | $this->assertEquals('mongo_db', CollectionRegistry::getDefaultConnection()); 79 | } 80 | 81 | public function testSetDefaultConnectionString() 82 | { 83 | CollectionRegistry::setDefaultConnection('new_default_connection'); 84 | $this->assertEquals('new_default_connection', CollectionRegistry::getDefaultConnection()); 85 | } 86 | 87 | public function testCustomConnectionConfig() 88 | { 89 | ConnectionManager::config('mongo_db', [ 90 | 'className' => 'CakeMonga\Database\MongoConnection', 91 | 'database' => 'local' 92 | ]); 93 | 94 | CollectionRegistry::setNamespace("CakeMonga\\Test\\TestCollection\\"); 95 | $test_collection = CollectionRegistry::get('Tests'); 96 | $conn_name = $test_collection->getConnection()->configName(); 97 | $this->assertEquals('mongo_db', $conn_name); 98 | 99 | ConnectionManager::drop('mongo_db'); 100 | } 101 | 102 | public function testCachedCollectionObject() 103 | { 104 | CollectionRegistry::setNamespace("CakeMonga\\Test\\TestCollection\\"); 105 | $users_collection = CollectionRegistry::get('Tests', ['connection' => 'testing']); 106 | $cached_version = CollectionRegistry::get('Tests'); 107 | $this->assertEquals($users_collection->getConnection()->configName(), $cached_version->getConnection()->configName()); 108 | } 109 | } -------------------------------------------------------------------------------- /tests/TestCollection/DeleteEventCollection.php: -------------------------------------------------------------------------------- 1 | result['criteria'] = ['test' => false]; 24 | $this->_beforeRemove = true; 25 | } 26 | 27 | public function afterRemove($event, $result, $criteria) 28 | { 29 | $this->_afterRemove = true; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /tests/TestCollection/FindEventCollection.php: -------------------------------------------------------------------------------- 1 | result['query'] = ['test' => false]; 23 | $event->result['fields'] = ['test']; 24 | $this->_beforeFind = true; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /tests/TestCollection/InsertEventCollection.php: -------------------------------------------------------------------------------- 1 | result['data'] = ['test' => true, 'excluded' => false]; 24 | $this->_beforeInsert = true; 25 | } 26 | 27 | public function afterInsert($event, $results) 28 | { 29 | $this->_afterInsert = true; 30 | } 31 | } -------------------------------------------------------------------------------- /tests/TestCollection/SaveEventCollection.php: -------------------------------------------------------------------------------- 1 | result['document'] = ['test' => false, 'check' => 1]; 24 | $this->_beforeSave = true; 25 | } 26 | 27 | public function afterSave($event, $entity) 28 | { 29 | $this->_afterSave = true; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /tests/TestCollection/StopEventCollection.php: -------------------------------------------------------------------------------- 1 | _eventTrigger = $config['stop_event']; 28 | } 29 | 30 | public function beforeFind($event, $query, $fields, $findOne) 31 | { 32 | if ($this->_eventTrigger === 'find') { 33 | $event->stopPropagation(); 34 | } 35 | } 36 | 37 | public function beforeSave($event, $document) 38 | { 39 | if ($this->_eventTrigger === 'save') { 40 | $event->stopPropagation(); 41 | } 42 | } 43 | 44 | public function beforeInsert($event, $data) 45 | { 46 | if ($this->_eventTrigger === 'insert') { 47 | $event->stopPropagation(); 48 | } 49 | } 50 | 51 | public function beforeUpdate($event, $values, $query) 52 | { 53 | if ($this->_eventTrigger === 'update') { 54 | $event->stopPropagation(); 55 | } 56 | } 57 | 58 | public function beforeRemove($event, $criteria) 59 | { 60 | if ($this->_eventTrigger === 'remove') { 61 | $event->stopPropagation(); 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /tests/TestCollection/TestBehavior.php: -------------------------------------------------------------------------------- 1 | result['document'] = ['test' => true, 'check' => 1]; 24 | } 25 | } -------------------------------------------------------------------------------- /tests/TestCollection/TestsCollection.php: -------------------------------------------------------------------------------- 1 | result['values'] = ['test' => 50, 'check' => 3]; 24 | $event->result['query'] = ['check' => 3]; 25 | $this->_beforeUpdate = true; 26 | } 27 | 28 | public function afterUpdate($event, $entity) 29 | { 30 | $this->_afterUpdate = true; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'App', 41 | 'paths' => [ 42 | 'plugins' => [ROOT . 'Plugin' . DS], 43 | 'templates' => [ROOT . 'App' . DS . 'Template' . DS] 44 | ] 45 | ]); 46 | if (!getenv('db_dsn')) { 47 | putenv('db_dsn=sqlite:///:memory:'); 48 | } 49 | ConnectionManager::config('test', ['url' => getenv('db_dsn')]); 50 | Plugin::load('CakeMonga', [ 51 | 'path' => dirname(dirname(__FILE__)) . DS, 52 | ]); -------------------------------------------------------------------------------- /tests/testconfig.ini: -------------------------------------------------------------------------------- 1 | extension="mongo.so" --------------------------------------------------------------------------------