Tag Archives: cakephp

Twitter Boostrap

One thing I love about the open web is the multitude of tools available to you in order to build web sites and applications quickly and to a very high standard. Over the past few years frameworks and libraries have sprung into existence all aimed at a particular problem domain. Back in the day when these libraries weren’t available throwing together a site, with a content management system, that worked across all browsers, and was very robust, was no small task. Thankfully this has become a lot easier. You have MVC frameworks such as CakePHP that take the heavy lifting out of building the backend, you have JavaScript frameworks such as jQuery that abstract out all of the browser quirks between JavaScript engines and make animation and DOM manipulation a breeze, and finally you have CSS frameworks such as 960 grid which make it much easier to do complex layouts.

I utilise a lot of these frameworks, and the examples mentioned above are just one of the many available. One area of framework which I often would like to use is a UI framework for building interfaces. There is jQuery UI which is a very comprehensive framework but having looked at it I find it a little too bloated.

Another such framework which I recently became aware of was Twitter Bootstrap. This is an interface library which provides common markup patterns with associated CSS and a bit of JavaScript to build lovely cross browser user interfaces. The framework isn’t really suitable for public facing websites but is absolutely ideal as an interface to a CMS.

There are plenty of widgets available which makes it easy and quick to develop a consistent set of forms and tables to display and manipulate your data. There are buttons, tabs, menus, messages and even a handy grid system for your complete UI. I have used it recently on a big CMS build and the results were excellent.

There are a few missing widgets which I would particularly like such as a date picker, but these aren’t too hard to plugin from elsewhere. Also there is a robust roadmap for the framework so I expect things like this to be developed soon.

The CMS I built was based on CakePHP which has a number of in-built helpers which make it quick and simple to build forms. Integrating Twitter Bootstrap with CakePHP wasn’t that hard a task but it doesn’t work out of the box. The markup patterns for the helper and bootstrap weren’t compatible. Fortunately the helpers on CakePHP can be highly customised, its not easy but also not a big task.

I aim to release a plugin helper which will make this a lot easier. It will replace the FormHelper and provides some extra methods to output of UI elements. I’ve been very busy recently so haven’t had chance to write it yet, but look out for it soon on github.

Using S3 with CakePHP

Despite the fact I’ve used amazon S3 on and off for the past 3-4 years I’ve never had to integrate the service into anything and on having to do so for a project I’m currently involved in I’ve come across a bit of a misunderstanding of how it works.

I’ve always assumed it was basically access to a file system on a bunch of server space. There was some clever mapping in the background to push the files/directories around but nothing more than that. Turns out my understanding was wrong. I’m sure the files are still pushed around or mapped to huge SANs in a very clever way but amazon S3 does not have the concept of physical directories.

Amazon S3 is simply a map of keys to files at its most basic level. The keys can contain URI mappings but the actual concept of a directory is incorrect. This misunderstanding stems from using third party tools to access S3. It always seemed like you could created a directory in a bucket, but in most of these tools what you are actually doing is creating a specially named file to represent a directory.

This misunderstanding only became apparent when I was using a simple plugin to my system which interfaced with S3. The plugin uploaded physical files, but what I wanted to do was upload directories for the files first. Something which this library didn’t do.

As always there has to be a better way and in fact there most certainly was. I ended up using the excellent PHP S3 class created by Donovan Schönknecht. Using it was a breeze and integrating it with my CakePHP app turned out to be very simple. Below is an example of pushing a file to S3. Its barebones and not a complete example, in the app I’m working on I have the whole thing integrated with RabbitMQ and the Media plugin through a shell but it should give you an idea of how easy this class is to use.

Download the S3 class and place it in your vendors folder, then in your controller assuming the file is already on the server.


public function pushToS3($file) {

  App::import('Vendor', 'S3');
  $s3 = new S3('access_key', 'secret_key');

  $upload_file = $s3->inputFile($file, false);
  $s3->putObject($upload_file, 'bucket', $file, S3::ACL_PUBLIC_READ);

}

Its as simple as that. The third argument to the putObject() function is where you specify the key to the file, if you want a URI with a directory like structure here is where you specify it.

Huge thanks must go to Donovan Schönknecht as his class takes absolutely all of the heavy lifting required out of interfacing with S3. There’s a whole bunch of operations which the class can do, so if you need to do stuff a bit more complex I would highly recommend using this class.

Getting CakePHP database session data for an external script

I have a requirement for a current project to be able access the CakePHP session data for a script which is running outside of the framework. In normal circumstances this would be as simple as:

session_name('CAKEPHP');
session_start()
print_r($_SESSION);

However in my case the session data is stored in the database. In this situation you need to do a slight bit more to retrieve the session data. Thought I would share the code here for anyone who may need it.

The data is stored in the database in a serialised form and the session_key is essentially stored in a cookie. Therefore the equivalent of the above is:

include_once('../config/database.php');
$db = new DATABASE_CONFIG();

$dbh = mysql_connect($db->default['host'],$db->default['login'],$db->default['password']);
$dbn = mysql_select_db($db->default['database'], $dbh);

$session_qry = mysql_query('SELECT `data` FROM `sessions` WHERE `id`="'.$_COOKIE['CAKEPHP'].'"', $dbh);
$session_data = mysql_result($session_qry, 0, 'data');

session_start();
session_decode($session_data);
print_r($_SESSION);

Hope this comes in useful.

CakePHP Auth Component and Multiple Models

