CakePHP Tutorial: Build a bookmark site

Code

If you've followed our last few tutorials, you'll be a CakePHP expert by now: you know how to navigate controllers, delve into the depths of models and create views that astound your viewers. But having a taste of the sweet rapid development that CakePHP offers you, you want more, and you want to do more in less time. Fair enough too, so in our last project for this series, we're going to take a look at using CakePHP plugins to extend the functionality of our app. This is the result of the DRY principle (Don't repeat yourself). Solve a problem once, package it in a reusable manner, and then release it to the world for everyone to contribute to and benefit from your solution. While you're at it, grab some code that other people have already written, and include those plugins to save you huge amounts of time.

We're jumping in the deep end, and build a bookmarking site that will not only store URLs and associated information for us, but generate some pretty sweet thumbnails and store those for use too. Because we're a social community these days, we'll include some nifty tools from a useful plugin to enable quick and easy social network sharing and publishing to get the word out about a URL that we've published.

Using the Authentication component automatically encrypts passwords

Using the Authentication component automatically encrypts passwords

As always, we put in some time ensuring that our database fits the CakePHP conventions and standards so that the heavy lifting later on is handled by CakePHP. Don't stress though, if you want to deviate from the standards or structure here, it's extremely easy to customise, depending on what you're changing. Check out http://book.cakephp.org/view/901/CakePHP-Conventions for a list of the conventions adopted by the core, and for information on what you need to do to break free and go your own way.

Take a moment to consider the data we need to store. We'll need: URLs, users and ratings, so that when we share the bookmarked URLs via Twitter, Facebook or whatever our preferred social network giant is, people can rate the content if they like it.

CREATE DATABASE `bookmark`;
USE `bookmark`;
CREATE TABLE `urls` (
	`id` CHAR(36) NOT NULL PRIMARY KEY,
	`user_id` CHAR(36) NOT NULL,
	`name` VARCHAR(128) NOT NULL,
	`url` TEXT,
	`created` DATETIME,
	`modified` DATETIME
);
CREATE TABLE `users` (
	`id` CHAR(36) NOT NULL PRIMARY KEY,
	`username` VARCHAR(45) NOT NULL,
	`password` VARCHAR(255) NOT NULL,
	`email` VARCHAR(255) NOT NULL,
	`created` DATETIME,
	`modified` DATETIME
);

Hang on a moment… surely that can't be all the tables we need? That list of information we wanted to include for each bookmark was pretty long. And it doesn't look like there are enough fields in that table to hold all the data - except that each plugin is capable of distributing database information that it needs in a segregated way to ensure that you have all the functionality you need packaged in the plugin that provides it, including the database storage requirements.

Okay, but how does it work? Its done by using either the 'Schema' shell, which comes with CakePHP, or by using the 'Migrations' plugin, which is developed by the Cake Development Corporation. We'll run through the migrations plugin, because it provides much more functionality, while still remaining easy to use.

Time to hit the oven, baking time!

Bake is a console command for CakePHP, and it enables you to generate code, run migrations, run tests and much more. In order for this to work effectively from your console, the cake/console path within the CakePHP package needs to be in your PATH environment variable. You can achieve this with the following in your shell:

$ export PATH="$PATH:/path/to/cakephp/cake/console"

The only other requirement is that you have PHP available on your command line. Most distros have a php-cli package or similar to provide a command line interface PHP binary.

Bake a new project to create the skeleton structure we need to get moving. Run the following in your shell:

$ cake bake project bookmark
$ cd bookmark

CakePHP has created a basic app structure for you to use. This includes the directories and some basic initial files for you to use to serve static pages. Feel free to dig around and see exactly what has been produced for you. It's a good idea to get familiar with the file structure that CakePHP adopts.

Before we bake, let's tell CakePHP how to connect to our database. Take a copy of the /config/database.php.default file and rename it to /config/database.php. Enter in your database configuration to match the user details for your local database install. Here's what my config shows:

<?php
class DATABASE_CONFIG {
	var $default = array(
		'driver' => 'mysql',
		'persistent' => false,
		'host' => 'localhost',
		'login' => 'dev',
		'password' => 'dev',
		'database' => 'bookmark',
		'prefix' => '',
	);
}

You'll note that I have removed the test database configuration from the file. The test configuration is used for unit testing, and in this tutorial we won't be touching on it.

We can already add a new URL to our bookmarking service

We can already add a new URL to our bookmarking service

Let's get cracking on the baking of models, views and controllers for the database tables we created earlier. Baking will generate all the code we need to get the common functionality working, and will enable us to customise from there, saving a whole lotta time.

