Showing posts with label Tutorial. Show all posts
Showing posts with label Tutorial. Show all posts

Sunday, December 5, 2010

CakePHP: Tweak With Search, Managing Form Post Data

Nowadays, it has rather become a trend to let the visitors sort or filter search results. We let them search by the bestsellers, popular, newest, or, simply by price. We can use a form with a select box having options and let user hit the submit button. You can get the first result page to work pretty well with your default CakePHP setup. But the pagination fails. It is because Cake does not save the form post data. And here is a simple trick I found somewhere in the Internet.

USE SESSION VARIABLES.

In your controller setup:

function my_function() {

if(!empty($this->data)){
$this ->Session->write('search',$this->data['Model']['field']);
$search_string =$this->data['Model']['field'];
} else {
$search_string =$this ->Session->read('search');
}

$condition = array('Model.field_name'=>$search_string);
$search_data = $this->Model->find('all',array('conditions'=>$condition));
$this->set('search_data',$search_data);
}

/* in your view file use */
debug($search_data);
/* you can see the recordset, if debug mode is set to 2 or above. */

Hope this helps someone else as well.
Happy baking.

Saturday, August 7, 2010

How to use SEO urls in CakePHP without the ID

CakePHP offers massive support to create SEO friendly URLs on the fly. You can create SEO-friendly-urls without an ID value to refer to specific post id. It works like a charm like many other Cake MAGIC!

I'll refer to the post table.
1. Create a field called 'slug' (must be UNIQUE and NOT NULL) in the post table.

2. Download and use sluggable behavior to create post slug! Follow instructions step-by-step. It works perfectly.

3. Define routing rules in config/router.php file to display SEO friendly urls.

Router::connect(


       '/posts/:slug',


       array(


               'controller' => 'posts',


               'action' => 'view'


       ),


       array(


               'slug' => '[-a-z0-9]+',


               'pass' => array('slug')


       )


);

4. In posts_controller.php view() function modify query,

function view($slug = null)
{
        if (empty($slug))
        {
                // redirect ...
        }
    
         $data = $this->Post->findBySlug($slug) ; 
      
}

You you should be able to access urls of the form:
http://example.com/your-seo-friendly-post-url/

Hope it helps someone.

Tuesday, February 9, 2010

CakePHP hasAndBelongsToMany (HABTM) Join

Coming back to one of the most critical basics of Cake  - it is hasAndBelongsToMany (HABTM) relationship. You may know it is a type of relationship between two different database tables (Models) in CakePHP. A case study will explain its importance:

Say, you have Category model and Post model.
Each category has many posts and each post belongs to more than one categories.

Had there been only one category per post, it would be enough to use category_id field in posts table. But we are talking about posts each of which may have more than one categories. In this case besides categories and posts table, you need one extra table. By convention - this table should be named as categories_posts (note the alphabetical order of join tables). The table categories_posts will have following fields : id, category_id, post_id.

In your post model define the relationship.

<?php


class Post extends AppModel {
  var $name = 'Post';
  var $hasAndBelongsToMany = array(
  'Category' =>
  array(
  'className'              => 'Category',
  'joinTable'              => 'categories_posts',
  'foreignKey'             => 'post_id',
  'associationForeignKey'  => 'category_id'
  )
  );
  }


?>

This will show you all categories for a given post when you fire:

$this->Recipe->find();

Now the biggest question - how to SAVE records for HABTM association.

The default CakePHP functioning in this regard is not at all adequate. Say you want to add a new category to an existing post which already has a category defined by HABTM association - by default Cake will delete your existing category records for that post from categories_posts table before inserting the new record. That's an absolute mess if you do not really intend to desire existing records.  However, there is the savior.

ExtendAssociations Behavior.
This behavior allows you to easily add or delete HABTM associations!

A full detail of this Behavior can be found in Bakery!

You can download code for this behavior, and save that code as 'extend_associations.php' under '/app/models/behaviors' folder.

In your Post model, add the following code:


 <?php  
var $actsAs = 'ExtendAssociations'; 
?>  

So, our modified Post model should look like this:


 <?php  
class Post extends AppModel { 
    
var $name = 'Post'; 


var $actsAs = 'ExtendAssociations'; 
    
var $hasAndBelongsToMany = array( 
        
'Category' => array( 
            
'className' => 'Category', 
            
'joinTable' => 'categories_posts', 
            
'foreignKey' => 'post_id', 
            
'associationForeignKey' => 'category_id', 
        
), 
    
); 

?>  

Defining ACTION in PostsController
file: // app/controllers/posts_controller.php
Now in your PostsController - to ADD categories to a post use:


 <?php 
// to add only one category for a given post (say, post_id = 1)
$this->Post->habtmAdd('Category', 1, 1); 
// to add multiple categories 
$this->Post->habtmAdd('Category', 1, array(1, 2, 3)); 
?>  

// to DELETE categories for a given post (say, post_id = 1)


 <?php 
// delete a category 
$this->Post->habtmDelete('Category', 1, 1); 
// to delete multiple categories
$this->Post->habtmDelete('Category', 1, array(1, 3)); 
//  to delete all categories 
$this->Post->habtmDeleteAll('Tag', 1); 
?>   


That's it.
Good night.
Sweet Baking.

Tuesday, November 17, 2009

Creating a Complete User Registration System with CakePHP

I was just browsing through my old posts. Hmm. it looks okay. I've started newly with Cake (CakePHP), so, I'm to really learn a lot myself. Anyway, as I'm through my process of learning, I thought it would be great to keep track of what I'm actually reading to get me into the GAME quickly.

My today's task was to learn about a simple user registration system. Once again, I had to go through the CakePHP book and some other references. Instead of listing every detailed step, I would prefer to refer to those MUST read links, which just work like a CHARM in creating a User Management/Registration System using Cake.

Step: 1 Set up CakePHP Console
The console works like a charm. If you had problem in using this console in windows environment, simply, follow the step-by-step method given here.

Step: 2 Follow the CakePHP Simple ACL Control Application
This complete tutorial will guide you through the process of creating your user management system. But before going through this tutorial, please, make sure to understand basic working principles of Cake nicely. The tutorial makes full use of different Cake's core components like Acl Component & Auth Component.

Step: 3 Set custom routing
file: // app/config/routes.php

Copy paste following codes:

Router::connect('/login', array('controller' => 'users', 'action' => 'login'));
Router::connect('/logout', array('controller' => 'users', 'action' => 'logout'));
Router::connect('/register', array('controller' => 'users', 'action' => 'register'));


This will show login form, when someone types http://caketest.local/login and likewise.


Step: 3 Create a dynamic login/logout menu
The Cakebook tutorial does not include creating a dynamic login/logout menu. So, you need to create one.
1. Create a new file.
2. Copy-paste the following code.

<?php 
if(!$session->check('Auth.User')){
echo $html->link('Login','/login');
} else {
$username = $session->read('Auth.User.username');
echo " Hello ". $username ."&nbsp;";
echo $html->link("(logout)", "/logout", array(), null, false);
}
?>

3. Save this file as '/app/views/elements/login_menu.ctp'

4. Open '/app/views/layouts/default.ctp'
5. Copy-paste the following code.

<?php echo $this-> element('login_menu'); ?>

6. Save this file.

Now you can see the login/logout option.
Notice I have used SESSION variables to control login/logout option. To learn more about CakePHP session, please visit this page. For a formatted output of contents inside session variables, use pr($_SESSION) - STRICTLY for DEBUG;


Step 4:  Ban a user account
1.Fire the following SQL query:


ALTER TABLE `users` ADD  `is_banned` TINYINT NOT NULL DEFAULT '0';

This adds a field 'is_banned' in the 'users' table. Set default values to zero.

2. Now copy-paste following code in UsersController::beforeFilter()
file:// app/controllers/users_controller.php

$this->Auth->userScope = array('User.is_banned' => 0);

3. Done. Cake will not allow users to login, when you have set 'is_banned' = 1.
Step 5: Email Validation during user registration
The code is pretty long and nicely explained here. To run with my User model (based on CakePHP's default ACL Component), I needed to make some small adjustment. So, I think it is better to give the codes intact here.

file://  app/controllers/users_controller.php


<?php
 uses('sanitize');
class UsersController extends AppController {



        var $name = 'Users';
var $components = array('Email','Auth');
                                                                    /* "Email' component will handle emailing tasks, 'Auth'    component will handle User Management */
var $helpers = array('Html', 'Form');

/* ..... member functions will go here ... */
}

function beforeFilter()

/* CakePHP CallBack methods */
function beforeFilter() {
   parent::beforeFilter(); 
$this->Email->delivery = 'debug'; /* used to debug email message */
$this->Auth->autoRedirect = false; /* this allows us to run further checks on login() action.*/
$this->Auth->allow('register', 'thanks', 'confirm', 'logout'); 
$this->Auth->userScope = array('User.is_banned' => 0); /* admin can ban a user by updating `is_banned` field of users table to '1' */
}

function register()

// Allows a user to sign up for a new account
        function register() {

                if (!empty($this->data)) {
                        // Applying Auth Components's Password Hashing Rules
/*
We have commented the following field as this was double-hashing password.
$this->Auth->password($this->data['User']['passwrd']); 

*/
                   //      $this->data['User']['passwrd'] = $this->Auth->password($this->data['User']['passwrd']);
 
                        $this->User->data = Sanitize::clean($this->data);
           
// Successfully created account – send activation email     
            
                        if ($this->User->save()) {
                                $this->__sendActivationEmail($this->User->getLastInsertID());


// pr($this->Session->read('Message.email')); /*Uncomment this code to view the content of email FOR DEBUG */


                                // this view is not show / listed – use your imagination and inform
                                // users that an activation email has been sent out to them.
                                $this->redirect('/users/thanks');
                        }
                        // Failed, clear password field
                        else {
                                $this->data['User']['passwrd'] = null;
                        }
                }
$groups = $this->User->Group->find('list');
$this->set(compact('groups'));
        }


Function login()


function login() {
                // Check for incoming login request.
//pr($this->data);
                if ($this->data) {
                        // Use the AuthComponent's login action
                        if ($this->Auth->login($this->data)) {
                                // Retrieve user data
                                $results = $this->User->find(array('User.username' => $this->data['User']['username']), array('User.active'), null, false);
                                // Check to see if the User's account isn't active
                                if ($results['User']['active'] == 0) {
                                        // Uh Oh!
                                        $this->Session->setFlash('Your account has not been activated yet!');
                                        $this->Auth->logout();
                                        $this->redirect('/users/login');
                                }
                                // Cool, user is active, redirect post login
                                else {
                                        $this->redirect('/');
                                }
                        }
                }
        }

function logout()
function logout() {
$this->Session->setFlash('Good-Bye');
$this->redirect($this->Auth->logout());
}


/* function to validate activation link

* and to set 'active' = 1
*/  

function activate()

function activate($user_id = null, $in_hash = null) {

        $this->User->id = $user_id;

if ($this->User->exists() && ($in_hash == $this->User->getActivationHash())) {
         if (empty($this->data)) {

$this->data = $this->User->read(null, $user_id);
   // Update the active flag in the database
$this->User->set('active', 1);
$this->User->save();

$this->Session->setFlash('Your account has been activated, please log in below.');
                $this->redirect('login');
}
}

     // Activation failed, render '/views/user/activate.ctp' which should tell the user.
}


function __sendActivationEmail()

/* function to send activation email */
 function __sendActivationEmail($user_id) {
                $user = $this->User->find(array('User.id' => $user_id), array('User.email', 'User.username','User.id'), null, false);
                if ($user === false) {
                        debug(__METHOD__." failed to retrieve User data for user.id: {$user_id}");
                        return false;
                }

                // Set data for the "view" of the Email
                $this->set('activate_url', 'http://' . env('SERVER_NAME') . '/users/activate/' . $user['User']['id'] . '/' . $this->User->getActivationHash());
                $this->set('username', $this->data['User']['username']);
                
                $this->Email->to = $user['User']['email'];
                $this->Email->subject = env('SERVER_NAME') . ' – Please confirm your email address';
                $this->Email->from = 'noreply@' . env('SERVER_NAME');
                $this->Email->template = 'user_confirm';
                $this->Email->sendAs = 'text';   // you probably want to use both :)    
                return $this->Email->send();

        }

Copy paste function getActivationHash at file:// app/models/user.php

function getActivationHash()
        {
                if (!isset($this->id)) {
                        return false;
                }
                return substr(Security::hash(Configure::read('Security.salt') . $this->field('created') . date('Ymd')), 0, 8);
        }
Copy-paste following code in the file:// app/app_controller.php inside the function beforeFilter()
function beforeFilter() {
$this->Auth->fields = array('username' => 'username', 'password' => 'passwrd');
       /* ... Rest of the function body goes here */
}

Now View Files


Registration form
file:// app/views/users/register.ctp



<h2>Create an Account</h2>
<?php
echo $form->create('User', array('action' => 'register'));
echo $form->input('username');
// Force the FormHelper to render a password field, and change the label.
echo $form->input('group_id', array('type' => 'hidden', 'value' => 'Insert-Default-Value'));
echo $form->input('passwrd', array('type' => 'password', 'label' => 'Password'));
echo $form->input('email', array('between' => 'We need to send you a confirmation email to check you are human.'));
echo $form->submit('Create Account');
echo $form->end();
?>

Notice replace 'Insert-Default-Value' with the actual value of your group_id.
   
Login form
file:// app/views/users/login.ctp



<?php
echo $form->create('User', array('action' => 'login'));
echo $form->input('username');
echo $form->input('passwrd', array('label' => 'Password', 'type' => 'password'));
echo $form->end('Login');
?>



user_confirm.ctp
file:// app/views/elements/email/text/user_confirm.ctp

<?php
  # /app/views/elements/email/text/user_confirm.ctp
  ?>
  Hey there <?= $username ?>, we will have you up and running in no time, but first we just need you to confirm your user account by clicking the link below:
  <?= $activate_url ?>

With all the above scripts, you should be able to get a workable user registration system.
Here, you will have groups/ users/ and you can set group level access per controller, even per action following Cake's default mechanism!

[Acknowledgements]
My sincere regards to Jonny Revees for his wonderful work on this CakePHP user registration system. It works like a charm!


Here are some more stuff I found helpful:
CakePHP Auth Component variables.
Understanding CakePHP Session
Saving data in CakePHP found in book.cakephp.org
Debuggable.com - this post explains how to debug CakePHP email.

Sunday, November 15, 2009

How to List Posts by Categories

There's a lot to bake with Cake. I'm entering into deeper domains. I hope you are also following me.
Today, I'll show you how to list posts by categories. To do so, we will use 'containable' behavior.

Edit Category Model
file:// app/models/category.php
Replace:
var $actsAs = array('Tree');
With
var $actsAs = array('Tree','Containable');
Notice I have added the word 'Containable' in the $actsAs behavior.

Add  listposts() 'action' in Category Controller
file:// app/controllers/categories_controller.php

function listposts() {


$listPosts = $this->Category->find('all');
$this->set(compact('listPosts'));
}
Create a  VIEW file for showing query result.
file:// app/views/categories/listposts.ctp



<h1>Posts found by category</h1>
  <?php
  foreach ($listPosts as $key => $value) {
    
   echo "<h3>" . $value["Category"]["name"] .   "</h3>";
  $posts = $value["Post"];
   foreach ($posts as $post) {
    echo "<b>" . $post["title"] . "</b>";
    echo "<br />";
   }
   }
  ?>


Now point your browser to:
http://caketest.local/categories/listposts


You should see something like this:



That's it!

Saturday, November 14, 2009

How to Join Database Tables (Models) in CakePHP

CakePHP has four association types to handle all database table (model) joining operations.
1. hasOne
2. hasMany
3. belongsTo
4. hasAndBelongsToMany (HABTM)

In our database model, each category should have many posts.
First add a foreign key, category_id.  in the post table.
SQL:


ALTER TABLE `posts` ADD `category_id` INT NOT NULL AFTER `id` ;

Foreign Key Convention
Notice the use of (model_name)_id field in the post table. This is a Cake's convention to name foreign key in related tables. Also notice that I have used category_id, and not categories_id. So, the foreign key rule is to name the foreign key field as (model_name_in_singular)_id in the related table. That's it.

Now I'll update both category model and post model to include their association.

Category Model
File: // app/models/category.php

<?php
class Category extends AppModel {
var $name = 'Category';
var $actsAs = array('Tree');
var $hasMany = 'Post'; /* each category has many posts */
}
?>

Post Model
File: // app/models/post.php

<?php
class Post extends AppModel {
var $name = 'Post';
var $belongsTo = 'Category'; /* each post belongs to a category */
var $validate = array(
'title' => 'notEmpty',
'body'  => 'notEmpty'
);


}
?>

Modify PostsController Class
Now in posts_controller.php, modify your add function.
file: // app/controllers/posts_controller.php


 function add() {
if (!empty($this->data)) {


if ($this->Post->save($this->data)) {
$this->Session->setFlash('Your post has been saved.');
$this->redirect(array('action' => 'index'));
}
} else {


$this->set('categories', $this->Post->Category->generatetreelist(null,null,null," - "));


}


}


Note:
$this->set('categories', $this->Post->Category->generatetreelist(null,null,null," - "));
This will fetch the category tree from categories table.

Modify Related Views to show category tree.
file: // app/views/posts/add.ctp

<h1>Add Post</h1>
<?php
echo $form->create('Post');
echo $form->input('category_id',array('label'=>'Select a category'));
echo $form->input('title');
echo $form->input('body', array('rows' => '3'));
echo $form->end('Save Post');
?>



Now, point your browser to:
http://caketest.local/posts/add

In this example: Cake has created the magical associations to take care of everything. Cake generates Category Tree, displays it in the form, and save category_id in the posts table on the fly.

For a complete tutorial on Models, Saving/ Retrieving Data, and building associations between different models in CakePHP, please see this tutorial.

Now in my next example, I'll try to list posts by categories.

Take care.

Update
Besides, you might just want to update posts as well.

Modify PostsController
Modify edit() function in the following file.
File: // app/controllers/posts_controller.php

function edit($id = null) {
$this->Post->id = $id;
if (empty($this->data)) {
$this->data = $this->Post->read();
$this->set('categories', $this->Post->Category->generatetreelist(null,null,null," - "));


} else {
if ($this->Post->save($this->data)) {
$this->Session->setFlash('Your post has been updated.');
$this->redirect(array('action' => 'index'));
}
}
}

2. Now modify post view
file: // app/views/posts/edit.php


<h1>Edit Post</h1>
<?php
echo $form->create('Post', array('action' => 'edit'));
echo $form->input('category_id',array('label'=>'Select a category'));
echo $form->input('title');
echo $form->input('body', array('rows' => '3'));
echo $form->input('id', array('type'=>'hidden')); 
echo $form->end('Save Post');
?>

Now if you try to edit an existing post, you can see the category tree with the selected category being displayed.
Done.

How to Create a Category Tree with CakePHP 'Tree' behavior

Okay! Let's try to create a category tree using CakePHP (This is something like parent category -> child category type records).

SQL:

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)
);

