Dynamic select box with CakePHP 2.0

I wanted some auto populating Select boxes in a site I was creating so that when I changed a Category the Subcategories would automatically update. CakePHP can do this pretty easily but it is let down by the documentation as there are no examples.

Initially I came up with a version that wrote JSON into a JavaScript variable in the page and then used jQuery to achieve the updating of select elements but I wanted to do this the Cake way which uses AJAX and as little code as possible.

Here is a simplified solution to demonstrate how it can be done.

This assumes a new CakePHP site already configured with a database connection. I used CakePHP 2.0.3 but this may also work with 1.3 (?)

1. Models

We need 3 associated tables

CREATE TABLE `categories` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `categories` (`id`, `name`)
VALUES
	(1,'books'),
	(2,'music'),
	(3,'electronics');

CREATE TABLE `posts` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(50) DEFAULT NULL,
  `subcategory_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `posts` (`id`, `title`, `subcategory_id`)
VALUES
	(1,'The title',1),
	(2,'A title once again',4),
	(3,'Title strikes back',7);

CREATE TABLE `subcategories` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `category_id` int(10) unsigned DEFAULT NULL,
  `name` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `subcategories` (`id`, `category_id`, `name`)
VALUES
	(1,1,'fiction'),
	(2,1,'biography'),
	(3,1,'children'),
	(4,2,'classical'),
	(5,2,'rock'),
	(6,2,'jazz'),
	(7,3,'camera'),
	(8,3,'audio'),
	(9,3,'tv');

Bake the 3 Models for these and allow CakePHP to define model associations for you.

2. Controllers

Bake a Controller for the Post Model with the default CRUD actions.

While you are at it you will also need to bake the CRUD Views for the Post Controller.

If you browse to the posts index page now you can view the data.
In this example I will add the Category and Subcategory select lists to the Add view, so now we need to change this so that the selection in the second list changes according to the selection in the first list.
This will be done via the Js Helper so make it available at the top of the Posts controller (after the Class declaration) with:

public $helpers = array('Js');

You need a Subcategories Controller with a single action to provide the data via AJAX:

request->data['Post']['category_id'];
		
		$subcategories = $this->Subcategory->find('list', array(
			'conditions' => array('Subcategory.category_id' => $category_id),
			'recursive' => -1
			));
		
		$this->set('subcategories',$subcategories);
		$this->layout = 'ajax';
	}
}

3. Views

The view is a very simple AJAX view that renders the option tags that go within the select tag.


 $value): ?>


4. Putting it all together

In the Post Add view, file path: View/Posts/add.ctp we can finally add the Js methods that make the dynamic updating happen. This is the cryptic bit that I struggled with for a few hours as although the CakePHP documentation outlines all the options there are not any complete examples.
Firstly add a categories select element to the form (and change the order of the elements):

Form->input('category_id');
	echo $this->Form->input('subcategory_id');
	echo $this->Form->input('title');
?>

Add the following code to the bottom of the view, this uses the Js Helper to create the necessary jQuery to perform the updating:

Js->get('#PostCategoryId')->event('change', 
	$this->Js->request(array(
		'controller'=>'subcategories',
		'action'=>'getByCategory'
		), array(
		'update'=>'#PostSubcategoryId',
		'async' => true,
		'method' => 'post',
		'dataExpression'=>true,
		'data'=> $this->Js->serializeForm(array(
			'isForm' => true,
			'inline' => true
			))
		))
	);
?>

This is saying – watch the HTML element with Id PostCategoryId for a change. When it changes update the HTML element with Id PostSubcategoryId with the response from subcategories/GetByCategory, the data option is used to send the current value from the initial select element.

Before you leap to test this you need to make changes to the default layout to include jQuery and provide a place for your scripts to be written out.





	Html->charset(); ?>
	
		<?php echo $title_for_layout; ?>
	
	Html->meta('icon');

		echo $this->Html->css('cake.generic');
	?>


	
Session->flash(); ?>
element('sql_dump'); ?> Js, 'writeBuffer')) echo $this->Js->writeBuffer(); // Writes cached scripts ?>

Now you can test the application. On changing the category select list, the subcategory one will automatically update to show the relevant options.

CakePHP 2.0 AJAX sortable list that updates to databases

It is easy to create an AJAX sortable list with CakePHP 2.0, using jQuery that automatically updates the database, however it is hard to find documentation explaining how to do it.

Here is my explanation – it assumes a basic knowledge of CakePHP.

Step 1: Data

Lets use a simple categories table which includes a column for sort_order. This will be used to define the order the records are displayed on the page.

CREATE TABLE `categories` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(60) DEFAULT NULL,
  `sort_order` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
);

Step 2: CakePHP

Bake a model, controller and views for this table so that we have some files to work with.
You should be able to go onto your website at http://yoururl/categories and see the default index view.

Layout

In order to use the sortable feature, you will need to edit the default layout. If you don’t already have your own layout you can copy the CakePHP default one (which is used if your own one isn’t found) from /lib/Cake/View/Layouts/default.ctp and place it in app/View/Layouts/default.ctp.
Edit this so that the writeBuffer method is one of the final lines, just before the close body tag:

Js->writeBuffer();?>


This will create the HTML and JavaScript generated by the Js helper in the view.

Controller

At the top of the CategoriesController make the Js helper available to the controller.

public $helpers = array('Js');

I am going to add 2 actions, one to display the sorted categories and one to handle the AJAX requests to update the database.
The first action is called sort, all this does is retrieve all the categories sorted by the sort_order column and send them off to the view.

public function sort() {
	$this->set('categories', $this->Category->find('all',array(
		'order' => 'sort_order ASC',
		)
	));
}

The second action is called reorder.
This will receive the AJAX post and update the categories table. I’ve commented out a line that can be used for debugging. If uncommented this will write to app/tmp/logs/error.log and is a really good way to see what is happening when you are dealing with AJAX requests.

public function reorder() {
	foreach ($this->data['Category'] as $key => $value) {
		$this->Category->id = $value;
		$this->Category->saveField("sort_order",$key + 1);
	}
	//$this->log(print_r($this->data,true));
	exit();
}

The code here is just looping through the posted data which will look like this:

Array
(
    [Category] => Array
        (
            [0] => 4
            [1] => 5
            [2] => 3
            [3] => 6
            [4] => 2
            [5] => 1
        )
)

The index is the order of elements after they have been sorted and the value is the id of each element. When saving the data I am adding 1 to the index as the one coming from jQuery is zero based.

View

For the sake of simplicity I’ve included the links to the jQuery and jQuery UI libraries here but in practice you would probably want to put them in the head of your layout.
In the view I have a simple unordered list tag containing the data.
The id of each element is important as this is used by jQuery UI to format the data sent back to the server.



  • '>
Js->get('#my-list'); $this->Js->sortable(array( 'complete' => '$.post("/categories/reorder", $("#my-list").sortable("serialize"))', )); ?>

And that is all there is to it.
You can simply drag and drop any item in the list and behind the scenes the database will be updated with the new sort order.

Blog Tutorial with CakePHP Framework

In a follow up to my previous article: blog tutorial with Fat-Free Framework I have tried to make exactly the same application but using the CakePHP Framework – to contrast using a micro-framework with a fully featured one.
CakePHP works with PHP 4 or 5 so this should work on most servers.

Step 1: Setup

Download CakePHP from the website. This tutorial uses version 1.3.11.
It runs happily from its own folder in a web site (so you don’t need to set up a separate virtual site) – I just used a folder called cake1, with the contents looking like this
CakePHP root directory

Step 2: Bootstrapping

We’ll use this section to configure the database connection.
Copy /app/config/database.php.default to database.php in the same folder.
Edit the values in the $default array to match your database.
You should also edit /app/config/core.php and change the values for Security.salt and Security.cipherSeed.
Make the app/tmp folder writable for the webserver by chown -R www-data app/tmp.
As we’re developing it may be worth disabling caching so in /app/config/core.php so uncomment

Configure::write('Cache.disable', true);

Step 3: Routing

With CakePHP a lot of routing happens automagically by putting correctly names files in the right places.
However we need to tell it a route for the homepage of the site.
Edit /app/config/routes.php and comment out all the default route(s), replacing with one to the articles controller.

//Router::connect('/', array('controller' => 'pages', 'action' => 'index'));
Router::connect('/', array('controller' => 'articles', 'action' => 'index'));

Step 4: Models

CakePHP needs the database to follow a naming convention. The table names should be plural so we’ll use articles and users.
CakePHP will dynamically creates model objects for you if it cannot find corresponding files in /app/models, as this is a tutorial about getting a prototype running quickly we’ll take advantage of this here.
Here’s the SQL that will set you up with the 2 tables necessary for this tutorial, it is exactly the same as for my Fat-Free Framework tutorial:

CREATE DATABASE `blog` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
 
USE `blog`;
 
CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `timestamp` datetime NOT NULL,
  `title` varchar(128) NOT NULL,
  `summary` varchar(128) NOT NULL,
  `content` text NOT NULL,
  `author` varchar(128) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
INSERT INTO `articles` (`id`, `timestamp`, `title`, `summary`, `content`, `author`) VALUES
(1, '2011-07-28 02:03:14', 'Hello World!', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut ', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 'Mr White'),
(2, '2011-07-28 02:03:14', 'More Hello World!', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut ', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 'Mr Green');

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `users` (`id`, `name`, `password`) VALUES
  ('1', 'admin', 'password');

Step 5: Application Front End

First create a layout that will be used as a template for the individual views /app/views/layouts/default.ctp:



  
  <?php echo $title_for_layout?>


  


Doing this will override the default CakePHP layout which means you will loose the nice diagnostic section that tells you what queries are being sent to the database. You can get this back whenever you want by temporarily renaming the layout default.ctp to something like default.html

Now the layout is in place, create a file called articles_controller.php inside /app/controllers with the code below. The index function provides the logic for the homepage and tells it to retrieve all articles and the set command passes the data to the view.

set('articles', $this->Article->find('all'));
	}
}
?>

Add in another function to deal with the detail view which gets all the data for an individual record.

function view($id = null) {
	$this->Article->id = $id;
	$this->set('article', $this->Article->read());
}

Now the logic is in place lets deal with the views. Create a file for the home page /app/views/articles/index.ctp.

Blog Titles

Html->link($article['Article']['title'], array('controller' => 'articles', 'action' => 'view', $article['Article']['id'])); ?> by

This is more complicated than the Fat-Free Framework version and begins to show where using a micro framework may save you a lot of typing.
The file for the details view should be saved as /app/views/articles/view.ctp.


Published: by

Back to Homepage

You can now check this all works by visiting /articles/index with a browser.

Step 6: Application Back End

To mimic the previous tutorial I did, the whole admin section needs to accessed under a /admin URL.
This is built into CakePHP and you can achieve it by editing /app/config/core.php and uncommenting

Configure::write('Routing.prefixes', array('admin'));

You can now add functions like admin_index() to a controller and these are only available when using an admin prefix in the URL like /admin/articles.
The admin home page is very similar to the normal home page with some extra links to add/edit/delete blog articles, so the controller method looks like this:

function admin_index() {
	$this->set('articles', $this->Article->find('all'));
}

And the view, saved as /app/views/articles/admin_index.ctp looks like this:

My Blog Administration

Html->link('Add Article', array('controller' => 'articles', 'action' => 'admin_add')); ?>

Title Date Author Actions
Html->link('Edit', array('controller' => 'articles', 'action' => 'admin_edit', $article['Article']['id'])); ?> Html->link('Delete', array('controller' => 'articles', 'action' => 'admin_delete', $article['Article']['id'])); ?>

You can now access this via /admin/articles/, we’ll add the authentication in the next step.

CakePHP uses something called the Flash to communicate temporary messages to the user (don’t confuse with Adobe Flash).
The following code in a view will display the message if present:

Session->flash(); ?>

We will add this to the admin_index view under the H1 tag so that messages from the add/edit/delete actions can be displayed.
Also the following code is required to make the component available in the controller:

var $components = array('Session');

Note that built into CakePHP is a mechanism for confirming the delete of these records:

Html->link('Delete', array('action' => 'admin_delete', $article['Article']['id']), null, sprintf('Are you sure you want to delete # %s?', $article['Article']['id'])); ?>

This is starting to get complicated by the number of parameters you have to pass this method. I you use this every day then maybe it will become second nature, however for a beginner it can be a little daunting.

Add/Edit/Delete
To keep things simple we will create separate actions and views for the add/edit functionality – even though there is a lot of similarity between the two.

function admin_add() {
	if (!empty($this->data)) {
		if ($this->Article->save($this->data)) {
			$this->Session->setFlash('Your article has been saved.');
			$this->redirect(array('action' => 'index'));
		}
	}	
}

function admin_edit($id = null) {
	$this->Article->id = $id;
	if (empty($this->data)) {
		$this->data = $this->Article->read();
	} else {
		if ($this->Article->save($this->data)) {
			$this->Session->setFlash('Your article has been saved.');
			$this->redirect(array('action' => 'index'));
		}
	}
}

function admin_delete($id) {
	if ($this->Article->delete($id)) {
		$this->Session->setFlash('The article has been deleted.');
		$this->redirect(array('action' => 'index'));
	}
}

Delete doesn’t require a view as the action is confirmed by use of the flash. Edit is nearly the same as Add except for a few extra lines to retrieve the record first.
CakePHP uses a method called ‘save’ to save changes to the database, but behind the scenes this is going to be an insert or update. If Cake gets passed an id field via the submitted form it assumes you are going to be updating a record.

Thoughts at this stage – can clearly see the difference between a micro framework and a full size one now, Cake is more complex, there’s more to learn (much more documentation) and it takes longer.
Now the views of Add and Edit, these are nearly identical except that Edit includes the id field in a hidden variable.
views/articles/admin_add.ctp

Add

Form->create('Article');?> Form->input('title'); echo $this->Form->input('summary'); echo $this->Form->input('content'); echo $this->Form->input('author'); ?> Form->end('Submit');?>

views/articles/admin_edit.ctp

Edit

Form->create('Article');?> Form->input('title'); echo $this->Form->input('summary'); echo $this->Form->input('content'); echo $this->Form->input('author'); echo $this->Form->input('id', array('type' => 'hidden')); ?> Form->end('Submit');?>

You can now test this in a browser and should be able to add/edit and delete records.

Step 7: Using Middleware

To implement authentication from a database table we use CakePHPs Authentication Component.
This gets messy if you want to do it differently to the CakePHP way – that means we need to create a new database table with different column names and use that instead.

DROP TABLE users;
CREATE TABLE users (
    id integer auto_increment,
    username char(50),
    password char(40),
    PRIMARY KEY (id)
);

This requires a users controller called users_controller.php

redirect($this->Auth->logout());
    }
}

And the following 2 changes to articles_controller.php, update the components variable to include the Auth component and add a new function.

var $components = array('Session','Auth');  //changed to add Auth

function beforeFilter() {
	$this->Auth->allow('index', 'view');
	$this->Auth->loginAction = array('admin' => false, 'controller' => 'users', 'action' => 'login');
}

The beforeFilter function changes some of the defaults for the Auth component, the first line allows the index and view actions to be executed without requiring authentication. Without the second line CakePHP will look for an action called admin_login so this tells it to simply use the login action.
Now we need a view to contain a login form.
views/users/login.ctp

Session->flash('auth');
    echo $this->Form->create('User');
    echo $this->Form->input('username');
    echo $this->Form->input('password');
    echo $this->Form->end('Login');
?>

As the passwords are stored hashed in the database (which is a good idea) we have a minor annoyance here – we can’t insert a row for the admin user into the database manually as we don’t know what the password field needs to hold.
We can get around this by returning to the CakePHP default layout which contains useful debug information and see what SQL it is using.
Rename app/views/layouts/default.ctp to default.htm
Visit /admin/articles and input your chosen username and password (I used ‘admin’ and ‘password’ for this tutorial) then click submit, in the diagnostic queries section you will see what SQL it tried to use, copy the hashed version of the password and manually insert this into the users database table with your username.
CakePHP default template showing SQL
Now you can rename default.htm back to default.ctp and get your own layout back.

Thoughts – if you know what you are doing it can be set up with very little coding and it works well.
However it took me about 45 mins to figure it out from the documentation on their website.

Step 8: Summary

I guess it should have been obvious from the start but using a bigger framework will lead to more complexity and a steeper learning curve. In exchange for that you gain greater flexibility which aren’t being taken advantage of with such a simple system developed here. A some really important ones for me are the form helper, validation, built in pagination (with sorting) and custom DataSources. You may be interested in my article on creating a DataSource to read from a RESTful API which lets you page through and sort the data just as if it were from MySQL.

Rather than putting the whole framework into a zip file, I’ve just included the files that I created with this tutorial, they all exist within the app folder of the CakePHP installation.

controllers
	articles_controller.php
	users_controller.php
views
	articles
		admin_add.ctp
		admin_edit.ctp
		admin_index.ctp
		index.ctp
		view.ctp
	layouts
		default.ctp
	users
		login.ctp

Download CakePHP app folder.

I’m going to try this very same blog with CodeIgniter next.

Blog Tutorial with Fat-Free Framework

View the blog tutorial updated for version 3 here

I recently read an excellent tutorial on ‘Rapid Application Prototyping in PHP Using a Micro Framework‘, this used the Slim micro framework but one thing that bothered me was it required 5 packages before development could start.
These were: Slim (the micro framework), Slim Extras (additional resources for Slim), Twig (template engine), Idiorm (object-relational mapper) and Paris (Active Record implementation).
It struck me that if you need an extra 4 packages alongside Slim for a basic blog then maybe it is a little too skinny and I set out to find a micro framework that could do the same thing without these dependencies.

I found the Fat-Free Framework. It is condensed into a single 55KB file and has a host of features, you can find out about these on their web site so I won’t repeat it here. Instead I’ve reproduced the Slim tutorial to create a simple blog site, but using Fat-Free Framework instead.
You will need PHP 5.3 on your server, I used Ubuntu 11.04 for the tutorial as that version of PHP is easily installed. If you’re on RHEL or Centos then I’d suggest taking a look at the IUS Community Project for getting a recent PHP version.

Step 1: Setup

Download Fat-Free Framework (F3) from the website.
F3 works just as happily in the site root as from a subdirectory – I’ll assume you’re using a subdirectory here as it means you won’t have to set up a seperate site to do this tutorial.
Create a folder called blog and unzip the contents of the F3 download into that. It should look like this:
Move up one level in the directory heirarchy and set the permissions:

sudo chgrp -R www-data blog
sudo chmod -R 775 blog

The folder contents should look like this:
Folder Contents

Notice how much simpler the starting site is compared with Slim + extra packages.

If using Apache, mod_rewrite will need to be running. Edit .htaccess and adjust RewriteBase to add the blog subfolder that you are using, so it looks like RewriteBase /blog.

You can browse this site right away and get the F3 start page.
start page
(Once the site has been visited, F3 will create additional folders cache and temp – you don’t need to worry about these).

Step 2: Bootstrapping

As everything we need is already part of F3 you don’t need to do anything here!

You can however tidy up the site to remove the current home page and add a database connection.
Edit index.php
comment out the CACHE option and increase the DEBUG level to help while developing, then remove the only route command. It should look like this:


To setup a database connection add the following between the set and run commands:

F3::set('DB',
	new DB(
		'mysql:host=localhost;port=3306;dbname=YourDatabaseName',
		'YourUsername',
		'YourPassword'
	)
);

All the User Interface files will go in the ui directory, you can delete welcome.htm and style.css from here as they were just used by the default home page.

Step 3: Routing

Similar to Slim you need to tell F3 the route request method (GET, POST, PUT etc), the URI to respond to and how to respond.
Home page route:

F3::route('GET /',
	function () {
	//do something
	}
);

The anonymous function will contain the logic to populate the page.
To view a blog article:

F3::route('GET /view/@id',
	function () {
		$id = F3::get('PARAMS["id"]');
	}
);

This tells F3 to expect a URI parameter and assigns it to a PHP variable in the anonymous function.

Now the administration routes:

// Admin Home
F3::route('GET /admin',
	function () {	
	}
);

//Admin Add
F3::route('GET /admin/add',
	function() {
	}
);

//Admin Edit 
F3::route('GET /admin/edit/@id',
	function() {
		$id = F3::get('PARAMS["id"]');
	}
);

//Admin Add & Edit, Deal with Form Posts
F3::route('POST /admin/edit/@id','edit');
F3::route('POST /admin/add','edit');
	function edit() {

	}

//Admin Delete
F3::route('GET /admin/delete/@id',
	function() {
		$id = F3::get('PARAMS["id"]');
	}
);

Notice that we’re using the same function to process Add and Edit form posts so it isn’t anonymous but the function name is passed as a parameter to the route command.

Step 4: Models

The ORMs in Fat-Free Framework do all the hard work for you – no directories, files or code required here.
Are you beginning to see how much simpler this is compared with Slim?

Here’s the SQL that will set you up with the 2 tables necessary for this tutorial:

CREATE DATABASE `blog` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
 
USE `blog`;
 
CREATE TABLE IF NOT EXISTS `article` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `timestamp` datetime NOT NULL,
  `title` varchar(128) NOT NULL,
  `summary` varchar(128) NOT NULL,
  `content` text NOT NULL,
  `author` varchar(128) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
INSERT INTO `article` (`id`, `timestamp`, `title`, `summary`, `content`, `author`) VALUES
(1, '2011-07-28 02:03:14', 'Hello World!', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut ', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 'Mr White'),
(2, '2011-07-28 02:03:14', 'More Hello World!', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut ', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 'Mr Green');

CREATE TABLE IF NOT EXISTS `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `user` (`id`, `name`, `password`) VALUES
  ('1', 'admin', 'password');