$ cake bake all user
$ cake bake all url

Don't be alarmed by the amount of output text from the bake command; the more it outputs, the more time it has saved you through generating code. If you take a look at the output, you'll see file paths and names to all the files that have been created for you. You now have a user and URL management system, and can browse through to add and edit that information. Try browsing to /users on your site. For example, I baked into my webroot, and so the URL I use is: http://localhost/bookmark/users. You'll be greeted with the users index page, and from here you can navigate around to add and edit users.

Enabling authentication

We'll quickly run through locking the system down so we can create users and log in, but not access anything else until we are logged in. This is a great way to secure your system, and its dead easy to do!

Open up the app_controller.php file in the root of your project. This is an empty controller from which all the other controllers in the application inherit functionality. Anything we implement here will be used in all our controllers, so this is the perfect place to enforce user authentication, and keep out nasty guests from editing data. Add the “Auth" and “Session" components. Your AppController will now look like this:

<?php
class AppController extends Controller {
	var $components = array('Auth', 'Session');
	function beforeFilter() {
		$this->set('authUser', $this->Auth->user());
		return parent::beforeFilter();
	}
}

You'll note we snuck in a set call in the function beforeFilter there. This will ensure that we have access to the current user information in the views throughout the site. If there is no user currently logged in, this will be set to null.

Next, open up /controllers/users_controller.php and add a login action. This can be empty, as CakePHP handles the tricky stuff behind the scenes. While we're digging around, also add the logout function for logging out users:

function login() {
}

function logout() {
	return $this->redirect($this->Auth->logout());
}

Finally, on the controller side, we want to ensure that we don't have to log in to create a user, otherwise we'd be stuck with no users, and no way to create one! Add the following to your users controller:

function beforeFilter() {
	$this->Auth->allow('add');
	return parent::beforeFilter();
}

The final piece of this puzzle is the view file, which isn't created with the CakePHP bake tool. This will take the username and password and enable users to log in. Create the login view in a new file: /views/users/login.ctp:

<div class="users form">
<?php echo $this->Form->create('User', array('url' => array('action' => 'login')));?>
	<fieldset>
 		<legend><?php __('Login'); ?></legend>
	<?php
	echo $this->Form->input('username');
	echo $this->Form->input('password');
	?>
	</fieldset>
<?php echo $this->Form->end(__('Login', true));?>
</div>