Now insert some record:
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(1, 'Tutorials', NULL, 1, 8);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(2, 'PHP', 1, 2, 5);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(3, 'MySQL', 1, 6, 7);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(4, 'CakePHP', 2, 3, 4);

Now I'll create a model for this category.
1. Create a new file.
2. Copy-paste the following code:

<?php
   class Category extends AppModel {  
var $name = 'Category';
   var $actsAs = array('Tree');
}
?>

3. Save the files as app/models/category.php.

Note:
The variable $actsAs tells Cake to attach 'Tree' behavior to this model, i.e.,  Cake will generate a Tree data structure for category model. I think it is also a good time to introduce you with another fascinating feature of CakePHP - 'Behaviors'. CakePHP has built-in behaviors, like - behaviors for tree structures, translated content, access control list interaction etc., which you can attach with any model. As you might know - 'add', 'edit', 'delete' options for these type of data structures need special care. Cake takes care of it once you have specified the applicable 'behavior' in the model. Behaviors are attached with models using $actsAs variable. In this case, I have specified $actsAs = array('Tree'). This will enforce 'Tree' behavior on Category model. Simple.
To learn more about 'Behaviors', please refer to CakePHP online book.

Now I'll create CategoriesController
Step:
1. Create a new file.
2. Copy-paste the following code.

