The flawless upgrade to Cake 1.3

Cake 1.3 has been around for quite some time now, but I never had the desire to upgrade with it being in such a beginning stage. Well seeing as how its now in Alpha and many users are testing it with no problems, I thought it would be a good idea. The project I was going to upgrade is quite large, heres a quick rundown of just how large:

19 controllers, 8 components, 15 locales, 64 models, 2 plugins, 6 vendors, 8 stylesheets, 14 javascripts, 7 helpers, 159 views

This isn't counting the admin panel, which is a whole separate app, and which I have not upgraded yet. Now lets get onto the upgrade, its probably the easiest thing to do. All I did was follow the migration guide. First off was checking to see if I used any deprecated methods, which was simple, all I did was search the whole app folder for words like del(. I was surprised to find I used it like 5 times, when I always preferred the full delete().

Next in the list was upgrading to the new Router conventions, which lucky for me didn't apply. I never used any non basic conventions, I mean why would you! One thing that bugged me though is the new page title implementation. It took me quite a while to go through all those controllers, and all the actions within them. Another nuisance was adding "echo" to all the flash() calls in the views, and removing the "inline" parameter from all meta() and css() calls, but wasn't too time consuming.

One of my most favorite updates was the new pagination support with passedArgs. I had to hack all my paging before, but in the new update I removed it all and it worked wonderfully! Thanks Cake team :]

All in all, the upgrade went smooth. If you follow Cake conventions all the way through, you don't need to do much alterations. I for one am looking forward to the future versions.

Sorry for the lack of activity

Its been over a month since I posted an entry and even longer then that since it was a CakePHP entry. My goal was to post on average two entries a week, or at most 10 entries a month, but I have failed to do that so I am sorry. I mainly have been absent because of my new job I started on November 2nd, and it has been taking up most of my time and day. Furthermore, I haven't worked in CakePHP for months, seeing as how I have no new projects or features to add to my existing ones. I still haven't upgraded to 1.3!

However, I recently started on another CakePHP project and will hopefully have a few more topics to talk about. Lastly, you may have heard me mention on Twitter that I started building my own framework, which is 100% true. As of right now, the framework has a fully functional Controller and View system, but is missing a Model/database. For the Model aspect, I think I will be going down the Doctrine adapter route, and possibly building my own later on.