Step 5: Application Front End

Like this Slim tutorial we’re going to keep this simple.
Instantiate an Axon object that interacts with the users table, call the afind method to return a simple array of results, finally the set command is used which will pass the variable between MVC components. F3 calls this a framework variable.

	$article=new Axon('article');
	$articles=$article->afind();
	F3::set('articles',$articles);

You could condense the final two lines together like F3::set('articles',$article->afind()); but I’m keeping this verbose to aid readability and help you figure out how it is all working.

To use templating you need a base layout file in the ui folder called layout.html



   
      {{@html_title}}
      
   
   
      
   

The F3 template engine uses {{@name}} to write out the value of a framework variable.
The F3:include directive will embed the contents of a file at the position where the directive is stated – in the example above we’re using a framework variable as the URL so that the content is dynamic.

Now lets create the first of those content files with the view for the homepage, called blog_home.html

Blog Titles

{{trim(@item['title'])}} by {{@item['author']}}

{{@item['summary']}}

The F3:repeat directive will loop through an array, setting item to the current array element. Within the loop item contains the array of data retrived from the database table and this is accessed using the column name as the array key.

Now that the view is in place we can complete the code in index.php to display it. Set the framework variable to tell the template which view to include, then tell F3 to serve the template.

	F3::set('content','blog_home.html');
	echo Template::serve('layout.html');