<?php
class CategoriesController extends AppController {
            var $name = 'Categories';  
             
function index() {
                  $categories = $this->Category->generatetreelist(null, null, null, '&nbsp;&nbsp;&nbsp;');
                  $this->set(compact('categories'));    
                  }
 }
?>
3. Save that file as categories_controller.php under 'app/controllers' folder.

Note: generatetreelist() method generates a tree-type views for our Categories. There are lots of options you can use with this method. For a complete guidelines on options for this method, please refer to CakePHP book.
compact(); function is used to pass variables to your views in CakePHP. Compact() method detects the variables having the same name (in this case 'categories') in your Controller and splits them as an array() of $key => value pairs. Now $this->set() is used to set those values for using them in your view file.

Now I'll create a view for our index() function.
file: '/app/views/categories/index.php'

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
  echo "<li>$value</li>";
  }
  echo "</ul>";
?>


Now point your browser to:
http://caketest.local/categories

And you should see following structure:

> Tutorials    
   > PHP    
> CakePHP    
> MySQL

To Add a new category to the list:
1. Open categories_controller.php (found under '/app/controllers')
2. Copy-paste the following function:


function add() {


if (!empty($this -> data) ) {
$this->Category->save($this -> data);
$this->Session->setFlash('A new category has been added');
$this->redirect(array('action' => 'index'));
} else {
$parents[0] = "[Top]";
$categories = $this->Category->generatetreelist(null,null,null," - ");
if($categories) {
foreach ($categories as $key=>$value)
$parents[$key] = $value;
}
$this->set(compact('parents'));
}


}

