How to create a CMS with CakePHP

Published on Mar 7, 2009 by Jamie Munro

I really enjoying writing code and I find that snippets just don't always cut it for me.  So in today's article, I am going to describe the process of creating a CMS (Content Management System) with CakePHP.

This will be a two part article, in part one we will focus on getting the basics working.  The basics will include ability to add, edit, and delete static content pages.

Part two will advance on our basis and allow us to create drafts and revert back to previous versions.

To begin, let's download the latest release of CakePHP.  After you've downloaded it, extract it to a folder of your choice.  In my case it will be c:\xampplite\htdocs\CMS.  I can now access my new web site by browsing to http://localhost/CMS.



At this point we now have our basic CakePHP web site installed.  To proceed further, we will need to update our database.php file in app/config.  Update the $default array with your database host, name, username, and password.  Once you are done, save the file.  Also inside this directory, we need to update our security salt.  I normally just randomly choose alphanumeric characters, about 32 characters in length.  Once we are done, we can save the file.

To begin working, we need to create our database and tables.  Create a new database with the same name specified in your database config file.  Ensure you have created the username and password as well.

Let's continue by creating our users table.  Below is a sample CREATE script that will get you started.  If you require additional fields, you may add them in:

CREATE TABLE  `users` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `first` varchar(45) NOT NULL,
  `last` varchar(45) NOT NULL,
  `email` varchar(45) NOT NULL,
  `username` varchar(45) NOT NULL,
  `password` varchar(45) NOT NULL,
  `created` datetime default NULL,
  `modified` datetime default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


Knowing that our plans for part two will be to create drafts and revisions, let's also create a pages table that will store our information.  At this point it won't save drafts, but it will save us the time of back-populating the main page table.  If I wasn't planning on allowing revision history, I would skip this step and perform directory options to retrieve my list of pages instead.  Here is a sample create statement for our pages table:

CREATE TABLE  `pages` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `title` varchar(255) NOT NULL,
  `body` text NOT NULL,
  `user_id` int(10) unsigned NOT NULL,
  `created` datetime default NULL,
  `modified` datetime default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


Do help us save a few lines of typing, let's use CakePHP's bakery to create our basic models, controllers, and views.  We will have to update some of these later, but it will save us some time.

First up is our model.  Perform the following commands:

Click Start -> Run
Type: "cmd" without the quotes and click OK
Type: "cd \xampplite\htdocs\CMS" (update to your path) and press enter
Type: "cake\console\cake bake" and press enter
Type: "M" and press enter
Enter again (to use default database config)
Type: "1" and press enter to create the Page model
Type: "y" and press enter.  This will build our validation
Type: "29" and press enter
Type: "20" and press enter
Type: "20" and press enter
Type: "29" and press enter
Type: "29" and press enter
Type: "29" and press enter
Type: "y" and press enter to add a relationship
Type: "y" and press enter to add a relationship to user
Type: "n" and press enter
Type: "y" and press enter to confirm the relationship
Type: "n" and press enter to skip creating unit tests
Type: "M" and press enter
Enter again (to use default database config)
TYpe: "2" and press enter to create the User model
Type: "n" and press enter to skip validation
Type: "y" and press enter to add a relationship
Type: "y" and press enter to add a relationship to page
Type: "n" and press enter
Type: "y" and press enter to confirm the relationship
Type: "n" and press enter to skip creating unit tests


We have now successfully created our two models.  Let's continue by creating our controllers.  To create our controllers:

Type: "C" and press enter
Type: "1" and press enter to create the pages controller
Type: "y" and press enter
Type: "n" and press enter
Type: "y" and press enter
Type: "n" and press enter
Type: "n" and press enter
Type: "n" and press enter
Type: "y" and press enter
Type: "y" and press enter
Type: "n" and press enter
Type: "C" and press enter
Type: "2" and press enter to create the users controller
Type: "n" and press enter
Type: "n" and press enter
Type: "y" and press enter
Type: "n" and press enter


Now we have both our models and controllers created.  Let's also create our views for our add and edit forms:

Type: "V" and press enter
Type: "1" and press enter
Type: "y" and press enter
Type: "n" and press enter


Let's do a quick recap to summarize what we have done so far.

  1. Installed CakePHP

  2. Configured our setup to connect to our database

  3. Created our two database tables

  4. Used the bakery to create our models, controllers, and views


Coming up next.

  1. Update our pages controller when add, editing, and saving to write/delete files


Before we can begin, we need to fix a small issue that we have created by using the pages controller.  By default CakePHP will allow you create views in the pages folder and automatically display them.  I like this feature and have plans to use it in our CMS, so let's copy the display() function from: cake/libs/controller/pages_controller.php to: app/controllers/pages_controller.php.  Be sure just to copy the individual function and not the entire file.