Serving the template also converts it to PHP code the first time it’s used and this optimised version is used thereafter which is great for performance.
The full code for this is:

F3::route('GET /',
	function () {
		F3::set('html_title','Home Page');
		$article=new Axon('article');
		F3::set('list',$article->afind());
		F3::set('content','blog_home.html');
		echo Template::serve('layout.html');	
	}
);

Now for the detail view, in index.php we need to get an Axon object, search for the id then send the data to the view/template. In this case we could have assigned each value individually e.g. F3::set('title',$article->title); but it is quicker to put all the values in the POST framework variable with the copyTo command.

F3::route('GET /view/@id',
	function () {
		$id = F3::get('PARAMS["id"]');
		//create Axon object and search for id
		$article=new Axon('article');
		$article->load("id='$id'");
		//set framework variables
		F3::set('html_title',$article->title);
		$article->copyTo('POST');
		//serve up the view
		F3::set('content','blog_detail.html');
		echo Template::serve('layout.html');
	}
);

And the view file itself called blog_detail.html accesses the individual data items from the POST framework variable:

{{@POST.title}}

Published: {{@POST.timestamp}} by {{@POST.author}}

{{@POST.content}}

Back to Homepage

Step 6: Application Back End

The admin home page just needs to list the blog articles and provide links, the code is similar to that for the homepage.