Browse back to your users list (http://localhost/bookmark/users) and you'll immediately be redirected to the user login form. Now, try visiting the user addition page (http://localhost/bookmark/users/add) and you'll note that you are allowed to view this page, and thus not redirected away to log in. Brilliant! It couldn't get any easier!

Enabling users to share your site's content will increase your traffic and popularity.

Enabling users to share your site's content will increase your traffic and popularity.

Time for a fresh coffee and a pat on the back. Your data is secure, and your site works. Take a spin around the app and create a new user. Log in and test that it works correctly, and add a URL or two. Once you are happy with how that is operating and you are familiar with the site, we'll move on to including plugins, and getting the cool stuff working!

Back in school they'd kick you out for it.. But here you're welcome to grab people's code and use it in your project to enhance functionality, or just save time. Just remember to check the licensing of the plugins you choose to use, as they may not be compatible with the project you are building.

How to cheat

Let's go hunting for plugins, grab what we need and set up the directories in /app/plugins. For the purposes of this plugin installation, I'm going to assume that you have Git installed. Using Git will speed up the download and organisation of our application plugins for usage. And while you're at it, its a great way to get used to using Git for version control if you're not already familiar with it.

Run the following Git commands in your console:

git clone https://github.com/CakeDC/migrations.git plugins/migrations
git clone https://github.com/CakeDC/ratings.git plugins/ratings
git clone https://github.com/CakeDC/search.git plugins/search
git clone https://github.com/predominant/cake_social.git plugins/cake_social
git clone https://github.com/predominant/goodies.git plugins/goodies

You've just saved yourself months of development time. No kidding. You're utilising a bunch of open source plugins that are available to any CakePHP developer who wants to use it, to cover the common functionality in your application. This saves time and money and lets you get on to leisure time quicker than the rest of the developers. Bonus!

Re-using plugins

If you are using a central installation of CakePHP and it's on your php.ini include_path (See http://book.cakephp.org/view/1645/Deployment for more information on deployment and development setups and the best approaches), then you can add your plugins to the /cake/plugins directory, and each of the plugins immediately becomes available to any CakePHP application using that core. This is a super awesome way to ensure your plugins are up to date, and saves time from checking out many copies or copying files all over the place.

Running migrations

Earlier I mentioned that we had a lack of database tables, but that would be taken care of by database migrations. That time has come, and you'll see how simple database table creation can be for plugins and code reuse through projects. Not all plugins need or have migrations. Those that do will have a migrations directory in their config directory. For example, you will see the directory /plugins/ratings/config/migrations. You can check through to see what needs migrations, or just run them all, and you'll get an error message for those that didn't actually have migrations available, which is fine.

Run the following to complete the table setup for the plugins we've introduced:

$ cake migration -plugin ratings all

You should now have all the tables you need for the site!

Let's start by enabling ratings. We want those accessible for URLs only. Adding the component is really easy. Open up the UrlsController in /controllers/urls_controller.php and add the Ratings component. Your resulting code will now look like the following:

class UrlsController extends AppController {
	var $components = array('Ratings.Ratings');
		// ... existing code ...
}

What's super cool about this is that the component we've just added takes care of including the helper that you need to display the ratings form, as well as handles the saving and loading of ratings information for you. So all you need to do is set up your view as you like it to show the form for ratings, through the ratings helper, and the rating itself. Now being a truly portable and flexible plugin, the ratings information isn't averaged or calculated for you on load, since that would be making the assumption that we know what maths you need applied for your specific situation. So some effort is required to get the right value showing, and to get the form showing only when users have not yet rated it.

The next section of code is pretty big;

<?php
$rated = Set::extract('/Rating[user_id=' . $authUser['User']['id'] . ']', $url);
if (empty($rated)): ?>
	<dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Rate'); ?></dt>
	<dd<?php if ($i++ % 2 == 0) echo $class;?>>
	<?php echo $this->Rating->display(array(
	    'item' => $url['Url']['id'],
	    'type' => 'radio',
	    'stars' => 5,
	    'createForm' => array(
			'url' => array_merge(
				$this->passedArgs,
				array(
					'rate' => $url['Url']['id'],
					'redirect' => true))
			)
	)); ?>
	 
	</dd>
<?php else: ?>
	<dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Rating'); ?></dt>
	<dd<?php if ($i++ % 2 == 0) echo $class;?>>
		<?php
		$ratings = Set::extract('/Rating/value', $url);
		echo array_sum($ratings) / count($ratings); ?>
		 
	</dd>
<?php endif; ?>

There are two main sections to the code: the first shows the form if you have not already rated the current bookmark, and the second displays our rating. Both sections of code use the Set class, which makes it easy to handle larger collections of data, and enables filtering. Add the code just after the dl tag at the start of your URLs view in /views/urls/view.ctp.

Browsing to a URL now shows a set of radio buttons, and a Rate Submission button. Sweet! Give it a go, rate a URL - you'll be redirected to the same page, and the resulting rating will be displayed for you. Feel free to test this out some more by using another user, and see how the resulting rating changes to be the average of all the rates submitted. Pretty darn cool!

Using gravatars

The URL view page is pretty bland. There, I said it! What we really should do is at least include some more imagery and items to represent the information on the page in a more rapid manner for visitors. We'll leave the complexities of CSS and styling the page to perfection to the web designers, but we will make their job easy through using gravatars to show user icons from the user information we have for each URL. A gravatar is a global avatar, and is provided for free through http://gravatar.com. We included a Goodies plugin when we cloned all the plugins, and the gravatar helper is just one of the awesome things that this plugin provides.

Enough sales talk.. Lets get some gravatars showing. If you have not done already, head over to the gravatar website, register your account for free, and set up a gravatar. Next, open up the URLs view (/views/urls/view.ctp). Scroll down to where the User information is output. The line we're going to replace is:

<?php echo $this->Html->link($url['User']['id'], array('controller' => 'users', 'action' => 'view', $url['User']['id'])); ?>

Replace this with a similar code link, but this time, include the output of the gravatar helper:

<?php echo $this->Html->link(
	$this->Gravatar->image($url['User']['email']),
	array('controller' => 'users', 'action' => 'view', $url['User']['id']),
	array('escape' => false)); ?>
<?php echo $url['User']['username']; ?>

Finally, include the helper in your AppController in /app_controller.php:

class AppController extends Controller {
	var $helpers = array('Html', 'Form', 'Session', 'Goodies.Gravatar');
	// ... existing code ...
}

This will show the user's gravatar based on their email address supplied at registration time, and also their username, instead of that ugly UUID string. Go ahead, refresh the page or browse to another URL to view it, and the gravatar will be shown in place of the user information.

Adding a social sharing widget

Let's do one more step to fancy up the extra information and add some social sharing capabilities to promote more people to visit our site, and to enable users to share the bookmarks they really like. This functionality comes from the CakeSocial plugin we added at the beginning. The CakeSocial plugin includes a helper for the “ShareThis" service, which makes social sharing really simple. So a simple plugin for a simple service, should be super simple, right? Judge for yourself. Add the helper to the AppController in /app_controller.php:

class AppController extends Controller {
	var $helpers = array('Html', 'Form', 'Session', 'Goodies.Gravatar', 'CakeSocial.ShareThis');
	// ... existing code ...
}

And just before the closing tag on your view for URLSs /views/urls/view.ctp include the following:

<dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Share'); ?></dt>
<dd<?php if ($i++ % 2 == 0) echo $class;?>>
	<?php echo $this->ShareThis->display(); ?>
	 
</dd>

Whoa! There are five lines of code for that view, but only one is doing the social output. The rest is wrapping styles for the output, and a label. Reload your page and you'll have the ShareThis social helpers appear, and you can share on a list of default services.

Icing on the cake: Web thumbnails

Since we saved so much time using plugins and getting these awesome features included, we have some spare time to include a cool feature like thumbnailing. Wouldn't it be cool to have a small picture of the website pop up when we're looking at the view page for a URL? That really would be the icing on the cake.

We're going to take advantage of another helper from the Goodies plugin to grab the thumbnail from the Thumboo service (http://www.thumboo.com). Fetching on each view is not loading your server, as the request is handled by the external service, and it ensures that your thumbs are always up to date!

The website thumbnails really make a difference to a bookmarking service

The website thumbnails really make a difference to a bookmarking service

Head over to Thumboo (http://www.thumboo.com) and create an account for free. You'll be given an API Access key. Hold on to that, you'll need it later.

Once more unto the breach! Or, once more we'll edit the AppController `/app_controller.php`, and add a helper. Add the Thumboo helper from the Goodies plugin:

class AppController extends Controller {
	var $helpers = array('Html', 'Form', 'Session', 'Goodies.Gravatar', 'CakeSocial.ShareThis', 'Goodies.Thumboo');

	// ... existing code ...
}

Almost there! Switch back to the view for urls `/views/urls/view.ctp` and add the following immediately after the heading at the top of the file:

<?php echo $this->Thumboo->image($url['Url']['url']); ?>

We've used the smallest form possible to call the helper, and we have not yet supplied that extremely easy to remember API key that Thumboo has provided us with. For this, we're going to create a configuration file, and CakePHP will load this up as it needs it to read the key value. Using this method lets you keep configuration separate from code, and its a great way to kep your secure information safe.

Create a new file in `/config/thumboo.php` and add the following, replacing the key value with what you were supplied after signup:

<?php
$config = array(
	'Thumboo' => array(
		'Key' => 'MY KEY HERE',
	)
);

Reload your page and revel in the beauty of what has been achieved in such a short period of time. You've earned yourself a beer.

Where to now? The app you have built today is a good example of how quickly you can build apps with CakePHP, and the quality of the plugins that are available for free within the community. The current app has a lot of potential to expand to provide extra details like private URLs, a REST service for integration, a bookmarklet that uses Ajax that you can include for your browser, some styling, and removal of some default data and pages. The possibilities are endless!

Footnotes

The code for this article has been made available on GitHub under my account `http://github.com/predominant/cakephp_linux_format`. You can grab the code from here if you had any issues with the code generation through the bake facility, or if you just want to get the application up and running quickly, you can clone the code without going through each of the steps in the article. If you have any questions or feedback on the content, please let me know at http://grahamweldon.com.
First published in Linux Format

First published in Linux Format magazine, issue 141.

You should follow us on Identi.ca or Twitter


Your comments

Great Tut

Thanks Dude, it really helped me out.

php kids ; This is Gut Tut.

Man you are helping alot out of confussion.
I always feel good to see such people, who contributes to out community. We are the World babe.

PHP learner owe lot of you

PHP learner owe lot of you guys . Excellent community service .

He will help you with:

believe through upfront run a the use Nike ? fewer before your is tackle simple do multiple ? when click the the longer or frustrating data ? a years. hear are knives business disaster hard ? these to this a regularly along of device

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

CAPTCHA
We can't accept links (unless you obfuscate them). You also need to negotiate the following CAPTCHA...

Username:   Password:
Create Account | About TuxRadar