Archive for the ‘ PHP ’ Category

How to Maintain Simple, Static Pages in CakePHP

Cake’s default way of handling simple static content is to use the built-in PagesController to serve up .tpl files from /app/views/pages. This is a simple and straightforward approach and works for very small websites, but comes with some obvious drawbacks:

  • Making changes to the content of the pages requires editing template files;
  • There’s no easy way (generally) to edit these pages the way you’d edit other content on your site, using controllers with admin actions, for example;
  • There’s no way to specify a hierarchy of pages, which can be quite useful for large websites;
  • The URL structure of the pages, although it follows Cake’s URL conventions, isn’t intuitive and looks pretty clunky and unprofessional. It would be much nicer to have /about rather than /pages/about, and so forth.

Logically, it makes more sense to place static content in the database so that it can be manipulated just like any other model. Additionally, we want our URLs to be pretty.

This is surprisingly easy to accomplish, thanks in part to this article, which shows how to load a model from within our routes configuration. I use the same technique, but my code below is updated to run on CakePHP 1.2, and I’ll show you how to create a hierarchy of nested pages as well.

Database

Let’s start by creating the database schema for our model. I call mine “StaticPage”, but you can name yours whatever you’d like. I don’t use “Page” to avoid controller conflicts down the road should I ever decide to make use of Cake’s PagesController for anything.

CREATE TABLE IF NOT EXISTS `static_pages` (
  `id` INTEGER(12) NOT NULL AUTO_INCREMENT,
  `parent_id` INTEGER(12) NULL,
  `title` VARCHAR(128) NOT NULL,
  `slug` VARCHAR(128) NOT NULL,
  `content` LONGTEXT NOT NULL,
  PRIMARY_KEY(`id`)
);

Model

Next, let’s create the actual model in CakePHP. We need to define the belongsTo and hasMany relationships for the tree hierarchy to work properly:

<?php
class StaticPage extends AppModel {

  var $belongsTo = array(
    'ParentPage' => array(
      'className' => 'StaticPage',
      'foreignKey' => 'parent_id'
  ));

  var $hasMany = array(
    'ChildPage' => array(
      'className' => 'StaticPage',
      'foreignKey' => 'parent_id',
      'dependent' => false
  ));
}
?>

Controller

Moving on, we create our StaticPagesController. You’ll likely want to create your own admin CRUD actions (the entire point of this, afterall, is to be able to manage these pages dynamically!), but for simplicity I’m just going to define the one action we need to display our pages. Conveniently, we’re just going to use the “index” action:

<?php
function index( $slug = null ) {
  if (!$slug) {
    $this->Session->setFlash(__('Invalid StaticPage.', true));
    $this->redirect(array('action'=>'index'));
  }
  $staticPage = $this->StaticPage->find('first', array(
    'conditions' => array(
      'StaticPage.slug' => $slug
  )));
  $this->set(compact('staticPage'));
  $this->pageTitle = $staticPage['StaticPage']['title'];
}
?>

Notice we’re going to be using the slug as the unique identifier when looking up the page. But what happens if we have two pages with the same slug? As you’ll see, that won’t be a problem as long as they’re nested under separate parent pages.

Routing

Now for the key part: we need to set up a custom route to handle our pages. In our routes.php file, we’re going to pull a list of static page slugs from the database and use them as the regular expression to match against with our route:

<?php
// routes.php

App::import('Model', 'StaticPage');
$page = new StaticPage();
$slugs = $page->find('list', array(
  'fields' => array('StaticPage.slug'),
  'order' => 'StaticPage.slug DESC'
));

Router::connect('/:slug/',
  array('controller' => 'static_pages', 'action' => 'index'),
  array(
    'pass' => array('slug'),
    'slug' => implode($slugs, '|')
));
?>

Now that everything is set up, we can start creating some static pages in the database. The key thing to remember is that the slug should be the full path to the page. So, for example, if we create a page called “about”, the slug should simply be “about”. The page will then be accessible at yourdomain.com/about. If we want to create a subpage of that page called “projects”, the slug for that page should be “about/projects”. Some people may not like storing the full path as the slug, but I find that it has two main advantages: it prevents ambiguity among pages with the same name/slug, and it makes managing your pages easier since you can immediately know the location of any given page.

This is also the reason that we load the slugs from the database in decending order for our regular expression: the route can first try to match the full URL of a subpage before the parent page is considered for matching.

If you want to take this one step further, you could write some sort of method, getFullSlug(), for the StaticPage model that generates the full slug (rather that storing it) by recursively appending the simple slug from parent pages. The obvious downside to this is that more SQL queries will be required, something we want to avoid, especially when dealing with what should be static content.

Happy baking!

Passing A Variable Number of Arguments to a Function at Run-time

At the request of an Ozzu member last night, I wrote up two new quick tutorials outlining how to write functions that can accept a variable-length argument list at run-time.  I wrote the original tutorial for C programmers and later adapted it for PHP.  I’ll likely adapt it for a few other languages in the near future.

TUTORIAL: Pass Variable Number of Arguments to a C Function
TUTORIAL: Pass Variable Num. of Arguments to a PHP Function

Feedback is always welcome either here or in the tutorial topics themselves.

A Calendar Element for CakePHP

Here’s a quick calendar element I whipped up for a CakePHP application I’m writing.  I needed to use the calendar in a number of different places, so creating a view element for it in Cake made the most sense.

<<< March 2009 >>>

Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

Download the source (zipped .ctp file) here: CakePHP Calendar Element

The code is pretty simple.  Pass the element a year and a month when rendering it and it will render that month in a simple table.  It also accepts two additional arguments, a day link and a month link.  The day link allows you to specify what the base URL for each day should be, and similarly, the month link specifies the base URL for traversing months.

Drop the calendar.ctp into your elements directory in your Cake app (usually /app/views/elements) and render it an any view you’d like.  Below is an example of rendering the element with all of its parameters:

<?php
$this->renderElement('calendar', array(
    'year' => 2009,
    'month' => '11',
    'month_link' => '/controller/showmonth/',
    'day_link' => '/controller/showday/'
));
?>

The month link and day link provide the base URLs for linking to additional months and days, allowing you to modify the element for particular controller actions.

Feel free to use this component for anything you’d like, no strings attached.  If you make any improvements or enhancements to it, be sure to share!