F3::route('GET /admin',
	function () {
		F3::set('html_title','My Blog Administration');
		$article=new Axon('article');
		$list=$article->afind();
		F3::set('list',$list);
		F3::set('content','admin_home.html');
		echo Template::serve('layout.html');	
	}
);

The view called admin_home.html and contains a table of the results. It looks like this:

My Blog Administration

Add Article

Title Date Author Actions
{{@item['title']}} {{@item['timestamp']}} {{@item['author']}} Edit Delete

The output of this look like:
Admin Home Page
Now a form to add and edit articles, called admin_edit.html

Edit

{{ @message }}









I’ve kept this basic, apologies for the lack of styling but that’s not what this tutorial is about.
Note that there’s an area to display validation messages.

Now for the logic with the routes, add & edit both use the same view remember.

F3::route('GET /admin/add',
	function() {
		F3::set('html_title','My Blog Create');
		F3::set('content','admin_edit.html');
		echo Template::serve('layout.html');
	}
);

F3::route('GET /admin/edit/@id',
	function() {
		F3::set('html_title','My Blog Edit');
		$id = F3::get('PARAMS["id"]');
		$article=new Axon('article');
		$article->load("id='$id'");
		$article->copyTo('POST');
		F3::set('content','admin_edit.html');
		echo Template::serve('layout.html');
	}
);