3. Save this file.

Now we need to create a view file for this add() method (to display the add category form).

1. Create a new file.
2. Copy-paste the following code:


<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>

3. Save the file as '/app/views/categories/add.ctp'

Now point your browser to this location:
http://caketest.local/categories/add

You should be able to add a new category.

EDIT Category
To edit category, I'll specify a controller action. To do so:
1. Open categories_controller.php (found under '/app/controllers').
2. Copy-paste the following function:


function edit($id=null) {
if (!empty($this->data)) {
if($this->Category->save($this->data)==false)
$this->Session->setFlash('Error saving Node.');
$this->redirect(array('action'=>'index'));
} else {
if($id==null) die("No ID received");
$this->data = $this->Category->read(null, $id);
$parents[0] = "[ Top ]";
$categories = $this->Category->generatetreelist(null,null,null," - ");
if($categories) 
foreach ($categories as $key=>$value)
$parents[$key] = $value;
$this->set(compact('parents'));
}
}


3. Save that function.

Now, I'll write the view file (the form to edit a category). To do so:
1. Create a new file.
2. Copy paste the following code:


<?php
echo $html->link('Back',array('action'=>'index'));
echo $form->create('Category');
  echo $form->hidden('id');
  echo $form->input('name');
  echo $form->input('parent_id', array('selected'=>$this->data['Category']['parent_id']));
  echo $form->end('Update');