Here are just a couple of features currently implemented in the system: Routing, Environments, Controller containers (similar to Zend modules), Class dependencies (not really injection, you'll have to see an example), Registry (Factories and config injection), Lazy loading dependencies, Hooks and callbacks, Debugging and error logging, Exception handling, App global configuration, and has been built using a modular system. What does a modular system mean you ask? Well it basically means that you can use your own script or any 3rd party script in place of the default module. For example, you can use the frameworks view engine, or you can configure it to use a Twig or Smarty adapter engine very easily. The same goes for helpers, libraries, models (Doctrine adapter), extensions, etc. The only modules that can not be changed are the controller and core classes that are used to run the framework.

I'm pretty happy with my self and how this framework came along so far, and I will be showing some code snippets at a later time. Besides all that, I will try my best to start posting actively again!

Fixing the missing Model and AppModel replacement errors

If you are a CakePHP developer, I am almost certain you have run into this problem on multiple occasions. What problem are you referring to you ask? Well, the problem that you have when your model is not being found and Cake automatically substitutes it with AppModel. Cake does this on purpose so that your relations and HABTM's do not need the junction Model to operate correctly. This works in most cases, but sometimes you get some weird errors or missing method problems. But before I continue with this, lets setup a quick scenario so I can better explain this problem. I will be using an example of relating users to teams.

User Model - users Table
Team Model - teams Table
TeamsUser Model - teams_users Table (Join)
User -> hasAndBelongsToMany -> Team
Team -> hasAndBelongsToMany -> User
TeamsUser -> belongsTo -> User, Team

In our code we are stating that a user can be on multiple teams, and a team can have multiple users. Are relation seems pretty simple, but lets not rely on Cake to magically figure everything out. In some cases we would receive the following errors:

  • Model "User" is not associated with model "Team"
  • Warning (512): SQL Error: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'myCustomMethod' at line 1 [CORE\cake\libs
    \model\datasources\dbo_source.php, line 514] Query: myCustomMethod

Now what do those errors mean? Well the first one states that our Models can not be related. So the first thing we need to do is check that our Models naming conventions are correct: model names are singular and camelcased, where as the filenames are singular, underscored and lowercased (without having _model in the filename). The second error means we are trying to call a method on a Model that does not have that method in its class. In other words, it basically means our Model is not being loaded and the AppModel is being loaded in its place. This happens when Cakes naming magic fails to load the correct Model or we have improperly followed conventions.

Most of these problems can be fixed by following the proper naming conventions. I'd also like to note that most of these errors appear during HABTM relations, and can easily be fixed with the following:

  • With any relation, its best to define the "className" parameter. By doing this, we are telling Cake the exact name of our Model, instead of having Cake rely on the relation name (which can easily be changed to custom text).
  • When defining conditions or containments, be sure to include the model name followed by the column name: Model.field
  • Important! When working with complex HABTM relations, define the "className, joinTable and with" parameters. If you have complex relations with crazy table names, its highly required you define the joinTable and with parameters.

During one of my projects, I had a Model that had a HABTM relationship, in which that HABTM had its own HABTM relationship, so it got quite complex. I ran into problems over and over again where Models would not be related and the AppModel was used instead. I later found out that the problem was the with parameter. Since I didn't define it manually, Cake could not figure out the correct HABTM Model to use, hence the whole application broke. Its pretty funny that my whole app didn't work for weeks, all based on this small declaration.

So, since we know how to fix this problem, lets define our relations:

class User extends AppModel {
	public $hasAndBelongsToMany = array('Team' => array(
		'className' => 'Team',
		'joinTable' => 'teams_users',
		'with' => 'TeamsUser',
		'foreignKey' => 'user_id',
		'associationForeignKey' => 'team_id'
	));
}

So in conclusion, follow the naming conventions and define the required parameters when creating relations, and I promise you, you won't have any problems.

To bake or not to bake...

By now most of you have heard that the awesome Nate Abele (which I had the privilege of talking to at Zendcon) is leaving the CakePHP team. A lot of developers were taken back by this and are unsure if they should continue to develop with CakePHP. Now, I find this ludicrous on many levels. Sure Nate was the lead developer, but that doesn't mean he was the only one. We still have the mighty Mark Story and the returning Larry Masters.

I had the opportunity to attend Nate's "Future of CakePHP" panel at Zendcon in which he talked about many of the problems facing current frameworks. Towards the end of the panel he showed off the new Cake 3 functionality using filters and closures, which I have to say, is pretty clever and badass. Now if you haven't figured out by now, the new Lithium project is a fork of the Cake 3 branch, so basically Lithium is a new 5.3 specific framework, which I am fully looking forward to fiddling with. Others have already figured this out, and we should expect it sometime this Monday, according to Nate.

Now where does this leave the future of CakePHP? It will stay the same. I know for once thing, ill continue to develop and pump out scripts because I see no reason in stopping. Additionally, if Lithium is anywhere near the same setup, I can easily port my current component, behaviors, etc to the Lithium architecture. I don't see CakePHP dieing anytime soon, simply because Lithium is an experimental 5.3 framework, and 5.3 wont take off as fast anytime soon. However it should be fun to mess with :]

I wish Nate Abele and the CakePHP team good luck with their new endeavors, I will thorougly be looking forward to both projects.

Uploader Plugin officially released!

For the past month I have been talking about CakePHPs lack of a built in uploader component or mechanism, and how I wanted to build one. Well wouldn't it be great if I actually did build it? Oh wait, what is this? I did build one? Yes. Is it released? Yes. May I use it? Of course! The Uploader is primarily used as an all purpose file uploader and was built to not interact with a model/database. Here is a quick rundown of the plugin.

The plugin comes bundled with an uploader component and a validation behavior. The component is used to upload the files to a destination, resize or generate images, validate against mime types, log errors, scan for viruses and more. The behavior is used to add validation rules to a model to check against image uploads.

A big thank you to http://mark-story.com/ and Matt Curry for beta testing and giving input and ideas.

- Download Uploader v1.3 and view full documentation

Official release of the AutoLogin Component

Many of you have heard me mention a component that keeps your Auth session active, even after the browser is closed. Well I think its time to officially announce it, my AutoLogin component. This component ties into the Auth component and allows a user to "remember me" to keep them constantly logged in. All data is saved to cookies and encrypted and hashed to no hijacking can occur. The best part though, is that the component automatically and magically saves and deletes cookies without you having to configure it (although you can configure it, check out the docs).

Download the latest version of AutoLogin

In other script related news...
Commentia has been updated to v1.3
Resession has been updated to v1.8 (now with more Security!)

Fresh websites, Fresh updates

Well its been a while since I have posted anything useful, and I'm sorry for the lack of activity. I actually have many post ideas up on my whiteboard, its just that I have been extremely busy lately. Tomorrow I will be going on a week long cruise to Canada, so I have been preparing for that all week. Besides preparing for my trip, I have been working on two of my own websites, I will review them both below. Both of which are CakePHP applications... for your information, hah.

GameSync

http://www.gamesync.com

This is a personal project I started on over a year ago. I wanted to create a online interactive social community that is geared towards gaming and the competitive scene. It has many of the features a social community has, and on top of that has a fully robust gaming and activity system. Its something you need to try out for yourself, so please sign up! I am also looking for help (volunteered would be best) on this site, as well as searching for investors.

Starcraft 2 Armory

http://www.sc2armory.com

About 2 years ago I launched a fansite for the game Starcraft 2. Since then it has grown tremendously (just hit 10 million views!) and I recently decided to redesign it. In that process, I also decided to rebuild the whole system in CakePHP from the ground up, check it out! Oh and by the way, I am the #1 unofficial fansite for Starcraft 2... of course!

Anywho, once I get back from my vacation, I promise to put out more posts then I am now. With 2 websites out of the way, I will have so much free time. Hats off to that!

Proxy Search - Doing a search while using named parameters

While working on a project of mine, I wanted to be able to do a search, but have the post data be retained as named parameters (basically like a $_GET string). My first attempt was setting the form method to get, and trying to convert the $_GET by doing some mod_rewrite magic. This didn't work out at all, mainly because I couldn't get it to work for multiple named parameters. The only alternative I found was suggested by someone in the Google groups; they suggested posting a form to another action to deal with the logic, and finally redirect applying named parameters. This works perfectly, albeit adding an extra step in the process.

The process I created is something id like to call a Proxy Search. The following code should be placed in your app_controller.php file, so that it can be used as a global search by all controllers. You should not overwrite the proxy() action, unless you want specific logic in a certain controller.

/**
 * Sorts the named params for a posted page
 * @param string $model
 * @access public
 */
public function proxy($model) {
	$data = array_map('urlencode', $this->data[Inflector::camelize($model)]);
	$referer = explode('/', trim($this->referer(), '/'));
	$router = array_merge(array('controller' => $referer[0], 'action' => $referer[1]), $data);
	$this->redirect($router);
}	
/**
 * Applies the named params to the controller data
 * @return array
 */
public function _proxyGather() {
	return array_map('urldecode', $this->params['named']);
}

I will quickly try to explain how the form works. For example, if we are in the Users controller and want to do a search using the search() action, the forms url should point to /users/proxy/user instead of /users/search. Now shouldn't it be /users/proxy you ask? Well no, we need to know what model should be used in $data, so we need to pass an argument for the model that is used for the form (during $form->create()).

// UsersController::search()
echo $form->create('User', array('url' => array('controller' => 'users', 'action' => 'proxy', 'user')));
// If searching through books
echo $form->create('Book', array('url' => array('controller' => 'books', 'action' => 'proxy', 'book')));

Now back in the search() action, we need to grab the named params and pass them to the fields in the form. We do this by using the _proxyGather() method. No model name needs to be passed to _proxyGather(), simply because the data is pulled from the controller.

/**
 * Search players
 * @access public 
 */
public function search() {
    $this->data['User'] = $this->_proxyGather();
    $this->pageTitle = 'Search Users';
    $this->set('results', $this->paginate('User'));
}

You can also have this interact with pagination quite easily. All you need to do is add some conditional logic in the controller, and pass the data to paginator.

/**
 * Search players
 * @access public 
 */
public function search() {
    $this->data['User'] = $this->_proxyGather();
    if (!empty($this->data['User']['username'])) {
        $this->paginate['User']['conditions']['User.username LIKE'] = '%'. $this->data['User']['username'] .'%';
    }
    if (!empty($this->data['User']['firstName'])) {
        $this->paginate['User']['conditions']['User.firstName LIKE'] = '%'. $this->data['User']['firstName'] .'%';
    }
    $this->pageTitle = 'Search Users';
    $this->set('results', $this->paginate('User'));
}
// In the views using $paginator
$paginator->options(array('url' => $this->passedArgs));

I hope this has been helpful to someone who was looking to do a search system using named params, I know I had fun building it! I was thinking of turning this into a component, but have not figured out a way to do so yet, enjoy anyways!

Pre-populating form values

When I first started using CakePHP, I couldn't find a way to pre-populate a form with data. Why would I need this you ask? Well it's simple, it would be used to edit something already existent. So what I need to do is grab the data from the database, and then populate the form. Took me a few hours to figure out how to do it, the guide really didn't explain how it should be done (or I just overlooked it). To have the data populate, all you need to do is to set $this->data to the database result. Heres a quick example action:

function edit($id) {
    $user = $this->User->findById($id);
    // If data is empty, populate it with user array
    if (empty($this->data)) {
    	$this->data = $user;
    }
    // Form is posted
    if ($this->RequestHandler->isPost()) {
   	 	// Do model validation and save
    }
    $this->pageTitle = 'Edit User';
}

Pre-populating will only be done if the post/data is empty; so once you post the form, the new data should fill the form. Now that we have given $this->data some value, we need to make sure the form is using the correct model ($this->data['User'] equals form User). In our case it would be the User model and its as easy as that.

// $this->data['User'] will populate here
echo $form->create('User');
echo $form->input('email');
echo $form->input('website');
echo $form->end();

Commentia v1.2 Released!

I recently got a few emails from individuals about by Commentia script. They suggested that I add a settings system for the Behavior that loads on setup() (didn't know about this with the lack of documentation). Well to say the least I took their advice and redid the script so you don't have to manually edit the file, you just define the settings in the model. The documentation has been updated to reflect this change.

Download Commentia v1.2
View the complete change log