Deploying a CakePHP app with Capistrano

Deploying a CakePHP app with Capistrano

During my internship i was working on a large project and at the end of the process it became more and more difficult to keep the development version and the live version in sync while maintaining different configurations.

A couple of colleagues used Capistrano for their Rails projects and i tried to modify Capistrano so that it would work with CakePHP.

Password-less login

First we need to be able to login without providing a password this guide from Brian Rosner describes the steps to do this.
The reason for this password-less login is that you don’t have to provide a password when you deploy this way.

Capistrano

Next we need to get Capistrano. I work on a mac with OSX 10.5, witch has Capistrano built-in. If you have never updated this version of capistrano you might want to gem update your version.

Folder structure

capistrano folder structure
The image above shows my folder structure. The current folder is actually a symlink to the latest deploy of my project. The logs is for my apache access and error logs and the releases folder contains the latest 10 releases deployed to the server.

The shared folder contains files and folders that i want to use instead the ones i deploy. In my case i keep the /app/config folder here.

Capfile

In the root of my project folder i created a capfile (no extension)

that contains the following code:

load 'deploy' if respond_to?(:namespace) # cap2 differentiator
role :web, 'example.com'
ssh_options[:username] = 'notrootofcourse'
ssh_options[:forward_agent] = true

set :scm, :git
set :scm_verbose, true
set :repository, 'git@git.example.com:repository'
set :branch, 'master'
set :deploy_via, :remote_cache
set :use_sudo, false
set :application, 'application_name'
set :deploy_to, "/data/webservers/#{application}/"

namespace :deploy do
  task :start do
  end
  
  task :stop do
  end
  
  task :restart do
  end

  desc <<-DESC
    Symlinks shared configuration and directories into the latest release
    Also clear persistent and model cache
  DESC
  task :finalize_update do
    run "rm -rf #{latest_release}/app/config; ln -s #{shared_path}/app/config #{latest_release}/app/config"
    run "rm -rf #{latest_release}/app/tmp/models/*"
    run "rm -rf #{latest_release}/app/tmp/persistent/*"
  end
  
  
end

namespace :tail do
  task :default do
    run "tail -f #{deploy_to}/logs/*.log"
  end
end

The first lines of the Capfile set some global configuration options, like your ssh username that can login without providing a password, the (in my case git) repository to pull the latest release from and the name and path of the application.

To make capistrano work with (Cake)PHP you need to overwrite all the default tasks that capistrano runs when deploying. Since i use apache and php, we don’t need to restart the webserver, so we just create a :restart task that does nothing, the same goes for :start and :stop

in the :finalize_update task i link the /config dir of my CakePHP app to the shared folder i described above. This way i always use the live configurations instead of my dev configs. Also, just to be sure, i remove the cached data.

All there is to do is a “cap deploy” in the root folder of your project and capistrano will deploy the latest version to your server.

This is of course just a simple example of a capistrano config and you can create your own folder structure and run your own commands.

Besides deploying i also created a :tail namespace, witch tails my log files. This way i can do a “cap tail” to see what’s going on with apache without logging in trough ssh. Other possible functions could be a “cap clearcache” to remove all the cache files.

Filed Under: CakePHP, English - read on

Serializeable behavior for CakePHP

For a project i’m working on i needed to serialize a number of arrays before they could be saved and since i don’t like to serialize and un-serialize all those arrays in each controller action, i wrote a behavior to take care of this.

It serializes all array data before it is saved into the database and when you do a find it will unserialize all data(including related model data).

Download serializeable behavior

Filed Under: CakePHP, Code, English - read on

Easy forms with CakePHP

Last night i was thinking about the fact that in some cases i can’t use bake to re-bake my views to contain a new field for example. CakePHP also wants you to not write the same code twice, but what about the views. Every time you add a field to your model, you either need to re-bake your views, or update both the edit and add template files. You could use $form->inputs(); but the helper creates form fields for database fields you probably want to keep hidden (_count fields for example).

I can think of two solutions for this problem, namely.

1. Go the partial way (like rails uses it). Basically you make an element with all the form fields you want to display and use the $this->renderElement in the view to display the form fields.

2. Define public fields in your controller and use those. An example below:

post has the following fields:
id, title, content, created, comments_count.

we only want the user to be able to edit the title, content and maybe the created fields.

in our controller we create a new var: “publicFields”


var $publicFields = array('title', 'content', 'created');

you can set this var in the beforeRender callback like this: (controller)


function beforeRender(){
$this->set('publicFields', $this->publicFields);

and in your view you render the inputs using the inputs function of the form controller like:


$form->inputs($publicFields);

this way you only have to add a new field in your database and in the $publicFields array and all your forms update automagically.

What do you use to reduce the amount of code you need to edit for a change in the database?

Filed Under: CakePHP, Code, English - read on

Cakephp routing: prefixes and forms

Prefixes are a nice way to define routes for groups of actions, (see the cookbook).

All admin_ actions go with the /admin// url
For my project i needed both an admin route and a dashboard route, so i made prefixes for them like:

(/app/config/routes.php)

Router::connect('/dashboard/:controller/:action/*', array('prefix' => 'dashboard'));

Now in the view you can use the following:

echo $html->link('Profile', array('controller'=>'users', 'action'=>'dashboard_profile'), array('title'=>'Edit profile'))

But when it comes to forms things start to break.

For example the following:

echo $form->create('User', array('action'=>'dashboard_profile'));

or this one:

echo $form->create('User', array('url'=>array('controller'=>'users', 'action'=>'dashboard_edit')));

generate the following (wrong) link: /users/dashboard_profile/1

While it does point to the right action (dashboard_profile) you get an error message explaining that this is a private action. The only way to use this private action is to use the prefix method.

The reason for this is that the form generate part in the cake code doesn’t do anything with the routing, thus ignoring the prefix we’ve set.

After some digging in the api i found a cake-way to generate the right url:

echo $form->create('User', array('url'=>$html->url(array('action'=>'dashboard_profile'))));

We use the html helper, that does parse the prefix part and generates a nice correct url, namely: /dashboard/user/profile

The html helper does check for any prefixes in the routing and generates the right url.
Combining the form->create and the html->url we get the right url.

Filed Under: CakePHP, Code, English - read on

A central hub for cakePHP

Last week i was in the #CakePHP channel on irc.freenode.net when i noticed a discussion about someone who had created a component, but didn’t want it posted on the bakery. He thought that his code wasn’t up to par with everything else on the bakery. So he thought about posting it on his blog.

I can see why he didn’t want to post it on the bakery. You can get a lot of critics there, but those are only there to help improve the thing you posted and can help you to write better code.

It got me thinking how scattered the information about Cake really is. There is, of course, the bakery, but there are also the saved bookmarks on del.icio.us, the custom search engine, the superfeed and a gazillion blog posts about cake. All containing really good components, helpers and tutorials.

But what i really want is a central hub for all of these great contributions to Cake. Right now i’m feeling that i only get to see a part of all that’s good about cake and still miss out on a lot of good stuff. Shure if i need something i google it first before i create it myself, but there must be a better way to stay up-to-date about all that is cake.

The bakery is a good start, but there are a lot of people who don’t want to post their stuff there, because they think it isn’t good enough and there is a list on the CakePHP home page, but it only lists a few blogs and there are so much more.

I think it would help the community as a whole and give more attention to all the great CakePHP stuff out there.

What do you think? Does CakePHP need a central place for all stuff cake, or do you think it’s fine the way it is. Or maybe you have a great idea to get people to post more of their content to the bakery?
Let me know in the comments!

Filed Under: CakePHP, English - read on