6 Core Behaviors

Behaviors add extra functionality to your models. CakePHP comes with a number of built-in behaviors such as Tree and Containable.

6.1 ACL

The Acl behavior provides a way to seamlessly integrate a model with your ACL system. It can create both AROs or ACOs transparently.

To use the new behavior, you can add it to the $actsAs property of your model. When adding it to the actsAs array you choose to make the related Acl entry an ARO or an ACO. The default is to create AROs.

class Post extends AppModel {
	var $actsAs = array('Acl' => array('type' => 'requester'));
}
  1. class Post extends AppModel {
  2. var $actsAs = array('Acl' => array('type' => 'requester'));
  3. }

This would attach the Acl behavior in ARO mode. To join the ACL behavior in ACO mode use:

class Post extends AppModel {
	var $actsAs = array('Acl' => array('type' => 'controlled'));
}
  1. class Post extends AppModel {
  2. var $actsAs = array('Acl' => array('type' => 'controlled'));
  3. }

You can also attach the behavior on the fly like so:

	$this->Post->Behaviors->attach('Acl', array('type' => 'controlled'));
  1. $this->Post->Behaviors->attach('Acl', array('type' => 'controlled'));

6.1.1 Using the AclBehavior

Most of the AclBehavior works transparently on your Model's afterSave(). However, using it requires that your Model has a parentNode() method defined. This is used by the AclBehavior to determine parent->child relationships. A model's parentNode() method must return null or return a parent Model reference.

function parentNode() {
	return null;
}
  1. function parentNode() {
  2. return null;
  3. }

If you want to set an ACO or ARO node as the parent for your Model, parentNode() must return the alias of the ACO or ARO node.

function parentNode() {
        return 'root_node';
}
  1. function parentNode() {
  2. return 'root_node';
  3. }

A more complete example. Using an example User Model, where User belongsTo Group.

function parentNode() {
	if (!$this->id && empty($this->data)) {
		return null;
	}
	$data = $this->data;
	if (empty($this->data)) {
		$data = $this->read();
	} 
	if (!$data['User']['group_id']) {
		return null;
	} else {
		$this->Group->id = $data['User']['group_id'];
		$groupNode = $this->Group->node();
		return array('Group' => array('id' => $groupNode[0]['Aro']['foreign_key']));
	}
}
  1. function parentNode() {
  2. if (!$this->id && empty($this->data)) {
  3. return null;
  4. }
  5. $data = $this->data;
  6. if (empty($this->data)) {
  7. $data = $this->read();
  8. }
  9. if (!$data['User']['group_id']) {
  10. return null;
  11. } else {
  12. $this->Group->id = $data['User']['group_id'];
  13. $groupNode = $this->Group->node();
  14. return array('Group' => array('id' => $groupNode[0]['Aro']['foreign_key']));
  15. }
  16. }

In the above example the return is an array that looks similar to the results of a model find. It is important to have the id value set or the parentNode relation will fail. The AclBehavior uses this data to construct its tree structure.

6.1.2 node()

The AclBehavior also allows you to retrieve the Acl node associated with a model record. After setting $model->id. You can use $model->node() to retrieve the associated Acl node.

You can also retrieve the Acl Node for any row, by passing in a data array.

	$this->User->id = 1;
	$node = $this->User->node();
	
	$user = array('User' => array(
		'id' => 1
	));
	$node = $this->User->node($user);
  1. $this->User->id = 1;
  2. $node = $this->User->node();
  3. $user = array('User' => array(
  4. 'id' => 1
  5. ));
  6. $node = $this->User->node($user);

Will both return the same Acl Node information.

6.2 Containable

A new addition to the CakePHP core is the ContainableBehavior. This model behavior allows you to filter and limit model find operations. Using Containable will help you cut down on needless wear and tear on your database, increasing the speed and overall performance of your application. The class will also help you search and filter your data for your users in a clean and consistent way.

To use the new behavior, you can add it to the $actsAs property of your model:

class Post extends AppModel {
    var $actsAs = array('Containable');
}
  1. class Post extends AppModel {
  2. var $actsAs = array('Containable');
  3. }

You can also attach the behavior on the fly:

$this->Post->Behaviors->attach('Containable');
  1. $this->Post->Behaviors->attach('Containable');