?>

3. Now save the file as '/app/views/categories/edit.ctp'.

Hold on!
There is one more thing we should do. We need to show the link to edit record. To do show:
1. Open '/app/views/categories/index.ctp' file.
2. Replace the existing code with this one:

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
 echo "<li>$value &nbsp;[$edit]</li>";
  }
  echo "</ul>";
?>

3. Now save the file.

Now point your browser to:
http://caketest.local/categories/
You should be able to see the 'Edit' option against each category name.

Delete Category
CakePHP Format:
removeFromTree($id=null, $delete=false)

Using this method will either delete [to delete, set ($delete=true)] or move a node but retain its sub-tree, which will be re-parented one level higher.

Steps:
1. Open '/app/controllers/categories_controller.php'
2. Copy paste the following code:









function delete($id=null) {
  if($id==null)
  die("No ID received");
  $this->Category->id=$id;
  if($this->Category->removeFromTree($id,true)==false)
     $this->Session->setFlash('The Category could not be deleted.');
   $this->Session->setFlash('Category has been deleted.');
   $this->redirect(array('action'=>'index'));
}

3. Now save that file.

Next, I'll display the option to 'delete' a category.
Step:
1. Open '/app/views/categories/index.ctp
2. Replace the existing code with this one:

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
  $delete = $html->link("Delete", array('action'=>'delete', $key));
  echo "<li>$value &nbsp;[$edit]&nbsp;[$delete]</li>";
  }
  echo "</ul>";
?>
3. Save the file.

Done
Point your browser to:
http://caketest.local/categories
Here is a screenshot of what you should see:


[ACKNOWLEDGEMENT]
The code above is mostly based on
Bram Borggreve's beautiful website - Tree Behavior
I express my sincere gratitude to Bram for his wonderful contribution.
Further, to learn more about 'Tree' behavior, please visit: CakePHP Book.





Here is the COMPLETE script files:

1. categories_controller.php ('to be saved under '/app/controllers')


<?php
  class CategoriesController extends AppController {
 var $name = 'Categories';
 function index() {
  $categories = $this->Category->generatetreelist(null, null, null, '&nbsp;&nbsp;&nbsp;');
  // debug ($this->data); die; 
  $this->set(compact('categories')); 
  
  }
  
  function add() {
  
  if (!empty($this -> data) ) {
  $this->Category->save($this -> data);
  $this->Session->setFlash('A new category has been added');
  $this->redirect(array('action' => 'index'));
  } else {
  $parents[0] = "[ Top ]";
  $categories = $this->Category->generatetreelist(null,null,null," - ");
  if($categories) {
  foreach ($categories as $key=>$value)
  $parents[$key] = $value;
  }
  $this->set(compact('parents'));
  }
  
  }

  function edit($id=null) {
  if (!empty($this->data)) {
  if($this->Category->save($this->data)==false)
  $this->Session->setFlash('Error saving Category.');
  $this->redirect(array('action'=>'index'));
  } else {
  if($id==null) die("No ID received");
  $this->data = $this->Category->read(null, $id);
  $parents[0] = "[ Top ]";
  $categories = $this->Category->generatetreelist(null,null,null," - ");
  if($categories) 
  foreach ($categories as $key=>$value)
  $parents[$key] = $value;
  $this->set(compact('parents'));
  }
  }
 function delete($id=null) {
  if($id==null)
  die("No ID received");
  $this->Category->id=$id;
  if($this->Category->removeFromTree($id,true)==false)
  $this->Session->setFlash('The Category could not be deleted.');
  $this->Session->setFlash('Category has been deleted.');
  $this->redirect(array('action'=>'index'));
  }


}
  ?>

2. Category Model (file: '/app/models/category.php')

<?php
class Category extends AppModel { 
var $name = 'Category';
var $actsAs = array('Tree'); 

?> 

3. Views for Category Files
(a)File: '/app/views/categories/index.php'

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
  $delete = $html->link("Delete", array('action'=>'delete', $key));
  echo "<li>$value &nbsp;[$edit]&nbsp;[$delete]</li>";
  }
  echo "</ul>";
?>



(b) File: '/app/views/categories/add.php'





<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>


(c) File: 'app/views/categories/edit.php'

<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>



Thanks for reading the entry.

Thursday, November 12, 2009

CakePHP - Fine-tune Your Scaffolded Application

In my previous post, I tried to explain how you can create a workable application on the fly using scaffolding. Now I'll try to show you how to fine tune your scaffolded application. Since, I'll be referring to my previous post a lot, so, please read that first.


Control Display Fields

In our previous example, say, I want to display only the comments title, body, and created fields. So, I will add the following lines in comments_controller.php (found under '/app/controllers') just below
   var $scaffold;
statement.


function beforeRender() {
if($this->action === 'index'){
$this->set( 'scaffoldFields', array( 'title', 'body', 'created' ) );
  }
}


Note 'beforeRender()' is a CakePHP callback. It gets executed before the actual controller action. This helps you to add some more logic in your controller. There are three controller callback methods in common use: beforeFilter(), beforeRender(), afterFilter()

And here is that much desired screenshot:


For a more detailed discussion, please go through the book.

How to develop a PHP Application on the FLY Application scaffolding with CakePHP

Application scaffolding is a technique that allows a developer to define and create a basic application that can create, retrieve, update and delete objects.
CakePHP allows application scaffolding on the fly. With this technology we can develop a workable application on the fly.
We have already created 'add', 'edit', 'delete', and 'view' methods for 'posts' table.
Let us create another table, 'comments'.

SQL:


CREATE TABLE `comments` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `title` char(50) NOT NULL,
  `body` text NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY  (`id`)
)

Now create the model file.
Step:
1. Create a new file.
2. Copy-paste following lines:
<?php
class Comment extends AppModel {
var $name = 'Comment';
}
?>
3. Save the file as '/app/models/comment.php

Now create the controller file.
1. Create a new file.
2. Copy-paste following lines:

<?php
class CommentsController extends AppController {

var $name = 'Comments';
var $scaffold;
}
?>

3. Save the file as '/app/controllers/comments_controller.php'

Note:
Please note  $scaffold variable. It tells Cake to create a workable application on the fly.
And that's it.

Now point your browser to:
http://caketest.local/comments

You can see a full featured comments application with 'add', 'view', 'edit', 'delete' option.

Here is a screenshot:
In my next post, I'll try to demonstrate how can you fine-tune your scaffolded application.

Cheers!

CakePHP - Make Your URL SEO Friendly Using Routes

Routing is a feature that maps URLs to controller actions.


URL pattern default routes:
    http://example.com/controller/action/param1/param2/param3

In our example:
http://caketest.local/posts/add   /* here 'posts' is the controller and 'add' is the action.
It will map to: PostsContoller -> add();

http://caketest.local/posts/edit/2 /* here 'posts' is the controller, 'add' is the action, and '2' is the param1
It will map to: PostsController->edit(2);