Now we assigned a function for the POST routes and here’s the content:

	function edit() {
		// Reset previous error message, if any
		F3::clear('message');
		$id = F3::get('PARAMS["id"]');
		$article=new Axon('article');
		//load in the article, set new values then save
		//if we don't load it first Axon will do an insert instead of update when we use save command
		if ($id) $article->load("id='$id'");
		//overwrite with values just submitted
		$article->copyFrom('POST');
		//create a timestamp in MySQL format
		$article->timestamp=date("Y-m-d H:i:s");
		$article->save();
		// Return to admin home page, new blog entry should now be there
		F3::reroute('/admin');
	}

There’s a lot less coding required here than with Slim + extras.

Step 7: Using Middleware

This isn’t relevant to using the Fat-Free Framework.
Authentication using a database is built in so I’ll step through how to use it.
The following lines are added to the admin homepage route

//tell F3 the database table and fields for user id and password
F3::set('AUTH',array('table'=>'user','id'=>'name','pw'=>'password'));
//carry out authentication
$auth = Auth::basic('sql');
//if successful, set a framework variable
if ($auth) {
  //set the session so user stays logged in
  F3::set('SESSION.user',$auth->name);
  //display the admin view
  F3::set('content','admin_home.html');
} else {
  //login unsuccessful so display message
	F3::set('content','security.html');
}