To see how Containable works, let's look at a few examples. First, we'll start off with a find() call on a model named Post. Let's say that Post hasMany Comment, and Post hasAndBelongsToMany Tag. The amount of data fetched in a normal find() call is rather extensive:

debug($this->Post->find('all'));
  1. debug($this->Post->find('all'));
[0] => Array
        (
            [Post] => Array
                (
                    [id] => 1
                    [title] => First article
                    [content] => aaa
                    [created] => 2008-05-18 00:00:00
                )
            [Comment] => Array
                (
                    [0] => Array
                        (
                            [id] => 1
                            [post_id] => 1
                            [author] => Daniel
                            [email] => dan@example.com
                            [website] => http://example.com
                            [comment] => First comment
                            [created] => 2008-05-18 00:00:00
                        )
                    [1] => Array
                        (
                            [id] => 2
                            [post_id] => 1
                            [author] => Sam
                            [email] => sam@example.net
                            [website] => http://example.net
                            [comment] => Second comment
                            [created] => 2008-05-18 00:00:00
                        )
                )
            [Tag] => Array
                (
                    [0] => Array
                        (
                            [id] => 1
                            [name] => A
                        )
                    [1] => Array
                        (
                            [id] => 2
                            [name] => B
                        )
                )
        )
[1] => Array
        (
            [Post] => Array
                (...

For some interfaces in your application, you may not need that much information from the Post model. One thing the ContainableBehavior does is help you cut down on what find() returns.

For example, to get only the post-related information, you can do the following:

$this->Post->contain();
$this->Post->find('all');
  1. $this->Post->contain();
  2. $this->Post->find('all');
You can also invoke Containable's magic from inside the find() call:
$this->Post->find('all', array('contain' => false));
  1. $this->Post->find('all', array('contain' => false));

Having done that, you end up with something a lot more concise:

[0] => Array
        (
            [Post] => Array
                (
                    [id] => 1
                    [title] => First article
                    [content] => aaa
                    [created] => 2008-05-18 00:00:00
                )
        )
[1] => Array
        (
            [Post] => Array
                (
                    [id] => 2
                    [title] => Second article
                    [content] => bbb
                    [created] => 2008-05-19 00:00:00
                )
        )

This sort of help isn't new: in fact, you can do that without the ContainableBehavior doing something like this:

$this->Post->recursive = -1;
$this->Post->find('all');
  1. $this->Post->recursive = -1;
  2. $this->Post->find('all');
Containable really shines when you have complex associations, and you want to pare down things that sit at the same level. The model's $recursive property is helpful if you want to hack off an entire level of recursion, but not when you want to pick and choose what to keep at each level. Let's see how it works by using the contain() method. The contain method's first argument accepts the name, or an array of names, of the models to keep in the find operation. If we wanted to fetch all posts and their related tags (without any comment information), we'd try something like this:
$this->Post->contain('Tag');
$this->Post->find('all');
  1. $this->Post->contain('Tag');
  2. $this->Post->find('all');
Again, we can use the contain key inside a find() call:
$this->Post->find('all', array('contain' => 'Tag'));
  1. $this->Post->find('all', array('contain' => 'Tag'));

Without Containable, you'd end up needing to use the unbindModel() method of the model, multiple times if you're paring off multiple models. Containable creates a cleaner way to accomplish this same task.

Containable also goes a step deeper: you can filter the data of the associated models. If you look at the results of the original find() call, notice the author field in the Comment model. If you are interested in the posts and the names of the comment authors—and nothing else—you could do something like the following:

$this->Post->contain('Comment.author');
$this->Post->find('all');

//or..

$this->Post->find('all', array('contain' => 'Comment.author'));
  1. $this->Post->contain('Comment.author');
  2. $this->Post->find('all');
  3. //or..
  4. $this->Post->find('all', array('contain' => 'Comment.author'));

Here, we've told Containable to give us our post information, and just the author field of the associated Comment model. The output of the find call might look something like this:

[0] => Array
        (
            [Post] => Array
                (
                    [id] => 1
                    [title] => First article
                    [content] => aaa
                    [created] => 2008-05-18 00:00:00
                )
            [Comment] => Array
                (
                    [0] => Array
                        (
                            [author] => Daniel
                            [post_id] => 1
                        )
                    [1] => Array
                        (
                            [author] => Sam
                            [post_id] => 1
                        )
                )
        )
[1] => Array
        (...

As you can see, the Comment arrays only contain the author field (plus the post_id which is needed by CakePHP to map the results).

You can also filter the associated Comment data by specifying a condition:

$this->Post->contain('Comment.author = "Daniel"');
$this->Post->find('all');

//or...

$this->Post->find('all', array('contain' => 'Comment.author = "Daniel"'));
  1. $this->Post->contain('Comment.author = "Daniel"');
  2. $this->Post->find('all');
  3. //or...
  4. $this->Post->find('all', array('contain' => 'Comment.author = "Daniel"'));

This gives us a result that gives us posts with comments authored by Daniel:

[0] => Array
        (
            [Post] => Array
                (
                    [id] => 1
                    [title] => First article
                    [content] => aaa
                    [created] => 2008-05-18 00:00:00
                )
            [Comment] => Array
                (
                    [0] => Array
                        (
                            [id] => 1
                            [post_id] => 1
                            [author] => Daniel
                            [email] => dan@example.com
                            [website] => http://example.com
                            [comment] => First comment
                            [created] => 2008-05-18 00:00:00
                        )
                )
        )

Additional filtering can be performed by supplying the standard Model->find() options:

$this->Post->find('all', array('contain' => array(
    'Comment' => array(
        'conditions' => array('Comment.author =' => "Daniel"),
        'order' => 'Comment.created DESC'
    )
)));
  1. $this->Post->find('all', array('contain' => array(
  2. 'Comment' => array(
  3. 'conditions' => array('Comment.author =' => "Daniel"),
  4. 'order' => 'Comment.created DESC'
  5. )
  6. )));

Here's an example of using the Containble behavior when you've got deep and complex model relationships.

Let's consider the following model associations:

User->Profile
User->Account->AccountSummary
User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes
User->Post->Tag

This is how we retrieve the above associations with Containable:


$this->User->find('all', array(
   'contain'=>array(
      'Profile',
      'Account'=>array('AccountSummary'),
      'Post'=>array(
         'PostAttachment'=>array(
            'fields'=>array('id','name'),
               'PostAttachmentHistory'=>array(
                  'HistoryNotes'=>array(
                     'fields'=>array('id', 'note')
                   )
               )
         ),
         'Tag'=>array('conditions'=>array('Tag.name LIKE'=>'%happy%'))
       )
    )
));
  1. $this->User->find('all', array(
  2. 'contain'=>array(
  3. 'Profile',
  4. 'Account'=>array('AccountSummary'),
  5. 'Post'=>array(
  6. 'PostAttachment'=>array(
  7. 'fields'=>array('id','name'),
  8. 'PostAttachmentHistory'=>array(
  9. 'HistoryNotes'=>array(
  10. 'fields'=>array('id', 'note')
  11. )
  12. )
  13. ),
  14. 'Tag'=>array('conditions'=>array('Tag.name LIKE'=>'%happy%'))
  15. )
  16. )
  17. ));

Keep in mind that 'contain' key is only used once in the main model, you don't use 'contain' again for related models

When using 'fields' and 'contain' options - be careful to include all foreign keys that your query directly or indirectly requires

Here's an example of how to contain associations when paginating.

$this->paginate['User'] = array(
    'contain' => array('Profile', 'Account'),
    'order' => 'User.username'
);

$users = $this->paginate('User');
  1. $this->paginate['User'] = array(
  2. 'contain' => array('Profile', 'Account'),
  3. 'order' => 'User.username'
  4. );
  5. $users = $this->paginate('User');

6.3 Translate

This section has yet to be written, if you have an idea of what to put here please use the links and let us know your suggestion!

6.4 Tree

It's fairly common to want to store hierarchical data in a database table. Examples of such data might be categories with unlimited subcategories, data related to a multilevel menu system or a literal representation of hierarchy such as is used to store access control objects with ACL logic.

For small trees of data, or where the data is only a few levels deep it is simple to add a parent_id field to your database table and use this to keep track of which item is the parent of what. Bundled with cake however, is a powerful behavior which allows you to use the benefits of MPTT logic without worrying about any of the intricacies of the technique - unless you want to ;).

6.4.1 Requirements

To use the tree behavior, your database table needs 3 fields as listed below (all are ints):

  • parent - default fieldname is parent_id, to store the id of the parent object
  • left - default fieldname is lft, to store the lft value of the current row.
  • right - default fieldname is rght, to store the rght value of the current row.

If you are familiar with MPTT logic you may wonder why a parent field exists - quite simply it's easier to do certain tasks using if a direct parent link is stored on the database - such as finding direct children.

6.4.2 Basic Usage

The tree behavior has a lot packed into it, but let's start with a simple example - create the following database table and put some data in it:

CREATE TABLE categories (
	id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	parent_id INTEGER(10) DEFAULT NULL,
	lft INTEGER(10) DEFAULT NULL,
	rght INTEGER(10) DEFAULT NULL,
	name VARCHAR(255) DEFAULT '',
	PRIMARY KEY  (id)
);

INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(1, 'My Categories', NULL, 1, 30);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(2, 'Fun', 1, 2, 15);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(3, 'Sport', 2, 3, 8);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(4, 'Surfing', 3, 4, 5);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(5, 'Extreme knitting', 3, 6, 7);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(6, 'Friends', 2, 9, 14);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(7, 'Gerald', 6, 10, 11);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(8, 'Gwendolyn', 6, 12, 13);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(9, 'Work', 1, 16, 29);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(10, 'Reports', 9, 17, 22);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(11, 'Annual', 10, 18, 19);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(12, 'Status', 10, 20, 21);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(13, 'Trips', 9, 23, 28);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(14, 'National', 13, 24, 25);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(15, 'International', 13, 26, 27);

For the purpose of checking that everything is setup correctly, we can create a test method and output the contents of our category tree to see what it looks like. With a simple controller:

<?php
class CategoriesController extends AppController {

        var $name = 'Categories';
        
        function index() {
                $this->data = $this->Category->generatetreelist(null, null, null, '&nbsp;&nbsp;&nbsp;');
                debug ($this->data); die;       
        }
}
?>
  1. <?php
  2. class CategoriesController extends AppController {
  3. var $name = 'Categories';
  4. function index() {
  5. $this->data = $this->Category->generatetreelist(null, null, null, '&nbsp;&nbsp;&nbsp;');
  6. debug ($this->data); die;
  7. }
  8. }
  9. ?>

an an even simpler model definition:

<?php
// app/models/category.php
class Category extends AppModel {
	var $name = 'Category';
	var $actsAs = array('Tree');
}
?>
  1. <?php
  2. // app/models/category.php
  3. class Category extends AppModel {
  4. var $name = 'Category';
  5. var $actsAs = array('Tree');
  6. }
  7. ?>

We can check what our category tree data looks like by visiting /categories You should see something like this:

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Extreme knitting
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Reports
        • Annual
        • Status
      • Trips
        • National
        • International
6.4.2.1 Adding data

In the previous section, we used existing data and checked that it looked hierarchal via the method generatetreelist. However, usually you would add your data in exactly the same way as you would for any model. For example:

// pseudo controller code
$data['Category']['parent_id'] =  3;
$data['Category']['name'] =  'Skating';
$this->Category->save($data);
  1. // pseudo controller code
  2. $data['Category']['parent_id'] = 3;
  3. $data['Category']['name'] = 'Skating';
  4. $this->Category->save($data);

When using the tree behavior its not necessary to to do any more than set the parent_id, and the tree behavior will take care of the rest. If you don't set the parent_id, the tree behavior will add to the tree making your new addition a new top level entry:

// pseudo controller code
$data = array();
$data['Category']['name'] =  'Other People\'s Categories';
$this->Category->save($data);
  1. // pseudo controller code
  2. $data = array();
  3. $data['Category']['name'] = 'Other People\'s Categories';
  4. $this->Category->save($data);

Running the above two code snippets would alter your tree as follows:

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Extreme knitting
        • Skating New
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Reports
        • Annual
        • Status
      • Trips
        • National
        • International
  • Other People's Categories New
6.4.2.2 Modifying data

Modifying data is as transparent as adding new data. If you modify something, but do not change the parent_id field - the structure of your data will remain unchanged. For example:

// pseudo controller code
$this->Category->id = 5; // id of Extreme knitting
$this->Category->save(array('name' =>'Extreme fishing'));
  1. // pseudo controller code
  2. $this->Category->id = 5; // id of Extreme knitting
  3. $this->Category->save(array('name' =>'Extreme fishing'));

The above code did not affect the parent_id field - even if the parent_id is included in the data that is passed to save if the value doesn't change, neither does the data structure. Therefore the tree of data would now look like:

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Extreme fishing Updated
        • Skating
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Reports
        • Annual
        • Status
      • Trips
        • National
        • International
  • Other People's Categories

Moving data around in your tree is also a simple affair. Let's say that Extreme fishing does not belong under Sport, but instead should be located under Other People's Categories. With the following code:

// pseudo controller code
$this->Category->id = 5; // id of Extreme fishing
$newParentId = $this->Category->field('id', array('name' => 'Other People\'s Categories'));
$this->Category->save(array('parent_id' => $newParentId)); 
  1. // pseudo controller code
  2. $this->Category->id = 5; // id of Extreme fishing
  3. $newParentId = $this->Category->field('id', array('name' => 'Other People\'s Categories'));
  4. $this->Category->save(array('parent_id' => $newParentId));

As would be expected the structure would be modified to:

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Skating
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Reports
        • Annual
        • Status
      • Trips
        • National
        • International
  • Other People's Categories
    • Extreme fishing Moved
6.4.2.3 Deleting data

The tree behavior provides a number of ways to manage deleting data. To start with the simplest example; let's say that the reports category is no longer useful. To remove it and any children it may have just call delete as you would for any model. For example with the following code:

// pseudo controller code
$this->Category->id = 10;
$this->Category->delete();
  1. // pseudo controller code
  2. $this->Category->id = 10;
  3. $this->Category->delete();

The category tree would be modified as follows:

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Skating
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Trips
        • National
        • International
  • Other People's Categories
    • Extreme fishing
6.4.2.4 Querying and using your data

Using and manipulating hierarchical data can be a tricky business. In addition to the core find methods, with the tree behavior there are a few more tree-orientated permutations at your disposal.

Most tree behavior methods return and rely on data being sorted by the lft field. If you call find() and do not order by lft, or call a tree behavior method and pass a sort order, you may get undesirable results.

6.4.2.4.1 Children

The children method takes the primary key value (the id) of a row and returns the children, by default in the order they appear in the tree. The second optional parameter defines whether or not only direct children should be returned. Using the example data from the previous section:

$allChildren = $this->Category->children(1); // a flat array with 11 items
// -- or --
$this->Category->id = 1;
$allChildren = $this->Category->children(); // a flat array with 11 items

// Only return direct children
$directChildren = $this->Category->children(1, true); // a flat array with 2 items
  1. $allChildren = $this->Category->children(1); // a flat array with 11 items
  2. // -- or --
  3. $this->Category->id = 1;
  4. $allChildren = $this->Category->children(); // a flat array with 11 items
  5. // Only return direct children
  6. $directChildren = $this->Category->children(1, true); // a flat array with 2 items

If you want a recursive array use find('threaded')

6.4.2.4.2 Counting children

As with the method children, childCount takes the primary key value (the id) of a row and returns how many children it has. The second optional parameter defines whether or not only direct children are counted. Using the example data from the previous section:

$totalChildren = $this->Category->childCount(1); // will output 11
// -- or --
$this->Category->id = 1;
$directChildren = $this->Category->childCount(); // will output 11

// Only counts the direct descendants of this category
$numChildren = $this->Category->childCount(1, true); // will output 2
  1. $totalChildren = $this->Category->childCount(1); // will output 11
  2. // -- or --
  3. $this->Category->id = 1;
  4. $directChildren = $this->Category->childCount(); // will output 11
  5. // Only counts the direct descendants of this category
  6. $numChildren = $this->Category->childCount(1, true); // will output 2
6.4.2.4.3 generatetreelist

This method will return data similar to find('list'), with an indented prefix to show the structure of your data. Below is an example of what you can expect this method to return see the api for the other find-like parameters.

array(
	[1] =>  "My Categories",
	[2] =>  "_Fun",
	[3] =>  "__Sport",
	[4] =>  "___Surfing",
	[16] => "___Skating",
	[6] =>  "__Friends",
	[7] =>  "___Gerald",
	[8] =>  "___Gwendolyn",
	[9] =>  "_Work",
	[13] => "__Trips",
	[14] => "___National",
	[15] => "___International",
	[17] => "Other People's Categories",
	[5] =>  "_Extreme fishing"
)
6.4.2.4.4 getparentnode

This convenience function will, as the name suggests, return the parent node for any node, or false if the node has no parent (its the root node). For example:

$parent = $this->Category->getparentnode(2); //<- id for fun
// $parent contains All categories
  1. $parent = $this->Category->getparentnode(2); //<- id for fun
  2. // $parent contains All categories
6.4.2.4.5 getpath

The 'path' when refering to hierachial data is how you get from where you are to the top. So for example the path from the category "International" is:

  • My Categories
    • ...
    • Work
      • Trips
        • ...
        • International

Using the id of "International" getpath will return each of the parents in turn (starting from the top).

$parents = $this->Category->getpath(15);
  1. $parents = $this->Category->getpath(15);
// contents of $parents
array(
	[0] =>  array('Category' => array('id' => 1, 'name' => 'My Categories', ..)),
	[1] =>  array('Category' => array('id' => 9, 'name' => 'Work', ..)),
	[2] =>  array('Category' => array('id' => 13, 'name' => 'Trips', ..)),
	[3] =>  array('Category' => array('id' => 15, 'name' => 'International', ..)),
)

6.4.3 Advanced Usage

The tree behavior doesn't only work in the background, there are a number of specific methods defined in the behavior to cater for all your hierarchical data needs, and any unexpected problems that might arise in the process.

6.4.3.1 moveDown

Used to move a single node down the tree. You need to provide the ID of the element to be moved and a positive number of how many positions the node should be moved down. All child nodes for the specified node will also be moved.

Here is an example of a controller action (in a controller named Categories) that moves a specified node down the tree:

function movedown($title = null, $delta = null) {
        $cat = $this->Category->findByTitle($title);
        if (empty($cat)) {
            $this->Session->setFlash('There is no category named ' . $title);
            $this->redirect(array('action' => 'index'), null, true);
        }
        
        $this->Category->id = $cat['Category']['id'];
        
        if ($delta > 0) {  
            $this->Category->moveDown($this->Category->id, abs($delta));
        } else {
            $this->Session->setFlash('Please provide the number of positions the field should be moved down.'); 
        }
    
        $this->redirect(array('action' => 'show'), null, true);
    }
  1. function movedown($title = null, $delta = null) {
  2. $cat = $this->Category->findByTitle($title);
  3. if (empty($cat)) {
  4. $this->Session->setFlash('There is no category named ' . $title);
  5. $this->redirect(array('action' => 'index'), null, true);
  6. }
  7. $this->Category->id = $cat['Category']['id'];
  8. if ($delta > 0) {
  9. $this->Category->moveDown($this->Category->id, abs($delta));
  10. } else {
  11. $this->Session->setFlash('Please provide the number of positions the field should be moved down.');
  12. }
  13. $this->redirect(array('action' => 'show'), null, true);
  14. }

For example, if you'd like to move the "Cookies" category one step down, you would request: /categories/movedown/Cookies/1.

6.4.3.2 moveUp

Used to move a single node up the tree. You need to provide the ID of the element to be moved and a positive number of how many positions the node should be moved up. All child nodes will also be moved.

Here's an example of a controller action (in a controller named Categories) that moves a node up the tree:

function moveup($title = null, $delta = null){
        $cat = $this->Category->findByTitle($title);
        if (empty($cat)) {
            $this->Session->setFlash('There is no category named ' . $title);
            $this->redirect(array('action' => 'index'), null, true);
        }
        
        $this->Category->id = $cat['Category']['id'];
        
        if ($delta > 0) {  
            $this->Category->moveup($this->Category->id, abs($delta));
        } else {
            $this->Session->setFlash('Please provide a number of positions the category should be moved up.'); 
        }
    
        $this->redirect(array('action' => 'index'), null, true);
    
    }
  1. function moveup($title = null, $delta = null){
  2. $cat = $this->Category->findByTitle($title);
  3. if (empty($cat)) {
  4. $this->Session->setFlash('There is no category named ' . $title);
  5. $this->redirect(array('action' => 'index'), null, true);
  6. }
  7. $this->Category->id = $cat['Category']['id'];
  8. if ($delta > 0) {
  9. $this->Category->moveup($this->Category->id, abs($delta));
  10. } else {
  11. $this->Session->setFlash('Please provide a number of positions the category should be moved up.');
  12. }
  13. $this->redirect(array('action' => 'index'), null, true);
  14. }

For example, if you would like to move the category "Desserts" up one level up you would request /categories/moveup/Desserts/1.

6.4.3.3 removeFromTree
6.4.3.4 reorder

This method can be used to sort hierarchical data.