Routes are defined in '/app/config/routes.php' file using the Router::connect() method.

Basic format:

Router::connect(
    'URL',
    array('paramName' => 'defaultValue'),
    array('paramName' => 'matchingRegex')
)

Now, copy-paste following code in that file:


Router::connect(
'/posts/:slug-:id',
array('controller' => 'posts', 'action' => 'view'),
array(
// order matters
'pass' => array('slug' , 'id'),
'id' => '[0-9]+'
)
);

'/posts/:slug-:id'  is the default URL patter. (Here 'posts' can be replaced by any string, 'slug' is a unique identifier used as url part, and 'id' is the article id).
The next array() will match this URL pattern with $PostsController->view() method.
The array key 'pass' will pass two parameters 'slug' and 'id', the next line sets up rules for acceptable values for 'id'.
That's it.

Now in your index.ctp file, (found under '/app/views/posts/index.ctp')

Replace these lines:
echo $html->link($post['Post']['title'],
"/posts/view/".$post['Post']['id']);

With:
echo $html->link($post['Post']['title'], array(
'controller' => 'posts',
'action' => 'view',
'slug' => Inflector::slug($post['Post']['title'],'-'),
'id' => $post['Post']['id'] )
);

Note: we have used Inflector::slug method to generate the SEO friendly url part.
And you are done. 

To view a particular post,
Add this method at PostsController (File '/app/controllers/posts_controller.php)

function view($id = null, $slug = null) {
$this->Post->id = $this->params['id'];
$this->set('post', $this->Post->read());
}

Now create a new view file: ('/app/views/posts/view.ctp')
<!-- File: /app/views/posts/view.ctp -->
<h1><?php echo $post['Post']['title']?></h1>
<p><small>Created: <?php echo $post['Post']['created']?></small></p>
<p><?php echo $post['Post']['body']?></p>

Now point your browser to:
http://caketest.local/posts/

You can see the list of posts. Note the titles have been made SEO friendly. (Like: http://caketest.local/posts/Second-Blog-Post-Title-2 ). You can read the post by following the post title.

That's it.
Since CakePHP routing has a lot more to do, you MUST read this article thoroughly to understand how it works. This is very important to fine tune your application.

So, I think we have learnt how to include 'ADD', 'UPDATE', 'DELETE', and 'VIEW' operations in a database, plus, how to make SEO friendly urls.

In my next post, I'll tell you how to develop a workable application within minutes using 'scaffolding'.

CakePHP - Edit a Record in Database

So, the keen readers may guess what we need to do to edit a record in our 'posts' table.
Step:
1. Open posts_controller.php (found under '/app/controllers' folder).
2. Copy-paster the function edit() to edit record.


function edit($id = null) 
{   $this->Post->id = $id; 
if (empty($this->data)) { 
$this->data = $this->Post->read(); 
}
else {  
if ($this->Post->save($this->data)) {

  $this->Session->setFlash('Your post has been updated.');
  $this->redirect(array('action' => 'index')); 
}  
}  


}
As such, this function edit() is self-explanatory. If you read it carefully, the function edit() expects '$id' value.
$this=>data contains form submitted data. If it is empty ($this=>data will obviously be empty the first time you try to edit a record), the function $this->Post->read() reads the record from database and stores it at $this->data variable to display the same via edit form. If some data has been submitted, the function $this->Post->save($this->data); tries to save that data by updating the respective record.

Now we need to create a form (Cake calls it 'view') to edit record.
Step:
1. Create a new file.
2. Copy-paste following code:
<h1>Edit Post</h1>
<?php  
echo $form->create('Post', array('action' => 'edit'));  
echo $form->input('title');  
echo $form->input('body', array('rows' => '3'));  
echo $form->input('id', array('type'=>'hidden'));  
echo $form->end('Save Post');  
?>
3. Save this file as 'app/views/posts/edit.ctp'

Now we need to add a link to our index.ctp file to display edit option.

Step:
1. Open index.ctp (found under '/app/views/posts' folder)
2. Add the following line just below the line we have added to show 'delete' option.
<?php echo $html->link('Edit', array('action'=>'edit', 'id'=>$post['Post']['id']));?>
3. Save that file.

Now point your browser to:
http://caketest.local/posts

You can see the 'Edit' option.
Now if you follow 'Edit' link, you can see the form with respective record.
Here is a screenshot:


  
In my next blog post, I'll show you how to make your url SEO friendly using CakePHP.