├── README.md ├── models └── behaviors │ └── linkable.php └── tests ├── cases └── models │ └── behaviors │ └── linkable.test.php └── fixtures ├── comment_fixture.php ├── generic_fixture.php ├── legacy_company_fixture.php ├── legacy_product_fixture.php ├── order_item_fixture.php ├── post_fixture.php ├── posts_tag_fixture.php ├── profile_fixture.php ├── shipment_fixture.php ├── tag_fixture.php └── user_fixture.php /README.md: -------------------------------------------------------------------------------- 1 | # Linkable Plugin 2 | CakePHP plugin, PHP 5 3 | 4 | ## Introduction ## 5 | 6 | Linkable is a lightweight approach for data mining on deep relations between models. Joins tables based on model relations to easily enable right to left find operations. 7 | 8 | ## Requirements ## 9 | - CakePHP 1.2.x or 1.3.x 10 | - PHP 5 11 | 12 | ## Installation ## 13 | 14 | 1. [Download] the latest release for your version of CakePHP or clone the Github repository 15 | 16 | 2. Place the files in a directory called 'linkable' inside the *app/plugins* directory of your CakePHP project. 17 | 18 | 3. Add the LinkableBehavior to a model or your AppModel: 19 | 20 | var $actsAs = array('Linkable.Linkable'); 21 | 22 | ## Usage ## 23 | 24 | Use it as a option to a find call. For example, getting a Post record with their associated (belongsTo) author User record: 25 | 26 | $this->Post->find('first', array( 27 | 'link' => array( 28 | 'User' 29 | ) 30 | )); 31 | 32 | This returns a Post record with it's associated User data. However, this isn't much different from what you can do Containable, and with the same amount of queries. Things start to change when linking hasMany or hasAndBelongsToMany associations. 33 | 34 | Because Linkable uses joins instead of seperate queries to get associated models, it is possible to apply conditions that operate from right to left (Tag -> Post) on hasMany and hasAndBelongsToMany associations. 35 | 36 | For example, finding all posts with a specific tag (hasAndBelongsToMany assocation): 37 | 38 | $this->Post->find('all', array( 39 | 'conditions' => array( 40 | 'Tag.name' => 'CakePHP' 41 | ), 42 | 'link' => array( 43 | 'Tag' 44 | ) 45 | )); 46 | 47 | But what if you would still like all associated tags for the posts, while still applying the condition from the previous example? Fortunately, Linkable works well together with Containable. This example also shows some of the options Linkable has: 48 | 49 | $this->Post->find('all', array( 50 | 'conditions' => array( 51 | 'TagFilter.name' => 'CakePHP' 52 | ), 53 | 'link' => array( 54 | 'PostsTag' => array( 55 | 'TagFilter' => array( 56 | 'class' => 'Tag', 57 | 'conditions' => 'TagFilter.id = PostsTag.tag_id', // Join condition (LEFT JOIN x ON ...) 58 | 'fields' => array( 59 | 'TagFilter.id' 60 | ) 61 | ) 62 | ) 63 | ), 64 | 'contain' => array( 65 | 'Tag' 66 | ) 67 | )); 68 | 69 | If you're thinking: yeesh, that is a lot of code, then I agree with you ;). Linkable's automagical handling of associations with non-standard names has room for improvement. Please, feel free to contribute to the project via GitHub. 70 | 71 | ### Pagination ### 72 | 73 | As a last example, pagination. This will find and paginate all posts with the tag 'CakePHP': 74 | 75 | $this->paginate = array( 76 | 'fields' => array( 77 | 'title' 78 | ), 79 | 'conditions' => array( 80 | 'Tag.name' => 'CakePHP' 81 | ), 82 | 'link' => array( 83 | 'Tag' 84 | ) 85 | 'limit' => 10 86 | ); 87 | 88 | $this->paginate('Post'); 89 | 90 | ### Notes ## 91 | 92 | When fetching data in right to left operations, meaning in "one to many" relations (hasMany, hasAndBelongsToMany), it should be used in the opposite direction ("many to one"), i.e: 93 | 94 | To fetch all Users assigned to a Project: 95 | 96 | $this->Project->find('all', array('link' => 'User', 'conditions' => 'project_id = 1')); 97 | This won't produce the desired result as only a single user will be returned. 98 | 99 | $this->User->find('all', array('link' => 'Project', 'conditions' => 'project_id = 1')); 100 | This will fetch all users related to the specified project in one query. 101 | 102 | ## Authors ## 103 | - Originally authored by: Rafael Bandeira (rafaelbandeira3 (at) gmail (dot) com), http://rafaelbandeira3.wordpress.com 104 | - Maintained by: Arjen Verstoep (terr (at) terr (dot) nl), https://github.com/Terr 105 | - giulianob, https://github.com/giulianob 106 | - Chad Jablonski, https://github.com/cjab 107 | - Nathan Porter, https://github.com/n8man 108 | 109 | ## License ## 110 | 111 | Licensed under The MIT License 112 | Redistributions of files must retain the above copyright notice. 113 | 114 | [Download]: https://github.com/Terr/linkable/downloads 115 | -------------------------------------------------------------------------------- /models/behaviors/linkable.php: -------------------------------------------------------------------------------- 1 | true, 'table' => true, 'alias' => true, 21 | 'conditions' => true, 'fields' => true, 'reference' => true, 22 | 'class' => true, 'defaults' => true 23 | ); 24 | 25 | protected $_defaults = array('type' => 'LEFT'); 26 | 27 | public function beforeFind(&$Model, $query) { 28 | if (isset($query[$this->_key])) { 29 | 30 | $optionsDefaults = $this->_defaults + array('reference' => $Model->alias, $this->_key => array()); 31 | $optionsKeys = $this->_options + array($this->_key => true); 32 | 33 | if (empty($query['contain'])) { 34 | $query = am(array('joins' => array()), $query, array('recursive' => -1)); 35 | } else { 36 | // If containable is being used, then let it set the recursive! 37 | $query = am(array('joins' => array()), $query); 38 | } 39 | 40 | $iterators[] = $query[$this->_key]; 41 | $cont = 0; 42 | 43 | do { 44 | $iterator = $iterators[$cont]; 45 | $defaults = $optionsDefaults; 46 | 47 | if (isset($iterator['defaults'])) { 48 | $defaults = array_merge($defaults, $iterator['defaults']); 49 | unset($iterator['defaults']); 50 | } 51 | 52 | $iterations = Set::normalize($iterator); 53 | 54 | foreach ($iterations as $alias => $options) { 55 | if (is_null($options)) { 56 | $options = array(); 57 | } 58 | 59 | $options = am($defaults, compact('alias'), $options); 60 | 61 | if (empty($options['alias'])) { 62 | throw new InvalidArgumentException(sprintf('%s::%s must receive aliased links', get_class($this), __FUNCTION__)); 63 | } 64 | 65 | if (empty($options['table']) && empty($options['class'])) { 66 | $options['class'] = $options['alias']; 67 | } elseif (!empty($options['table']) && empty($options['class'])) { 68 | $options['class'] = Inflector::classify($options['table']); 69 | } 70 | 71 | $_Model =& ClassRegistry::init($options['class']); // the incoming model to be linked in query 72 | $Reference =& ClassRegistry::init($options['reference']); // the already in query model that links to $_Model 73 | $db =& $_Model->getDataSource(); 74 | $associations = $_Model->getAssociated(); 75 | 76 | if (isset($Reference->belongsTo[$_Model->alias])) { 77 | $type = 'hasOne'; 78 | $association = $Reference->belongsTo[$_Model->alias]; 79 | } else if (isset($associations[$Reference->alias])) { 80 | $type = $associations[$Reference->alias]; 81 | $association = $_Model->{$type}[$Reference->alias]; 82 | } else { 83 | $_Model->bindModel(array('belongsTo' => array($Reference->alias))); 84 | $type = 'belongsTo'; 85 | $association = $_Model->{$type}[$Reference->alias]; 86 | $_Model->unbindModel(array('belongsTo' => array($Reference->alias))); 87 | } 88 | 89 | if (empty($options['conditions'])) { 90 | if ($type === 'belongsTo') { 91 | $modelKey = $_Model->escapeField($association['foreignKey']); 92 | $referenceKey = $Reference->escapeField($Reference->primaryKey); 93 | $options['conditions'] = "{$referenceKey} = {$modelKey}"; 94 | } elseif ($type === 'hasAndBelongsToMany') { 95 | if (isset($association['with'])) { 96 | $Link =& $_Model->{$association['with']}; 97 | 98 | if (isset($Link->belongsTo[$_Model->alias])) { 99 | $modelLink = $Link->escapeField($Link->belongsTo[$_Model->alias]['foreignKey']); 100 | } 101 | 102 | if (isset($Link->belongsTo[$Reference->alias])) { 103 | $referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey']); 104 | } 105 | } else { 106 | $Link =& $_Model->{Inflector::classify($association['joinTable'])}; 107 | } 108 | 109 | if (empty($modelLink)) { 110 | $modelLink = $Link->escapeField(Inflector::underscore($_Model->alias) . '_id'); 111 | } 112 | 113 | if (empty($referenceLink)) { 114 | $referenceLink = $Link->escapeField(Inflector::underscore($Reference->alias) . '_id'); 115 | } 116 | 117 | $referenceKey = $Reference->escapeField(); 118 | $query['joins'][] = array( 119 | 'alias' => $Link->alias, 120 | 'table' => $Link->getDataSource()->fullTableName($Link), 121 | 'conditions' => "{$referenceLink} = {$referenceKey}", 122 | 'type' => 'LEFT' 123 | ); 124 | 125 | $modelKey = $_Model->escapeField(); 126 | $options['conditions'] = "{$modelLink} = {$modelKey}"; 127 | } else { 128 | $referenceKey = $Reference->escapeField($association['foreignKey']); 129 | $modelKey = $_Model->escapeField($_Model->primaryKey); 130 | $options['conditions'] = "{$modelKey} = {$referenceKey}"; 131 | } 132 | } 133 | 134 | if (empty($options['table'])) { 135 | $options['table'] = $db->fullTableName($_Model, true); 136 | } 137 | 138 | if (!empty($options['fields'])) { 139 | if ($options['fields'] === true && !empty($association['fields'])) { 140 | $options['fields'] = $db->fields($_Model, null, $association['fields']); 141 | } elseif ($options['fields'] === true) { 142 | $options['fields'] = $db->fields($_Model); 143 | } 144 | // Leave COUNT() queries alone 145 | elseif($options['fields'] != 'COUNT(*) AS `count`') 146 | { 147 | $options['fields'] = $db->fields($_Model, null, $options['fields']); 148 | } 149 | 150 | if (is_array($query['fields'])) 151 | { 152 | $query['fields'] = array_merge($query['fields'], $options['fields']); 153 | } 154 | // Leave COUNT() queries alone 155 | elseif($query['fields'] != 'COUNT(*) AS `count`') 156 | { 157 | $query['fields'] = array_merge($db->fields($Model), $options['fields']); 158 | } 159 | } 160 | else 161 | { 162 | if (!empty($association['fields'])) { 163 | $options['fields'] = $db->fields($_Model, null, $association['fields']); 164 | } else { 165 | $options['fields'] = $db->fields($_Model); 166 | } 167 | 168 | if (is_array($query['fields'])) { 169 | $query['fields'] = array_merge($query['fields'], $options['fields']); 170 | } // Leave COUNT() queries alone 171 | elseif($query['fields'] != 'COUNT(*) AS `count`') { 172 | $query['fields'] = array_merge($db->fields($Model), $options['fields']); 173 | } 174 | } 175 | 176 | $options[$this->_key] = am($options[$this->_key], array_diff_key($options, $optionsKeys)); 177 | $options = array_intersect_key($options, $optionsKeys); 178 | 179 | if (!empty($options[$this->_key])) { 180 | $iterators[] = $options[$this->_key] + array('defaults' => array_merge($defaults, array('reference' => $options['class']))); 181 | } 182 | 183 | $options['conditions'] = array($options['conditions']); 184 | $query['joins'][] = array_intersect_key($options, array('type' => true, 'alias' => true, 'table' => true, 'conditions' => true)); 185 | } 186 | 187 | $cont++; 188 | $notDone = isset($iterators[$cont]); 189 | } while ($notDone); 190 | } 191 | unset($query['link']); 192 | 193 | return $query; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /tests/cases/models/behaviors/linkable.test.php: -------------------------------------------------------------------------------- 1 | array( 57 | 'className' => 'Tag', 58 | 'foreignKey' => 'parent_id' 59 | ) 60 | ); 61 | } 62 | 63 | class LegacyProduct extends TestModel 64 | { 65 | public $primaryKey = 'product_id'; 66 | 67 | public $belongsTo = array( 68 | 'Maker' => array( 69 | 'className' => 'LegacyCompany', 70 | 'foreignKey' => 'the_company_that_builds_it_id' 71 | ), 72 | 'Transporter' => array( 73 | 'className' => 'LegacyCompany', 74 | 'foreignKey' => 'the_company_that_delivers_it_id' 75 | ) 76 | ); 77 | } 78 | 79 | class LegacyCompany extends TestModel 80 | { 81 | public $primaryKey = 'company_id'; 82 | 83 | public $hasMany = array( 84 | 'ProductsMade' => array( 85 | 'className' => 'LegacyProduct', 86 | 'foreignKey' => 'the_company_that_builds_it_id' 87 | ) 88 | ); 89 | } 90 | 91 | class Shipment extends TestModel 92 | { 93 | public $belongsTo = array( 94 | 'OrderItem' 95 | ); 96 | } 97 | 98 | class OrderItem extends TestModel 99 | { 100 | public $hasMany = array( 101 | 'Shipment' 102 | ); 103 | 104 | public $belongsTo = array( 105 | 'ActiveShipment' => array( 106 | 'className' => 'Shipment', 107 | 'foreignKey' => 'active_shipment_id', 108 | ), 109 | ); 110 | } 111 | 112 | class LinkableTestCase extends CakeTestCase 113 | { 114 | public $fixtures = array( 115 | 'plugin.linkable.user', 116 | 'plugin.linkable.profile', 117 | 'plugin.linkable.generic', 118 | 'plugin.linkable.comment', 119 | 'plugin.linkable.post', 120 | 'plugin.linkable.posts_tag', 121 | 'plugin.linkable.tag', 122 | 'plugin.linkable.user', 123 | 'plugin.linkable.legacy_product', 124 | 'plugin.linkable.legacy_company', 125 | 'plugin.linkable.shipment', 126 | 'plugin.linkable.order_item', 127 | ); 128 | 129 | public $Post; 130 | 131 | public function startTest() 132 | { 133 | $this->User =& ClassRegistry::init('User'); 134 | } 135 | 136 | public function testBelongsTo() 137 | { 138 | $arrayExpected = array( 139 | 'User' => array('id' => 1, 'username' => 'CakePHP'), 140 | 'Profile' => array ('id' => 1, 'user_id' => 1, 'biography' => 'CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying applications.') 141 | ); 142 | 143 | $arrayResult = $this->User->find('first', array( 144 | 'contain' => array( 145 | 'Profile' 146 | ) 147 | )); 148 | $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Containable: %s'); 149 | $this->assertEqual($arrayResult, $arrayExpected, 'belongsTo association via Containable: %s'); 150 | 151 | // Same association, but this time with Linkable 152 | $arrayResult = $this->User->find('first', array( 153 | 'fields' => array( 154 | 'id', 155 | 'username' 156 | ), 157 | 'contain' => false, 158 | 'link' => array( 159 | 'Profile' => array( 160 | 'fields' => array( 161 | 'id', 162 | 'user_id', 163 | 'biography' 164 | ) 165 | ) 166 | ) 167 | )); 168 | 169 | $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Linkable: %s'); 170 | $this->assertTrue(!empty($arrayResult['Profile']), 'belongsTo association via Linkable: %s'); 171 | $this->assertEqual($arrayResult, $arrayExpected, 'belongsTo association via Linkable: %s'); 172 | 173 | // Linkable association, no field lists 174 | $arrayResult = $this->User->find('first', array( 175 | 'contain' => false, 176 | 'link' => array( 177 | 'Profile' 178 | ) 179 | )); 180 | 181 | $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Linkable (automatic fields): %s'); 182 | $this->assertEqual($arrayResult, $arrayExpected, 'belongsTo association via Linkable (automatic fields): %s'); 183 | 184 | // On-the-fly association via Linkable 185 | $arrayExpected = array( 186 | 'User' => array('id' => 1, 'username' => 'CakePHP'), 187 | 'Generic' => array('id' => 1, 'text' => '') 188 | ); 189 | 190 | $arrayResult = $this->User->find('first', array( 191 | 'contain' => false, 192 | 'link' => array( 193 | 'Generic' => array( 194 | 'class' => 'Generic', 195 | 'conditions' => 'User.id = Generic.id', 196 | 'fields' => array( 197 | 'id', 198 | 'text' 199 | ) 200 | ) 201 | ) 202 | )); 203 | 204 | $this->assertTrue(isset($arrayResult['Generic']), 'On-the-fly belongsTo association via Linkable: %s'); 205 | $this->assertEqual($arrayResult, $arrayExpected, 'On-the-fly belongsTo association via Linkable: %s'); 206 | 207 | // On-the-fly association via Linkable, with order on the associations' row 208 | $arrayExpected = array( 209 | 'User' => array('id' => 4, 'username' => 'CodeIgniter'), 210 | 'Generic' => array('id' => 4, 'text' => '') 211 | ); 212 | 213 | $arrayResult = $this->User->find('first', array( 214 | 'contain' => false, 215 | 'link' => array( 216 | 'Generic' => array( 217 | 'class' => 'Generic', 218 | 'conditions' => 'User.id = Generic.id', 219 | 'fields' => array( 220 | 'id', 221 | 'text' 222 | ) 223 | ) 224 | ), 225 | 'order' => 'Generic.id DESC' 226 | )); 227 | 228 | $this->assertEqual($arrayResult, $arrayExpected, 'On-the-fly belongsTo association via Linkable, with order: %s'); 229 | } 230 | 231 | public function testHasMany() 232 | { 233 | // hasMany association via Containable. Should still work when Linkable is loaded 234 | $arrayExpected = array( 235 | 'User' => array('id' => 1, 'username' => 'CakePHP'), 236 | 'Comment' => array( 237 | 0 => array( 238 | 'id' => 1, 239 | 'user_id' => 1, 240 | 'body' => 'Text' 241 | ), 242 | 1 => array( 243 | 'id' => 2, 244 | 'user_id' => 1, 245 | 'body' => 'Text' 246 | ), 247 | ) 248 | ); 249 | 250 | $arrayResult = $this->User->find('first', array( 251 | 'contain' => array( 252 | 'Comment' 253 | ), 254 | 'order' => 'User.id ASC' 255 | )); 256 | $this->assertTrue(isset($arrayResult['Comment']), 'hasMany association via Containable: %s'); 257 | $this->assertEqual($arrayResult, $arrayExpected, 'hasMany association via Containable: %s'); 258 | 259 | // Same association, but this time with Linkable 260 | $arrayExpected = array( 261 | 'User' => array('id' => 1, 'username' => 'CakePHP'), 262 | 'Comment' => array( 263 | 'id' => 1, 264 | 'user_id' => 1, 265 | 'body' => 'Text' 266 | ) 267 | ); 268 | 269 | $arrayResult = $this->User->find('first', array( 270 | 'fields' => array( 271 | 'id', 272 | 'username' 273 | ), 274 | 'contain' => false, 275 | 'link' => array( 276 | 'Comment' => array( 277 | 'fields' => array( 278 | 'id', 279 | 'user_id', 280 | 'body' 281 | ) 282 | ) 283 | ), 284 | 'order' => 'User.id ASC', 285 | 'group' => 'User.id' 286 | )); 287 | 288 | $this->assertEqual($arrayResult, $arrayExpected, 'hasMany association via Linkable: %s'); 289 | } 290 | 291 | public function testComplexAssociations() 292 | { 293 | $this->Post =& ClassRegistry::init('Post'); 294 | 295 | $arrayExpected = array( 296 | 'Post' => array('id' => 1, 'title' => 'Post 1', 'user_id' => 1), 297 | 'Tag' => array('name' => 'General'), 298 | 'Profile' => array('biography' => 'CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying applications.'), 299 | 'MainTag' => array('name' => 'General'), 300 | 'Generic' => array('id' => 1,'text' => ''), 301 | 'User' => array('id' => 1, 'username' => 'CakePHP') 302 | ); 303 | 304 | $arrayResult = $this->Post->find('first', array( 305 | 'conditions' => array( 306 | 'MainTag.id' => 1 307 | ), 308 | 'link' => array( 309 | 'User' => array( 310 | 'conditions' => 'Post.user_id = User.id', 311 | 'Profile' => array( 312 | 'fields' => array( 313 | 'biography' 314 | ), 315 | 'Generic' => array( 316 | 'class' => 'Generic', 317 | 'conditions' => 'User.id = Generic.id' 318 | ) 319 | ) 320 | ), 321 | 'Tag' => array( 322 | 'table' => 'tags', 323 | 'fields' => array( 324 | 'name' 325 | ) 326 | ), 327 | 'MainTag' => array( 328 | 'class' => 'Tag', 329 | 'conditions' => 'PostsTag.post_id = Post.id', 330 | 'fields' => array( 331 | 'MainTag.name' // @fixme Wants to use class name (Tag) instead of alias (MainTag) 332 | ) 333 | ) 334 | ) 335 | )); 336 | 337 | $this->assertEqual($arrayExpected, $arrayResult, 'Complex find: %s'); 338 | 339 | // Linkable and Containable combined 340 | $arrayExpected = array( 341 | 'Post' => array('id' => 1, 'title' => 'Post 1', 'user_id' => 1), 342 | 'Tag' => array( 343 | array('id' => 1, 'name' => 'General', 'parent_id' => null, 'PostsTag' => array('id' => 1, 'post_id' => 1, 'tag_id' => 1, 'main' => 0)), 344 | array('id' => 2, 'name' => 'Test I', 'parent_id' => 1, 'PostsTag' => array('id' => 2, 'post_id' => 1, 'tag_id' => 2, 'main' => 1)) 345 | ), 346 | 'User' => array('id' => 1, 'username' => 'CakePHP') 347 | ); 348 | 349 | $arrayResult = $this->Post->find('first', array( 350 | 'contain' => array( 351 | 'Tag' 352 | ), 353 | 'link' => array( 354 | 'User' => array( 355 | 'conditions' => 'User.id = Post.user_id' 356 | ) 357 | ) 358 | )); 359 | 360 | $this->assertEqual($arrayResult, $arrayExpected, 'Linkable and Containable combined: %s'); 361 | } 362 | 363 | public function testPagination() 364 | { 365 | $objController = new Controller(); 366 | $objController->uses = array('User'); 367 | $objController->constructClasses(); 368 | $objController->params['url']['url'] = '/'; 369 | 370 | $objController->paginate = array( 371 | 'fields' => array( 372 | 'username' 373 | ), 374 | 'contain' => false, 375 | 'link' => array( 376 | 'Profile' => array( 377 | 'fields' => array( 378 | 'biography' 379 | ) 380 | ) 381 | ), 382 | 'limit' => 2 383 | ); 384 | 385 | $arrayResult = $objController->paginate('User'); 386 | 387 | $this->assertEqual($objController->params['paging']['User']['count'], 4, 'Paging: total records count: %s'); 388 | 389 | // Pagination with order on a row from table joined with Linkable 390 | $objController->paginate = array( 391 | 'fields' => array( 392 | 'id' 393 | ), 394 | 'contain' => false, 395 | 'link' => array( 396 | 'Profile' => array( 397 | 'fields' => array( 398 | 'user_id' 399 | ) 400 | ) 401 | ), 402 | 'limit' => 2, 403 | 'order' => 'Profile.user_id DESC' 404 | ); 405 | 406 | $arrayResult = $objController->paginate('User'); 407 | 408 | $arrayExpected = array( 409 | 0 => array( 410 | 'User' => array( 411 | 'id' => 4 412 | ), 413 | 'Profile' => array ('user_id' => 4) 414 | ), 415 | 1 => array( 416 | 'User' => array( 417 | 'id' => 3 418 | ), 419 | 'Profile' => array ('user_id' => 3) 420 | ) 421 | ); 422 | 423 | $this->assertEqual($arrayResult, $arrayExpected, 'Paging with order on join table row: %s'); 424 | 425 | // Pagination without specifying any fields 426 | $objController->paginate = array( 427 | 'contain' => false, 428 | 'link' => array( 429 | 'Profile' 430 | ), 431 | 'limit' => 2, 432 | 'order' => 'Profile.user_id DESC' 433 | ); 434 | 435 | $arrayResult = $objController->paginate('User'); 436 | $this->assertEqual($objController->params['paging']['User']['count'], 4, 'Paging without any field lists: total records count: %s'); 437 | } 438 | 439 | /** 440 | * Series of tests that assert if Linkable can adapt to assocations that 441 | * have aliases different from their standard model names 442 | */ 443 | public function testNonstandardAssociationNames() 444 | { 445 | $this->Tag =& ClassRegistry::init('Tag'); 446 | 447 | $arrayExpected = array( 448 | 'Tag' => array( 449 | 'name' => 'Test I' 450 | ), 451 | 'Parent' => array( 452 | 'name' => 'General' 453 | ) 454 | ); 455 | 456 | $arrayResult = $this->Tag->find('first', array( 457 | 'fields' => array( 458 | 'name' 459 | ), 460 | 'conditions' => array( 461 | 'Tag.id' => 2 462 | ), 463 | 'link' => array( 464 | 'Parent' => array( 465 | 'fields' => array( 466 | 'name' 467 | ) 468 | ) 469 | ) 470 | )); 471 | 472 | $this->assertEqual($arrayExpected, $arrayResult, 'Association with non-standard name: %s'); 473 | 474 | 475 | $this->LegacyProduct =& ClassRegistry::init('LegacyProduct'); 476 | 477 | $arrayExpected = array( 478 | 'LegacyProduct' => array( 479 | 'name' => 'Velocipede' 480 | ), 481 | 'Maker' => array( 482 | 'company_name' => 'Vintage Stuff Manufactory' 483 | ), 484 | 'Transporter' => array( 485 | 'company_name' => 'Joe & Co Crate Shipping Company' 486 | ) 487 | ); 488 | 489 | $arrayResult = $this->LegacyProduct->find('first', array( 490 | 'fields' => array( 491 | 'name' 492 | ), 493 | 'conditions' => array( 494 | 'LegacyProduct.product_id' => 1 495 | ), 496 | 'link' => array( 497 | 'Maker' => array( 498 | 'fields' => array( 499 | 'company_name' 500 | ) 501 | ), 502 | 'Transporter' => array( 503 | 'fields' => array( 504 | 'company_name' 505 | ) 506 | ) 507 | ) 508 | )); 509 | 510 | $this->assertEqual($arrayExpected, $arrayResult, 'belongsTo associations with custom foreignKey: %s'); 511 | 512 | $arrayExpected = array( 513 | 'ProductsMade' => array( 514 | 'name' => 'Velocipede' 515 | ), 516 | 'Maker' => array( 517 | 'company_name' => 'Vintage Stuff Manufactory' 518 | ) 519 | ); 520 | 521 | $arrayResult = $this->LegacyProduct->Maker->find('first', array( 522 | 'fields' => array( 523 | 'company_name' 524 | ), 525 | 'conditions' => array( 526 | 'Maker.company_id' => 1 527 | ), 528 | 'link' => array( 529 | 'ProductsMade' => array( 530 | 'fields' => array( 531 | 'name' 532 | ) 533 | ) 534 | ) 535 | )); 536 | 537 | $this->assertEqual($arrayExpected, $arrayResult, 'hasMany association with custom foreignKey: %s'); 538 | } 539 | 540 | public function testAliasedBelongsToWithSameModelAsHasMany() 541 | { 542 | $this->OrderItem =& ClassRegistry::init('OrderItem'); 543 | 544 | $arrayExpected = array( 545 | 0 => array( 546 | 'OrderItem' => array( 547 | 'id' => 50, 548 | 'active_shipment_id' => 320 549 | ), 550 | 'ActiveShipment' => array( 551 | 'id' => 320, 552 | 'ship_date' => '2011-01-07', 553 | 'order_item_id' => 50 554 | ) 555 | ) 556 | ); 557 | 558 | $arrayResult = $this->OrderItem->find('all', array( 559 | 'recursive' => -1, 560 | 'conditions' => array( 561 | 'ActiveShipment.ship_date' => date('2011-01-07'), 562 | ), 563 | 'link' => array('ActiveShipment'), 564 | )); 565 | 566 | $this->assertEqual($arrayExpected, $arrayResult, 'belongsTo association with alias (requested), with hasMany to the same model without alias: %s'); 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /tests/fixtures/comment_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'user_id' => array('type' => 'integer'), 10 | 'body' => array('type' => 'string', 'length' => 255, 'null' => false) 11 | ); 12 | 13 | var $records = array( 14 | array('id' => 1, 'user_id' => 1, 'body' => 'Text'), 15 | array('id' => 2, 'user_id' => 1, 'body' => 'Text'), 16 | array('id' => 3, 'user_id' => 2, 'body' => 'Text'), 17 | array('id' => 4, 'user_id' => 3, 'body' => 'Text'), 18 | array('id' => 5, 'user_id' => 4, 'body' => 'Text') 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /tests/fixtures/generic_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'text' => array('type' => 'string', 'length' => 255, 'null' => false) 10 | ); 11 | 12 | var $records = array( 13 | array ('id' => 1, 'text' => ''), 14 | array ('id' => 2, 'text' => ''), 15 | array ('id' => 3, 'text' => ''), 16 | array ('id' => 4, 'text' => '') 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/legacy_company_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'company_name' => array('type' => 'string', 'length' => 255, 'null' => false), 10 | ); 11 | 12 | var $records = array( 13 | array('company_id' => 1, 'company_name' => 'Vintage Stuff Manufactory'), 14 | array('company_id' => 2, 'company_name' => 'Modern Steam Cars Inc.'), 15 | array('company_id' => 3, 'company_name' => 'Joe & Co Crate Shipping Company') 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/legacy_product_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'name' => array('type' => 'string', 'length' => 255, 'null' => false), 10 | 'the_company_that_builds_it_id' => array('type' => 'integer'), 11 | 'the_company_that_delivers_it_id' => array('type' => 'integer') 12 | ); 13 | 14 | var $records = array( 15 | array('product_id' => 1, 'name' => 'Velocipede', 'the_company_that_builds_it_id' => 1, 'the_company_that_delivers_it_id' => 3), 16 | array('product_id' => 2, 'name' => 'Oruktor Amphibolos', 'the_company_that_builds_it_id' => 2, 'the_company_that_delivers_it_id' => 2), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/order_item_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'active_shipment_id' => array('type' => 'integer'), 10 | ); 11 | 12 | var $records = array( 13 | array ('id' => 50, 'active_shipment_id' => 320) 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/post_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'title' => array('type' => 'string', 'length' => 255, 'null' => false), 10 | 'user_id' => array('type' => 'integer'), 11 | ); 12 | 13 | var $records = array( 14 | array ('id' => 1, 'title' => 'Post 1', 'user_id' => 1), 15 | array ('id' => 2, 'title' => 'Post 2', 'user_id' => 2) 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/posts_tag_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'post_id' => array('type' => 'integer'), 10 | 'tag_id' => array('type' => 'integer'), 11 | 'main' => array('type' => 'integer') 12 | ); 13 | 14 | var $records = array( 15 | array ('id' => 1, 'post_id' => 1, 'tag_id' => 1, 'main' => 0), 16 | array ('id' => 2, 'post_id' => 1, 'tag_id' => 2, 'main' => 1), 17 | array ('id' => 3, 'post_id' => 2, 'tag_id' => 3, 'main' => 0), 18 | array ('id' => 4, 'post_id' => 2, 'tag_id' => 4, 'main' => 0), 19 | ); 20 | } -------------------------------------------------------------------------------- /tests/fixtures/profile_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'user_id' => array('type' => 'integer'), 10 | 'biography' => array('type' => 'string', 'length' => 255, 'null' => false) 11 | ); 12 | 13 | var $records = array( 14 | array ('id' => 1, 'user_id' => 1, 'biography' => 'CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying applications.'), 15 | array ('id' => 2, 'user_id' => 2, 'biography' => ''), 16 | array ('id' => 3, 'user_id' => 3, 'biography' => ''), 17 | array ('id' => 4, 'user_id' => 4, 'biography' => '') 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /tests/fixtures/shipment_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'ship_date' => array('type' => 'date'), 10 | 'order_item_id' => array('type' => 'integer') 11 | ); 12 | 13 | var $records = array( 14 | array ('id' => 320, 'ship_date' => '2011-01-07', 'order_item_id' => 50), 15 | array ('id' => 319, 'ship_date' => '2011-01-07', 'order_item_id' => 50), 16 | array ('id' => 310, 'ship_date' => '2011-01-07', 'order_item_id' => 50) 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/tag_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'name' => array('type' => 'string', 'length' => 255, 'null' => false), 10 | 'parent_id' => array('type' => 'integer') 11 | ); 12 | 13 | var $records = array( 14 | array ('id' => 1, 'name' => 'General', 'parent_id' => null), 15 | array ('id' => 2, 'name' => 'Test I', 'parent_id' => 1), 16 | array ('id' => 3, 'name' => 'Test II', 'parent_id' => null), 17 | array ('id' => 4, 'name' => 'Test III', 'parent_id' => null) 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /tests/fixtures/user_fixture.php: -------------------------------------------------------------------------------- 1 | array('type' => 'integer', 'key' => 'primary'), 9 | 'username' => array('type' => 'string', 'length' => 255, 'null' => false) 10 | ); 11 | 12 | var $records = array( 13 | array('id' => 1, 'username' => 'CakePHP'), 14 | array('id' => 2, 'username' => 'Zend'), 15 | array('id' => 3, 'username' => 'Symfony'), 16 | array('id' => 4, 'username' => 'CodeIgniter') 17 | ); 18 | } 19 | --------------------------------------------------------------------------------