security.html looks like this:

You must supply valid login details.

In the add & edit routes, add this line before Template::serve

if (!F3::get('SESSION.user')) F3::set('content','security.html');

That’s it (if you didn’t spot it in the SQL, my example uses admin & password for the login credentials).
I did get a bug with version 2.0.5, auth.php line 230 if you get this then that line should read self::HTTP_WebAuth instead of HTTP_WebAuth on its own.

You could instead choose to redirect back to the homepage with:

if (!F3::get('SESSION.user')) F3::reroute('/');

This would also work in the start of delete and POST routes and may make the application more secure, security.html wouldn’t be required.

Step 8: Summary

So here is another example of how to quickly get a prototype running using a PHP micro framework.
Is it any faster than with Slim? I’d like to think that it is, there’s less code, less complexity and you aren’t dependent on other packages which may end up changing in some way or not being maintained.
I’ve used F3 to create a times tables application for my son and found it was fun to build and made me more productive – I’ve now put it online at times-tables.willis-owen.co.uk for anyone to use.

I should add that Licensing of Fat-Free Framework means for academic and personal use it uses the GNU General Public License and is free, however for business or commercial gain you need to contact the developers.
Slim is released under the MIT Public License and you are free to use, modify and even sell it.

You can download the files used from here blog.zip.