Next up, we need to fix our routes because by default CakePHP routes all pages/* to the above display function.  We do wish to use this function, but not for our standard list, add, edit, and delete.

Open the file app/config/routes.php and add the following lines (make sure you add these before the pages/* line):

 Router::connect('/pages/index', array('controller' => 'pages', 'action' => 'index'));
 Router::connect('/pages/add', array('controller' => 'pages', 'action' => 'add'));
 Router::connect('/pages/edit/*', array('controller' => 'pages', 'action' => 'edit'));
 Router::connect('/pages/delete/*', array('controller' => 'pages', 'action' => 'delete'));


Let's now update our pages_controller to save the file when we add and edit it and delete the file when we delete a page.  Below is our completed pages_controller.php

class PagesController extends AppController {

 var $name = 'Pages';
 var $helpers = array('Html', 'Form');

 function index() {
  $this->Page->recursive = 0;
  $this->set('pages', $this->paginate());
 }

 function add() {
  if (!empty($this->data)) {
   $this->Page->create();
   if ($this->Page->save($this->data)) {
    // save a physical copy of the file
    $this->_saveFile($this->data);
    
    $this->Session->setFlash(__('The Page has been saved', true));
    $this->redirect(array('action'=>'index'));
   } else {
    $this->Session->setFlash(__('The Page could not be saved. Please, try again.', true));
   }
  }
  $users = $this->Page->User->find('list');
  $this->set(compact('users'));
 }

 function edit($id = null) {
  if (!$id && empty($this->data)) {
   $this->Session->setFlash(__('Invalid Page', true));
   $this->redirect(array('action'=>'index'));
  }
  if (!empty($this->data)) {
   if ($this->Page->save($this->data)) {
    // save a physical copy of the file
    $this->_saveFile($this->data);
    
    $this->Session->setFlash(__('The Page has been saved', true));
    $this->redirect(array('action'=>'index'));
   } else {
    $this->Session->setFlash(__('The Page could not be saved. Please, try again.', true));
   }
  }
  if (empty($this->data)) {
   $this->data = $this->Page->read(null, $id);
  }
  $users = $this->Page->User->find('list');
  $this->set(compact('users'));
 }

 function delete($id = null) {
  if (!$id) {
   $this->Session->setFlash(__('Invalid id for Page', true));
   $this->redirect(array('action'=>'index'));
  }
  
  // get our file name
  $this->Page->id = $id;
  $page = $this->Page->read();
  
  if ($this->Page->del($id)) {
   // delete our file
   $this->_deleteFile($page);
   
   $this->Session->setFlash(__('Page deleted', true));
   $this->redirect(array('action'=>'index'));
  }
 }
 
 function _saveFile($data) {
  App::import('Sanitize');
  // clean the file name and replace spaces with dashes
  // and save the file locally
  file_put_contents($this->_buildPath($data), $data['Page']['body']);
 }
 
 function _deleteFile($data) {
  App::import('Sanitize');
  // clean the file name and replace spaces with dashes
  // and delete the file locally
  if (file_exists($filepath = $this->_buildPath($data))) {
   unlink($filepath);
  }
 }
 
 function _buildPath($data) {
  return APP . 'views\\pages\\' . Sanitize::paranoid(str_replace(' ', '-', $data['Page']['title']), array('-')) . '.ctp';
 }
 
 /**
  * Displays a view
  *
  * @param mixed What page to display
  * @access public
  */
 function display() {
  $path = func_get_args();

  $count = count($path);
  if (!$count) {
   $this->redirect('/');
  }
  $page = $subpage = $title = null;

  if (!empty($path[0])) {
   $page = $path[0];
  }
  if (!empty($path[1])) {
   $subpage = $path[1];
  }
  if (!empty($path[$count - 1])) {
   $title = Inflector::humanize($path[$count - 1]);
  }
  $this->set(compact('page', 'subpage', 'title'));
  $this->render(join('/', $path));
 }

}
To add a new page go to: http://localhost/CMS/pages/add.  To view a list of your pages go to: http://localhost/CMS/pages/index.  To view a page that you have created go to: http://localhost/CMS/pages/page-title-with-nospaces-and-dashes-instead

That's it for today's tutorial.  For some extra-curricular activities, add login protection to the index(), add(), edit(), and delete() functions and update the add and edit functions and views to use the logged in user instead of the drop-down that the bakery created for us.

Tags: CakePHP | php | cms

Related Posts

blog comments powered by Disqus