It seems recently all I have been doing is chasing things that are hard to debug :-( This may or may not be a bug but has had me chasing things for a while. I have a system which has two (three) different types of user, a system for users and a group user. They have very different use cases and it didn’t make sense to have a user type. One person may be either a user, a group admin or both. The features for each type were completely separate, i.e. if you were a group admin you wouldn’t automatically be a user as well.

As this was the case I decided to create a separate login system for both functions. Different tables, different models. Both had a login page and both used the Auth component. I did a simple switch in the app_controller to set up the Auth components variables and everything worked on an individual level, i.e. if you visited each login page without having first visited the other login page during the session. However if you went to one login page first and then to the other login page you couldn’t login to the second area.

After a few hours debugging I discovered that setting the $this->Auth->loginRedirect property isn’t enough to control the Auth component. When you first visit a page in a session cakephp sets the Auth.redirect session variable to the Auth::loginRedirect property. The Auth component then checks against this session variable when you login. If they don’t match then you are redirected to the session variable, which you probably don’t have access to as you haven’t logged in to this area.

The solution to this is to manually set this session variable when you change any Auth component properties.

function beforeFilter() {

  if($this->params['controller'] == 'groups' or $this->params['controller'] == 'group_users') {
  
    $this->Auth->userModel = 'GroupUser';
    $this->Auth->loginAction = array('controller' => 'group_users', 'action' => 'login');
    $this->Auth->loginRedirect = array('controller' => 'groups', 'action' => 'index');
    $this->Auth->logoutRedirect = array('controller' => 'group_users', 'action' => 'logout');
    $this->Auth->allow('display','login','logout');
    $this->Auth->authorize = 'controller';
    $this->Auth->userScope = array('GroupUser.active' => 1);
    
    $this->Session->write('Auth.redirect','/groups/index');
    
  }
  else {
  
    $this->Auth->userModel = 'User';
    $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
    $this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
    $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
    $this->Auth->allow('display','login','logout');
    $this->Auth->authorize = 'controller';
    $this->Auth->userScope = array('User.active' => 1);
    
    $this->Session->write('Auth.redirect','/users/index');
    
  }
  
}

At the moment I can’t decide whether this is a bug or whether my use case is a bit wrong. Obviously just setting the loginRedirect property of the Auth component shouldn’t affect the session. Maybe there needs to be a method to set this property instead which handles the session?

CakePHP Self Referencing User Model

I recently needed to create a data model which included a friend-type system. Not a social network type thing but simply the ability to tag other users of a system. Sounds simple enough!

The application is written in CakePHP which has some fairly good ORM facilities but what I wanted wasn’t really simple to implement. The relationship I wanted to configure was a hasAndBelongsToMany relationship with a single table. In other words the join table referenced just one table. The join table looked like this:

CREATE TABLE IF NOT EXISTS `users_tags` (
  `user_id` INT(11) NOT NULL,
  `tag_id` INT(11) NOT NULL
);

And here is where things get a bit harder. There is no tags table which by convention the HABTM would usually join up to. What I really wanted was this:

CREATE TABLE IF NOT EXISTS `users_users` (
  `user_id` INT(11) NOT NULL,
  `user_id` INT(11) NOT NULL
);

Which obviously won’t work as you can’t have two columns named the same. Fortunately everything in CakePHP is configurable so setting the table up like this:

CREATE TABLE IF NOT EXISTS `users_users` (
  `user_id` INT(11) NOT NULL,
  `tag_id` INT(11) NOT NULL
);

Then configuring the User model with this relationship:

var $hasAndBelongsToMany = array(
    'Tag' => array(
      'className' => 'User',
      'join_table' => 'users_users',
      'foreignKey' => 'user_id',
      'associationForeignKey' => 'tag_id'
    )
  );

Enabled me to pull out all the tagged users for a particular user. This is where the fun started for me. I now needed the ability to tag players. I set up a list of users you could tag with a controller action like this /users/tag/$id. As I was logged in I already had the user_id bit and was passing the tag_id into the controller action. I expect to be able to do:

$this->User->UserUser->set('tag_id', $tag_id);
$this->User->UserUser->set('user_id', $user_id);
$this->User->UserUser->save();

But no matter what I tried the data wouldn’t save as one row. For some reason two rows were inserted, both with a blank user_id and the two ids supplied as tag_ids. I know that HABTM calls delete before saving but I didn’t see how it would be affecting the code as above. I tried for a while to get the CakePHP way of doing this but couldn’t work it out. In the end I just used a plain old query() to achieve what I wanted:

$this->User->query('INSERT IGNORE INTO `users_users` SET `user_id`='.$id.', `tag_id`='.$tag_id);

Whilst this does the job I would rather have worked out why it wasn’t working in the CakePHP way, so if any experts out there can shed some light please do!

This wasn’t the end of my woes tho. The application also uses the Auth component. The HABTM relationship seemed to be causing the Auth component some issues, when it logged in it was issuing a save() call which resulted in all my tags being wiped out. The solution to this was to unbind the model in the beforeFilter() method:

function beforeFilter() {
    
    $this->Session->start();
    
    $this->User->unbindModel(array('hasAndBelongsToMany' => array('Tag')));

    $this->Auth->autoRedirect = false;
    $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
    $this->Auth->loginRedirect = array('controller' => 'games', 'action' => 'grid');
    $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
    $this->Auth->allow('display','login','logout');
    $this->Auth->authorize = 'controller';
    $this->Auth->userScope = array('User.active' => 1);

  }

After this the tagging relationship seems to work a treat. Hopefully this will post will prove useful to anyone trying to achieve a similar result.