I’ve done another tutorial on creating a near identical blog application to this but using CakePHP: Blog Tutorial with CakePHP Framework which may be interesting to compare with differences with using a micro-framework.

PHP Frameworks

Here’s my very basic research into current PHP Frameworks – the target was to get this done in an hour.

Firstly if they have a professional design I’m assuming there’s at least a bit of funding behind the project.
Secondly I do a simple search for AJAX in their documentation. If they don’t mention how to use the framework with AJAX then I’m not interested.

Name PHP Version Professional Design Search AJAX in Help Worth further investigation
Kohana
(CodeIgniter clone)
5 Yes No No – more CodeIgniter jobs about
Yii 5 Yes Yes Yes
Lithium
(CakePHP clone)
5.3 Yes No No – more CakePHP jobs about
Akelos 4 Yes No  
PHP on Trax
(based on Ruby on Rails)
? Not really No  
Fuel 5.3 Yes No  
Agile Toolkit 5.3 Yes Yes  
CodeIgniter 5.1.6 Yes No Yes
CakePHP (V2) 5.2.9 Yes No Yes
Solar 5.2 Yes No  
Rain 5 Yes A little  
Recess 5.2.4 Yes No  

PHP Micro frameworks:

  • DooPHP
  • Fat-Free Framework
  • Limondae
  • Slim

I looked in detail at Agile toolkit but ultimately found their documentation too limited.
Recess looked good because of its focus on RESTful API which is built in – however I’m also keen on built in validation which this doesn’t have yet.

In summary for me it boils down to 2 factors, is it popular framework where jobs are advertised as requiring that knowledge and/or is it going to give a big advantage in developing applications rapidly. I’m going to focus my efforts on CakePHP (I’ve already built an app using 1.2.6 and version 2 will be launched very soon), CodeIgniter, Yii and also Fat-Free Framework.