CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

Making great things even greater

If you ever have the time, take a few seconds of your day to check this out: https://github.com/cakephp/cakephp/commit/1e6c7b9d902d6867e3b475bb437eabe98c0acce3 Though this may seem trivial to some, it was a very significant moment in the history of the CakePHP framework. Over 8 years ago now, on the 15th of May 2005, the source code for the project was released under the MIT open source license. So, why was this so important? Simply because it was the first major step which got the project to where it is today. It's also been over 6 years now since the Cake Development Corporation was established by Larry Masters, founder of CakePHP, along side the now departed Michal Tatarynowicz and Kamil Dzielinski. Many well respected developers, as well as contributors to the project, past and present, have set foot in the company, delivering the very best of CakePHP in some awesome projects, while leaving their footprint in the process. And it not only counts for those on the inside, but also the developers from the community, who openly collaborate on the CakeDC open source plugins. These have been a long and colorful six years, full of roller coaster ups and downs, twists and turns, but it's not so much the "when it was created" that counts here, but the "why". Rewind back to 2007, and Larry's proposal was simple: to create a commercial entity which allows people to live and breathe CakePHP, doing what they love day-to-day, while also providing them with a means to support their financial obligations. That's it. Sounds simple, right? Ha! That's much easier said than done, and you’ll soon find out why. Over the coming months we'll be taking an in-depth look at the history and internals of the Cake Development Corporation, giving you a unique insight through a series of posts into how this singular company does business very differently. Hope you enjoy the ride!

Working with a company that embraces o...

I've done my fair share of working for closed and "open" companies. I've recently (in July 2011) clocked over two years working here at the Cake Development Corporation, and while attending the Open Source Developers Conference (2011) in Canberra, I have had some time to reflect on my experiences with the company, and my feelings regarding my work here. Traditionally I have found that companies that claim to be pro-FOSS or open source companies are those that are making a profit, through the use of open source technologies. This is awesome. I love that the proliferation of open source software continues to grow and be adopted by traditionally closed, and proprietary software users. This produces better quality software for all of us. While CakeDC produces a large quantity of client projects that are closed source, what we do have control over is the common reusable components that we use to build and produce web applications for our clients. These are developed and refined over a number of years, and have been a pillar in our success as a company. To be able to draw on years of experience through various developers and quickly build high quality, high performance websites continues to draw attention, referrals and interest from businesses and the community alike. A decision made back in mid 2010, initially proposed by Larry Masters, our President, was to open source all of our plugins. This decision stirred a lot of discussion internally, and there were mixed opinions. While we each individually contribute to open source, speak about it at conferences, engage the community and promote open source, the concept of releasing all our internal code for public consumption for me was a little daunting. The decision was made, and we spent some time cleaning up code, making sure everything was documented and in a good state to release. You can now find all of our plugins and projects on the CakeDC Github Account. The initial load of dealing with issues and support questions, emails and contact from the public was somewhat overwhelming. We deal with issues and features very well internally, but as the process is different to open source projects we contribute to, this produced a somewhat less productive period of time for us while we adjusted our work structure and organisation to accommodate our new open source projects. We now action issues, support requests and other contact from users in a timely manner, and are receiving new and useful commits to the repositories consistently from the community. Overall the experience has been a learning one, and a very positive one. CakeDC support the staff and community in other ways. We are constantly sending staff to conferences both to speak and to attend. This allows us to talk more broadly about CakePHP, PHP in general, and other projects we use and are involved with. It also allows those attending the events from CakeDC a great opportunity to network, and learn from some of the more interesting and innovative minds of our time. This is something that we have come to do through the support of CakePHP, and through our newfound knowledge and experience in working with communities and projects openly. Working with a company like CakeDC, embracing open source and supporting a community like CakePHP is extremely rewarding and equally challenging; and a job without challenges is not what that I would want to be involved in for any long period of time. After speaking with many people working on awesome, interesting projects that are closed, or "not ready" to open source, I really count myself lucky to be working for a company that has embraced open source, contributed to the community, and demonstrates a dedication to supporting those projects and communities.

Call out to the CakePHP community

Everyone knows CakePHP has one of the largest and most loyal communities in the Open Source world, over the last few months we have been witnessing some very disturbing things happening to one of our community members. Many of you may know Jonathan Freeman over at Widget Press he created some tools that have helped many people build applications using CakePHP. He has also been a target of a patent troll suing him for patent infringement. As a software developer I find the tactics of the software patent trolls to be one of the biggest hurdles of innovation in todays development market, too many people afraid to pursue an idea because they fear being sued by a company who had an idea and was not skillful enough to build something from that idea. What upsets me even more is these trolls target people or companies who do not have the funds to stand up to "Goliath" and defend themselves. Well today we as a community need to help one of our own standup and become a David facing Goliath. What I am proposing is helping Jonathan gather some "stones" in the form of small donations from our community. If we have enough people donate we might be able to help him arm himself to defeat the troll "Goliath". We (Cake Development Corporation) are putting up $1000.00 for his defense fund on behalf of the CakePHP project and I am asking people in our community to help also. You do not need to donate this amount or you can do more if you like, any amount will be useful. But let's come together like an unexpected force and help one of our own. Updated information, if you can not donate money to help Widget Press maybe you can help  via ArticleOne on twitter.com "We launched a Second Study around a #patent in the MacroSolve App Developer case, this time with $10,000 #Reward http://ow.ly/5tO9f"

Tags Plugin release v1.1

Following the release an update for our Utils Plugin, we've compiled a few commits that have been finalised on the Tags plugin, bundled it and packaged for release. The tags plugin, if you've not used it, is a great and simple plugin that allows you to apply tags to any object in your existing application without modification of tables or structure. Its unobtrusive, and awesome. This latest update takes it to v1.1 with the following changes:

  • Commit [79afb1d]: Update inline docs, and test behavior removal for #5
  • Commit [0d96881]: Renamed schema to work properly.
  • Commit [982ff5b]: Minor readme update.
  • Commit [edd0e8e]: updating readme
  • Commit [db78a26]: update russian translation plural forms
  • Commit [48c1a44]: Adding spanish translation
  • Commit [3347464]: Added Portuguese translation
  • Commit [f4c4e6b]: Adding german translation file
  • Commit [44379a7]: Update license text.
  • Commit [da433cb]: Cleaned up headers for all files.
  • Commit [8c76f95]: Renamed license and readme files.
  • Commit [6e6eae4]: Renamed license and readme files.
  • Commit [99f1e89]: Added an initial Russian translation
  • Commit [93a7ad6]: Documenting identifiers in tags and the new taggedCounter behavior option
  • Commit [0242a6e]: Adding assertion to ensure trailing whitspace is removed before saving the tag
  • Commit [2de4e80]: Fixing remaining failing test cases
  • Commit [d03e1a6]: Adding the ability to have a counterCache to track the times a record has been tagged with a particular tag
  • Commit [5527079]: Fixing bug in saving tags with identifiers prepended. Refactoring code to avoid repetetions
The tags plugin received a number of ticket submissions over on lighthouse app from the community. We can't thank you enough for taking the time to submit questions, issues and suggestions to the ticket system. Its helped us fix problems and extend the plugin to become an even more useful plugin for your apps. The release is available now on the master branch of the repository, or you can download a release archive here. We hope you enjoy the update!

Using the CakeDC Tags plugin for CakePHP

This is an introduction to using the CakeDC Tags plugin for CakePHP. I'll take you through a new project creation, and the addition of the Tags plugin to your project for use with tagging a Blog model on your project. You should be able to take the skills learnt here to any other project, and start taking advantage of the Tags plugin for tagging your models appropriately. Lets get started by baking a new project: cake bake project blog1 Follow the prompts to complete the baking operation. You will now have a "blog1" directory available. Change into that directory: cd blog1 ensure that the `tmp` directory is writable: chmod -R 777 tmp Open up the `config/database.php.default` file in your favourite editor. Immediately choose to "Save as..." and save the file in the same location omitting the ".default" part of the filename. So save the file as `config/database.php`. Configure the options at the bottom to match the database credentials for your application. Mine are as follows: <?php class DATABASE_CONFIG { var $default = array( 'driver' => 'mysql', 'persistent' => false, 'host' => 'localhost', 'login' => 'dev', 'password' => 'dev', 'database' => 'blog1', 'prefix' => '', ); } For the moment, I have remove the 'test' datasource, as we won't use that right now. Go ahead and create your MySQL database, and a simple table to hold blog items: CREATE DATABASE `blog1`; USE `blog1`; CREATE TABLE `blogs` ( `id` CHAR(36) NOT NULL PRIMARY KEY, `title` VARCHAR(255) NOT NULL, `body` TEXT, `created` DATETIME, `modified` DATETIME ); Now lets bake the controller, model and views for this blogs table, in order to be able to add and edit content. Once this is complete, we'll begin integrating the tags plugin into the application. First bake the model: cake bake model blog Next bake the controller. The following bakes all the "public" actions for this controller: cake bake controller blog public And finally, the views: cake bake view all Browse around your application at the address: /blogs to begin with to ensure that your app is functioning correctly. You should be able to add, edit, delete and view blog entries. Time to get cracking on the Tags plugin. Our objective here is to tag each blog entry with an arbitrary tag at add / edit time to allow us to easily categorise content we are posting. In order to download and install the Tags plugin, I'll be using git. You can however download an archive from the github website, and extract that archive into your `APP/plugins` directory. In either case, the result will be a `tags` directory in your `APP/plugins` directory, containing the contents of the CakeDC tags plugin. From your `APP` directory (in this example, the APP directory is `blog1`), clone the tags repository: git clone git://github.com/CakeDC/tags.git plugins/tags The first thing that we need to do now that the Tags plugin has been added to our project, is to create the tables required to store the tag information. These are available in schema's and migrations within the Tags plugin, so you don't need to handle the SQL yourself, just use the cake console to create the tables for you: If you prefer using the builtin CakePHP schema mechanism, or you are not sure what the "migrations" plugin is, you can create the database tables like this: cake schema create schema -plugin tags -name tags If however, you are familiar with using the migrations plugin, or you want to use the migrations plugin for this project, add the migration plugin to your project, and then run the migrations: git clone git://github.com/CakeDC/migrations.git plugins/migrations cake migration -plugin tags all Either method is fine. Next up, we need to add the `Taggable` behavior from the `Tags` plugin to our model to enable all the awesome functionality. Add the following variable to your `Blog` model in `APP/models/blog.php`: public $actsAs = array( 'Tags.Taggable' ); Finally, we need to add a new input for the tags on our add and edit screens, to allow users to customise the tags they want for the blog posts. Simply add a new input called 'tags' to your forms, such as the following: echo $this->Form->input('tags', array('type' => 'text')); Note that this needs to be done for both your add and edit views. You can also make this be of type `textarea`, if you need gigantic amounts of tags. `text` is fine though, to allow a good number of tags, and to minimise the input space. This is all you need to do to enable your content to be tagged! Looking back at all the instructions so far, the bulk of the content has been on how to create a new project, bake the model, views and controller, and the addition of plugins. In terms of code addition, we've only added a behavior to the Blog model, and a new input to the add and edit views. To test your tagging, use a comma to separate your tags when using the tags input. Using a comma allows you to enable users to add multiple-word tags. What now!? You can tag stuff, thats pretty cool. You probably want to look up blog posts based on tags now. Thats already provided for you in the Tags Controller quick comes with the Tags plugin. Browse to `/tags` to see the tags controller index action from the tags plugin render all the tags that you have added to your blog so far. There is a whole lot more that you can do with tagging in terms of both operation and the visual representation of the tags themselves. Stay tuned for more blog articles explaining our plugins and other interesting PHP and CakePHP code from myself and the rest of the CakeDC team. UPDATE: An excellent guide on how to style the tags with CSS has been written by @WyriHaximus, check it out here.

Utils Plugin release v1.1

The Utils plugin is our mixed bag of "awesome". If you've not yet checked it out, definitely hop over to github to check it out. It aggregates a lot of useful code and miscellaneous ideas into a single plugin thats portable and dead easy to use in your applications. Since its release in September, we've made a few changes and updates, and we've bundled a new version for release. Here's a summary of the commits:

  • Commit [7bdf401]: Update license and readme.
  • Commit [e7630bd]: Added tests for data retrieval and false return from model delete.
  • Commit [8510fe4]: Updated documentation for Soft Delete tests.
  • Commit [f7d9983]: Removed empty test file.
  • Commit [c5db61b]: Changed the behavior saving the position manipulation without running model callbacks and validation by default. This is now also configureable by setting 'callbacks' and 'validate' in the behavior settings to true/false.
  • Commit [ca98003]: updating readme
  • Commit [edc6576]: updating readme
  • Commit [da6ec86]: Add a russian translation
  • Commit [a2319ca]: Adding spanish translation
  • Commit [752f1d7]: Added a Portuguese translation
The release is available now on the master branch of the repository, or you can download a release archive here. Don't forget if you have any issues, suggestions or fixes for the utils plugin, you can lodge a ticket on Github. Enjoy!

CakeDC Plugins updates, October 2010

Its been a little while since we launched our plugins at CakeFest 2010 to the community, and a few things have been changed and updated in that time, so its time to throw out a new release for the community. We have received a huge response after opening our code to the community, and we're absolutely thrilled to know that you're taking advantage of the experience and effort that CakeDC has put into making these plugins. Getting feedback and hearing stories about usage makes it all worthwhile. The team has been monitoring tickets, and cleaning up where we can in-between "real work" :) Thanks to everyone that lodged tickets, submitted patches, we're overwhelmed with the generosity that people have shown by contributing to help benefit the community and to further the work we began. This blog marks the beginning of a run of updates we're doing with the plugins that have been released. We'll process tickets, package and release new versions every couple of weeks to ensure we're on top of tickets, and getting any updates published for people to use on a regular basis. We hope you enjoy the upcoming releases, and thanks again for the support! From all the team at CakeDC.

i18n routes with CakePHP 1.3

Internationalizing a CakePHP application can be tricky when it comes to deal with i18n urls. We will see in this article how the Custom route classes introduced by CakePHP 1.3 could be used to add the current language to your urls in a few lines of code. EDIT: This proof of concept has now been improved and a better version of the code below can be found in CakeDC's I18n plugin on Github

Requirements

This article will not go too deep in internationalizing an application as many resources already exist about it. We suppose the following:
  • Your application defines the current language on given the language code passed in the url
  • The available languages are configured via Configure::write('Config.languages', array('eng', 'fre', 'deu'));
  • You use the CakePHP array syntax for defining urls:
    • $this->Html->link('link', array('controller' => 'posts', 'action' => 'view', $post['Post']['id']));
    • $this->redirect(array('controller' => 'posts', 'action' => 'index'));
    • Router::url(array('controller' => 'posts', 'action' => 'index'), true);
Custom routes were already introduced by Mark Story on his blog, so we will not do it again here... before continuing be sure you have read "Using custom Route classes in CakePHP"

Show me some code!

I18nRoute

As I said (or not), routes are probably the best place for customizing your urls and add information in them... much more better at least than overriding the Helper::url() method in an AppHelper class! Custom routes introduced a way to customize how routes are processed in a very easy and powerful way (i.e ~20 lines of code). It is a bit like wrapping the Router class in CakePHP 1.2, a good example of this was the CroogoRouter. First, we are going to create an I18nRoute class extending CakeRoute in the "/libs/routes/i18n_route.php" file. Here is its code: <?php class I18nRoute extends CakeRoute { /** * Constructor for a Route * Add a regex condition on the lang param to be sure it matches the available langs * * @param string $template Template string with parameter placeholders * @param array $defaults Array of defaults for the route. * @param string $params Array of parameters and additional options for the Route * @return void * @access public */ public function __construct($template, $defaults = array(), $options = array()) { $options = array_merge((array)$options, array( 'lang' => join('|', Configure::read('Config.languages')) )); parent::__construct($template, $defaults, $options); } /** * Attempt to match a url array. If the url matches the route parameters + settings, then * return a generated string url. If the url doesn't match the route parameters false will be returned. * This method handles the reverse routing or conversion of url arrays into string urls. * * @param array $url An array of parameters to check matching with. * @return mixed Either a string url for the parameters if they match or false. * @access public */ public function match($url) { if (empty($url['lang'])) { $url['lang'] = Configure::read('Config.language'); } return parent::match($url); } } The most important part of the code is in the "match()" method. We just add the current language to the url "lang" named param if it was not set. The constructor was also overriden to add a regex pattern for the "lang" param. Thus, only lang prefixes defined in your list of available languages will be parsed by the route.

Define your routes

It is now time to use this custom route in your application. Here is how the default route for pages could be defined in "/config/routes.php": App::import('Lib', 'routes/I18nRoute'); Router::connect('/:lang/pages/*', array('controller' => 'pages', 'action' => 'display'), array('routeClass' => 'I18nRoute'));
  1. import the library file containing the custom route
  2. add a ":lang" param in where you want the language code appear in the url
  3. tell the Router you want to use this custom class (third param)

Link from everywhere!

Now you won't have to worry about the language code transmitted in your urls... every generated link will contain the current language code. If you want to switch the language (for instance switching to the French version of your application), you will just have to add the "lang" param to the url array. Here are some examples of urls which would be generated on the "/eng/posts/index" page: $this->Html->link(__('French', true), array_merge($this->passedArgs, array('lang' => 'fre'))); // /fre/posts/index $this->Html->link('link', array('controller' => 'posts', 'action' => 'view', $post['Post']['id'])); // /eng/posts/view/2

Disclaimer

This code is experimental and the article shows you how to use CustomRoutes to implement this basic feature. Many improvements could be added to fit your needs (no language code for the default application lang, short languages code...) Even if the tests we made were successful, we have not used this code in production yet so there may be "real word" use cases that are not handled correctly with this solution... if you find one, please tell us in the comments!

Feature rich, customizable comments pl...

Freshly baked by the friendly team here at CakeDC is the Comments plugin. The comments plugin allows you to enable comments on any controller for any existing model in you application. Built in a manner to allow complete separation from your application, enabling and including the comments functionality is almost too easy. A good use case is the addition of comments to blog posts. In this case you can facilitate user feedback on information posted on your web site to further enhance the facilities of your existing application. The documentation takes you through a practical example of how you can include this into an existing application with only a couple of code lines.  

Quick start with Migrations plugin

In a previous post I gave an overview of the CakePHP Migrations plugin, what it does and why you should use it in your applications. This article will explain how to use it in a practical way. We are going to bake a simple blog application recipe application and see how migrations are integrated in the development process. Since we recently moved all our open source projects on http://cakedc.github.com/, this sample application source code is also available there: Sample Migrations Application - Github (it is a CakePHP 1.3 application). Ready?

Bake a new application and add the migrations plugin

First of all, we need to bake a new CakePHP application. Easy enough to do using cake bake, then configure your database (an empty database is sufficient for now) and check that the home page is all green! If you have not set up your environment to use the CakePHP command line yet, take some time to do so... it worth it! Adding the migrations plugin might also be a straightforward task. You can either download the archive containing the plugin code and unzip it in the "/plugins/migrations" folder of your application, or  add it as a git submodule with the following command: git submodule add git://github.com/CakeDC/Migrations.git plugins/migrations Then check that it is correctly installed by executing the following command from your application root: cake migration help If you see a list of available commands you can move on next step.

Create initial tables and bake the MVC

We now need something to migrate! Let's create some tables in the database. The application will have Users who can publish Recipes, each one having several Ingredients (of course Ingredients can be used in many Recipes). Here is a SQL dump of this simple database schema: CREATE TABLE `ingredients` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; CREATE TABLE `ingredients_recipes` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ingredient_id` int(11) NOT NULL, `recipe_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; CREATE TABLE `recipes` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `name` varchar(100) NOT NULL, `content` text NOT NULL, `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `password` varchar(255) NOT NULL, `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; As our goal here is not to focus on the application code itself, baked MVC from these tables might be sufficient... just run the command cake bake all for User, Recipe and Ingredient to bake'em all! At this point we must have an application with an initial architecture ready to share. To start from here, one will just have to checkout the related commit... but don't you see a problem with this? How will he create the initial database? Maybe we could send him the SQL dump by email, or better commit it with the application! It is where the Migrations plugin comes in.

Generate the initial migration

"Be kind with your coworkers and include the database schema with your code... along with some sample data." Let's use the migrations shell to generate an agnostic database schema containing our 4 tables, and an initial admin user account. To do so we just need to run the following command: cake migration generate After entering a name for the migration and selected the database dump option, we might have a new "/config/migrations" directory containing two files:
  • map.php representing the different migrations order,
  • name_of_the_migration.php a migration file containing all the necessary information to create your actual database. In the sample application it is named: "001_added_users_recipes_and_ingredients_tables.php". You might have noticed that we added a 001 prefix to the migration name to make it easier to see migrations order, it is a good practice.
We can now open the generated migration file (/config/migrations/001_added_users_recipes_and_ingredients_tables.php) and take a look at it. If you need more information and understand all available migration directives, you can read the plugin documentation. For now we are just going to focus on the empty "after()" callback. This callback is triggered once the migration has been executed, and allow you to do whatever you want, given the direction of the migration: applied (up) or reverted (down). We are going to use this callback to create an initial admin User. Here is the code of the callback (as you are a CakePHP developer you might understand it quite easily): function after($direction) { if ($direction === 'up') { if (!class_exists('Security')) { App::import('Core', 'Security'); } $User = $this->generateModel('User'); $user = array( 'User' => array( 'name' => 'admin', 'password' => Security::hash('unsecurepassword', null, true))); $User->save($user); } return true; } Notice the use of the generateModel() method provided by the Migrations plugin. It is a shorthand allowing you to cleanly load a model in the callback to insert new data or update the existing. We could explain the reason of it more deeply but it is not the goal of this article, so just keep in mind that it is the best way to load a Model from callbacks! Here we are! We can now share the application with anyone. After checked out the application, one will just have to run cake migration all to turn an empty database to a database containing all the needed tables, and an initial admin user to start using the application.

Categorize the recipes!

As the application evolves, we need to sort recipes by categories. This change involves two changes in the current database schema: a new categories table must be created, and a category_id field added to the recipes table. Note: If you later want to use the migrations diff feature to generate a migration containing a diff between your previous database schema and the current one, you have to generate a Cake Schema of your database at this point. Simply run cake schema generate. We can now update the recipes table and create a new categories table. Here is a simple SQL script: CREATE TABLE `categories` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; ALTER TABLE `recipes` ADD `category_id` INT NOT NULL Bake the MVC for categories and update recipes view pages to display the category so the application reflect these database changes. Before sharing these code changes, we need to generate a second migration describing the above SQL snippet in an agnostic way... and creating initial categories! Nothing different than what we did previously: run cake migration generate, give a name to the migration, and choose between generating a diff from the schema.php file (if one was generated), generating a dump of the database (we will remove unnecessary instructions later) or generating an empty migration file. Once generated, it is always important to check the generated directives for the migration and fix them if needed. The migration must look like this: var $migration = array( 'up' => array( 'create_table' => array( 'categories' => array( 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100), 'indexes' => array( 'PRIMARY' => array('column' => 'id', 'unique' => 1), ), 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'MyISAM'), ), ), 'create_field' => array( 'recipes' => array( 'category_id' => array('type' => 'integer', 'null' => false, 'default' => NULL) ), ), ), 'down' => array( 'drop_table' => array( 'categories' ), 'drop_field' => array( 'recipes' => array( 'category_id' ), ), ), ); If you understood what we did in the first migration callback to add an initial user you might be able to implement this one. We would like to add initial categories: Starters, Main Dish and Desserts. For lazy people, the code is here: function after($direction) { if ($direction === 'up') { $Category = $this->generateModel('Category'); $categories = array( array('name' => 'Starters'), array('name' => 'Main Dish'), array('name' => 'Desserts')); $Category->saveAll($categories); } return true; } Here we are again! The changes are ready to commit, and the commit will contains both code and database changes. One could update the database after checking out this commit by running: cake migration all.

The end

I hope this very simple use case and the code we built will help you to start using Migrations. As you could see it is very simple to use and will make your life much more easier: you would not have to worry anymore about the state of your database schema. The source code of this tutorial is available on Github. If you found any bug or have any suggestion about the Migrations plugin, please create a ticket on Github. Comment this article if you have any question, and do not hesitate to share it if you found it useful!

File uploading, file storage and CakeP...

This article includes how to upload and store files, because I've seen a lot of discussion about that too, but if you're just interested in how to use the MediaView class scroll down.

Handling file uploads in CakePHP

First let's start with the required form, to create a file upload form all you have to do is this: echo $form->create('Media', array('action' => 'upload', 'type' => 'file')); echo $form->file('file'); echo $form->submit(__('Upload', true));   The "type" in the options of Form::create() takes post, get or file. To configure the form for file uploading it has to be set to file which will render the form as a multipart/form-data form. When you submit the form now, you'll get data like this in $this->data of your controller: Array ( [Media] => Array ( [file] => Array ( [name] => cake.jpg [type] => image/jpeg [tmp_name] => /tmp/hp1083.tmp [error] => 0 [size] => 24530 ) ) ) Ok, now the big question with a simple answer is where the file data should be processed, guess where. Right – in the model because it's data to deal with and validation to do against it. Because it's a recurring task to upload files I suggest you to write a behaviour for it or convert your existing component to a behaviour. If you keep it generic you can extend it with a CsvUpload, VideoUpload or ImageUpload behaviour to process the file directly after its upload or do special stuff with it, like resizing the image or parsing the csv file and store its data in a (associated) model. We're not going to show you our own code here for obvious reasons, but I'll give you a few hints what you can or should do inside of the behavior:
  1. Validate the uploaded field, the field itself contains already an error code if something was wrong with the upload. Here is a link to the php manual page that shows you the list of the errors that you can get from the form data. http://www.php.net/manual/en/features.file-upload.errors.php
  2. Validate the uploaded file, is it really the kind of file you want and does it really contain the data structure you want?
  3. Check if the target destination of the file is writeable, create directories, whatever is needed and error handling for it, I suggest you to use CakePHP's File and Folder classes for that.
  4. Add a callback like beforeFileSave() and afterFileSave() to allow possible extending behaviors to use them.

Database vs file system storage

Feel free to skip that part if you already store the files in the file system. Storing files in the database is in nearly all cases a bad solution because when you get the file it has to go its way through the database connection, which can, specially on servers that are not in the same network, cause performance problems. Advantages of storage in the file system:
  1. Easy and direct file access, to parse them (csv, xml...) or manipulate them (images)
  2. You don't need to install any additional software to manage them
  3. Easy to move and mount on other machines
  4. Smaller then stored in a DB
The suggested solution is to store meta data of the file like size, hash, maybe path and other related info in a DB table and save the file in the file system. Some people come up with the security and want to store a file because of that in the database which is wrong. You should not store the file in a public accessible directory like the webroot of the application. Store it in another location like APP/media. You control the access to the file by checking the permissions against the DB records of your meta data and sending it by using the CakePHP MediaView class, I'll explain later how to use it. I don't say that storage of files inside the DB is in general a bad idea but for web based applications it is in nearly every case a bad idea.

File system Performance

A bottleneck in the long run on every file system is a large amount of files in a single directory. Imagine just 10.000 users and each has an individual avatar image. Further ext3 for example is limited to 32000 sub folders, other file systems have maybe similar restrictions. You can find a list of file system limitations here: http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits To avoid performance problems caused by that you should store your files in a pseudo-random directory structure like APP/media/32/a5/3n/. This will also allow you to easily mount some of the semi-random created directories on another machine in the case you run out of disk space. /** * Builds a semi random path based on the id to avoid having thousands of files * or directories in one directory. This would result in a slowdown on most file systems. * * Works up to 5 level deep * * @see http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits * @param mixed $string * @param integer $level * @return mixed * @access protected */ protected function _randomPath($string, $level = 3) { if (!$string) { throw new Exception(__('First argument is not a string!', true)); } $string = crc32($string); $decrement = 0; $path = null; for ($i = 0; $i < $level; $i++) { $decrement = $decrement -2; $path .= sprintf("%02d" . DS, substr('000000' . $string, $decrement, 2)); } return $path; } You should also know that php running in safe mode does not allow you to create more then one directory deep in one call. You have to take this in consideration, the above function does not cover that because safe mode is basically deprecated and will be also removed in php6

Sending a file to the client – or the unknown MediaView class

From what I've seen in the ruins of outsourced projects that asked us for rescue and also in the CakePHP googlegroup I think not many people are aware that CakePHP has a view that is thought to be used for downloads and display (images, text...) of files. It's called the MediaView class. I'll now explain you how to use this class to send files to the client. /** * Sends a file to the client * * @param string $id UUID * @access public */ public function download($id = null) { $this->Media->recursive = -1; $media = $this->Media->read(null, $id); if (empty($media)) { $this->redirect('/', 404, true); } $this->set('cache', '3 days'); $this->set('download', true); $this->set('name', $media['Media']['slug']); $this->set('id', $media['Media']['filename']); $this->set('path', APP . 'media' . DS . $media['Media']['path']); $this->set('modified', $media['Media']['modified']); $this->set('mimeType', $media['Media']['mime_type']); $this->set('extension', $media['Media']['extension']); $this->view = 'Media'; $this->autoLayout = false; if ($this->render() !== false) { $this->Media->updateAll( array('Media.downloads' => 'Media.downloads + 1'), array('Media.id' => $id)); } } You simply have to set autoLayout to false and the view class to media. $this->view = 'Media'; $this->autoLayout = false; There are a few view variables to set to “configure” the file download or display. To control if you want to make the client downloading the file or to display it, in the case of images for example, you simply set 'download' to true or false; $this->set('download', true); You can control the browser caching of the file by setting cache. Please not that you do not have to use caching if download is set to true! Downloads do not need caching. $this->set('cache', '3 days'); The next part might be a little confusing, you have “id” and “name”. Id is the actual file on your server you want to send while name is the filename under which you want to send the file to the client. “path” is the path to the file on the server. $this->set('name', $media['Media']['slug']); $this->set('id', $media['Media']['filename']); $this->set('path', APP . 'media' . DS . $media['Media']['path']); If you want to send a mime type that does not already in the MediaView class you can set it. $this->set('mimeType', $media['Media']['mime_type']); If you don't set it, the class will try to determine the mime type by the extension. $this->set('extension', $media['Media']['extension']); Note that you have to set the extension to make it work and that the extension is attached to the filename! If you store the filename with an extension you have to break it up. When everything is set you can check if render() was successfully and do whatever you want after that, for example count the download. if ($this->render() !== false) { $this->Media->updateAll( array('Media.downloads' => 'Media.downloads + 1'), array('Media.id' => $id)); }

 

Closing words

I hope you enjoyed reading the article and it helped you improving your knowledge about CakePHP. Feel free to ask further questions by using the comment functionality. Have fun coding!

Felix Geisendörfer - Javascript and Git

Felix gave a demonstration of the production level javascript separation and management that the team at Debuggable use in order to minimise the amount of Javasript that needs to be sent to the client for any specific page view, and to ensure the logic is separated into the pages that it is used for. This creates a better management system for Javascript than using a single file. In addition to this separation, Felix gave an overview of common practices and operations for using Git for version control in a day to day environment. This included: merges, conflict resolution, fast forwarding branches, and managing multiple repositories. Largely this presentation was an interactive one, and to gain the most out of it, you really needed to be there.

Marius Wilms - The CakePHP Media Plugin

If Marius had more than an hour to talk about the Media Plugin, he most certainly would have taken it. To go over the features and functionality of the entire plugin would have been many hours as there is a lot there. A brief touch on the features provided by the plugin was discussed, with some examples. Requirements are in the high end, but considering the state of PHP and the upcoming version of CakePHP, developers should be moving forward in terms of their PHP version and library support anyway. The Media plugin requires CakePHP 1.2.x.x and PHP 5.2.0+. It enables the transfer, manipulation and embedding of files in many varied ways. You can find the media plugin at: http://github.com/davidpersson/media Marius' focus was on doing media manipulation and embedding "properly", and identified that while there are lots of user contributions floating around the net, none of them were meeting his needs and were flexible enough. One of the main points he made here was that if done incorrectly, potential security risks arise due to command line interaction and file saving. Validation was one particular section of the code that made this a tricky plugin to develop, but allowed tests to be implemented to ensure security. Some common points that we hear all the time came through, and they make sense for CakePHP as well as any web application for security reasons:  

  1. Don't trust users supplied filenames
  2. Don't store files in an accessible webroot, rather have them accessible to scripts.
  3. Make the upload location (and local filenames) unguessable (like referencing files by UUIDs)
The media plugin contains about 8 new rules for file validation purposes to ensure that submitted data meets the application needs. Beyond validation, it handles all kinds of uploads, HTTP Post, Remote HTTP and local file inclusion.
A console is included to initialize the default directory structure, and as such, could be included as part of a deployment script with the CakePHP console.examples.
To ensure flexibility of use, a behavior is included to allow attachment to any number of models, and generioc storage and linking provided to ease integration into existing apps.
Marius concluded his talk with a plea for feedback. There are plenty of people using the plugin, but more feedback is required to ensure its the best it can be, and that all bugs  (if any) are squashed. Checkout the code at: http://github.com/davidpersson/media

Robert Scherer - Multi-Tenancy in CakePHP

Robert's talk was unscheduled, but ended up being a great case study for an insurance sales white-labelling solution that his company had undertaken and completed. Robert talked about multi-tenancy, and what this means for a web application, and how it relates to SaaS architecture. Challenges to be solved included:

  1. Differences in functionality
  2. Workflow differences
  3. Separation and security of data
  4. Branding and visual differences
Auth and Acl Components were used to solve a lot of the problems described, but in addition, Robert discussed the development of Modules as a new addon / plugin structure that allowed the addition, removal or configuration of application items at any level (Model, View or Controller).
Configuration of the modules was broken up into system default, mandators, and dealers configuration, allowing for inheritance of options along the way. To solve the view specific differences, built in themes were used to provide the differences required. This is a CakePHP builtin mechanism that serviced their needs well. Much of Robert's talk went through visuals of the site itself, and should we be able to get our hands on these, will post them up to see the various differences in presentation, and the module structure in terms of MVC.

Neil Crookes - Bake Master Class

After an introduction to bake, and what this shell means within CakePHP, Neil went on to explain and show examples of the code generation templates and capabilities provided by default. The bake shell is broken down into tasks and a main shell. These tasks separate out the logic required for various main task subsets including controller, model and view baking, amongst others. The main bake shell is found in the CakePHP directory cake/console/libs/bake.php. Tasks used by this shell are defined in the $tasks variable. Bake extends the CakeShell class and executes calls based on whether the users want interactive or non-interactive tasks through the __interactive() and bake() methods respectively. Neil made the suggestion that a persistent MySQL connection might be a good idea to stop database connection timeouts. Following this introduction, a great walkthrough of customisation of the bake process and templates was demonstrated. This included the addition of a new Shell that allows for multiple bakes to be done automatically of the same type. Neil has been kind enough to host the code, and you can find this over at the CakeFest downloads page.

Felix Geisendörfer - Recipies for succ...

Felix liked to Get Things Done™. And through experience and what became an interactive idea and experience sharing productivity session, he explained mechanisms and methods that he has used to achieve the best results for projects in the shortest time possible. Communication. While this means your team should be in contact, and that those contact points should be quality communication, it doesn't mean that instant communication is a requirement. Communication mechanisms would ideally be: decentralised and work in an offline capacity (at least for partial functionality). Return on investment is an interesting statistic to consider when responding to or creating a new item of communication. Provide a concise message. Enough to ensure the intention of the communication is clear, and ambiguity is reduced if not eliminated. Email is a great tool, especially for the following: Timezone differences, announcements, spawning debates that require discussion, emergency notifications / reports, mailing lists, shared email accounts and automated reporting / information. Using Email over an instant messaging mechanism for spawning debates allows contributors to formulate a constructed response. This can assist the better understanding of some ones input to the discussions, as instant messaging can be difficult for items that require discussion. Instant messaging has good and bad traits:

  1. Good Stuff
    1. Instant
    2. Group Chats / conference calls
    3. Varying methods of communication
    4. Various formats (text, voice, video)
  2. Bad Stuff
    1. Distracting (interrupts workflow)
    2. History tracking / compatibility
    3. Citations / logs
Task management helps keep projects on track. However the truth is that there is no overall solution. We do the best we can to manage all the information we need to be successful, through a variety of tools.
Problems that exist are:
  1. Getting tasks into the system (May be the laziness of users)
  2. Tracking tasks that manage to make it into the system
  3. Getting those tasks done
Tools available:
  1. Pen and Paper (plain text files)
  2. OmniFocus (Mac Only)
  3. Lighthouse / TheChaw
One of the CakeFest attendees suggested post-it notes on a wall, so that the tam involved in pursuing the tasks can have some physical interaction with them, making the experience more productive and fun.
So with this in mind, Felix quickly went over what has worked for him and his company:
  1. Check emails twice a day only.
  2. Turn off instant messaging tools in the morning
  3. Set clear distinct goals for the day, and achieve those goals
How to fail at unit testing. Felix described some common myths about unit testing, how he feels failing is possible and how to improve your approach.
Failed unit testing can come from factors such as: Attempting to reach 100% code coverage all the time, misunderstanding test driven development and expecting that all developers / users can write unit tests.
Success can be achieved by approaching projects with a top-down approach, incorporating performance tests and re-factoring code. Continuous integration was presented in a basic format, going through the setup that Felix has found useful, making use of git post-receive hooks, and parsing of results to send notifications when necessary. In terms of increasing productivity and performance overall, Felix has begun testing Pair programming, where there is one computer for two people, and the development process is discussed ongoing, meaning that a lot of bugs an individual would miss are caught by the secondary developer on the first code pass. Virtualization though open source and free products like VirtualBox and VMWare Server are worthwhile investigating for cheap scaling testing during development.
Version control was introduced, referencing the change of CakePHP to git from subversion, pointing out the benefits including: decentralization, can work offline, can work semi-online though the use of adhoc networks, its very fast to operate and its more intelligent with the storage of information. Following this, a detailed example of three separate working repositories was demonstrated.

Joël Perras - Demystifying Webservices...

Joël's presentation on Web Services and CakePHP identifies important and interesting points that really demystify both implementation of datasources, and what web services mean for developers trying to take advantages of their offerings. A Web Service is a defined interface. The interface is made known and public, however the implementation may not be known (and its not really important). The developer should be interested in the data supply and the data returned from the web service. Various mechanisms are available for communicating with a web service. Such as: RPC, SOA, REST and more. Much of this presentation covered best practices, better practices, and why people tend to make decisions like implementing components when they really want datasources, as well as implementing datasources, and going about the implementation the wrong way. In the case of web services datasources implementation, curl is presented as a good example of something that works, but a better solution is available through the use of HttpSocket. HttpSocket being one of the CakePHP core libraries provided, allowing a complete implementation of Http communication, extending the CakeSocket class. Authentication and Authorization options were presented, with specific reference to OpenID and OAuth. Authentication and Authorzation are part of the application flow graph. This means implementation should be at the controller level, and in terms of implementing easily managed pluggable sections of code in cakephp converntions, this means a component. Data Sources are the closest layer to the actual data. Correct implementation of a data source will allow models to connect and communicate in a transparent fashion, meaning easy access to data in a standard way. The basics of a datasource should implement the following: __construct, listSources, describe, create, read, update, delete as well as defining $_schema. Some great datasource examples can be seen in the core. When implementing a datasource, to ensure maximum use and compatibility, try to make use of CakePHP libraries such as HttpSocket in the place of curl. Google Charts was presented as a good example of what should not be implemented as a datasource. The data in this instance is handed by some other data source, and the formatted chart request is sent with an image response supplied. This is more appropriate for a helper than a datasource. Joël mentioned that he has a partial google charts helper that he would be willing to share if someone asked.

Garret Woodworth - CakePHP then, now a...

Beginning with an overview of the CakePHP project, changes and evolution of direction and development team members, Garret provided a great overview of where the project stands, and how it has grown to be as successful as it has today. Garret gave a great description of the types of participation that are seen in open source teams, and these are relevant to CakePHP. He also described the attributes that make a good team member in such projects. Contribution Levels:  

  1. No effort (tickets are subimitted with little explanation)
  2. Some effort (well explain the ticket, and have attempted to reproduce the issue to confirm it)
  3. Attempted effort ("Some effort" with patch)
  4. Good Effort ("Some effort" with test case)
  5. Ultimate effort ("Some effort" with test case and patch)
Good team member attributes:
  1. Communicate often.
    1. To keep people motivated and interested on working for / with a project, its important to talk about what they want to work on, and what they feel they can assign some of their time to. Developing for open source shouldn't feel like "work".
  2. Show diffs of code, and get feedback to ensure the quality of work overall for the project is as high as it can be.
  3. Think longer about the problems faced, and as a result, write code faster.
  4. Details, Details, Details.
  5. Give back to the project more than you take from it.
  6. Think outside the box, and be creative.
CakePHP is growing, and the stats presented spoke for themselves, with America, Japan, India, France and Germany being the top countries at the moment in terms of hits on the CakePHP websites at the moment. This is resulting in 24% unique new visitors per month. A statistics that is truly extraordinary.   With the feature development and more developers available to the CakePHP Core Development Team, git has been implemented widely and is the future of version control for source code for the CakePHP project. This should ease feature development, and remove some of the pain associated with merging with Subversion. Announcements! Garret announced new versions of CakePHP, currently being actively developed by the CakePHP core development team. Version 1.3 is a Step up with several enhancements over 1.2. Most notably Bake, Session, Javascript changes, Inflector and some library renames. Deprecated methods were also removed. There is even a wiki page describing migration steps from 1.2, to help ease the transition. CakePHP 2.0 was also announced. This is a huge move, stepping forward to drop PHP4 support, and move towards PHP5 Strict compliance, and much better Object Orientation and performance throughout. This new version is in active development,  but does not yet have a stable release for download. code.cakephp.org was launched at the time of the Keynote, and is designed to consolidate systems. it's running on thechaw.com code, and uses git for the main projects. Its available now for everyone to use. Closing things up, Garrett urged the community to "get involved". CakePHP isn't where it is today without the extensive help and support of the community. There are a number of ways that you can contribute, and he mentioned the following in particular:  
  1. Interact with the community and the core developers.
  2. Get interested in Bakery 2.0 which is currently under development
  3. Plugins and Plugin Server
  4. Forks
  5. Join #cakephp-bakery on the IRC server
 

Benchmarking requestAction

Now there has been a lot of discussion in the past few months about requestAction() and how it can very easily create a negative impact on your application. In fact I even wrote such an article myself. However, its high time that someone did the number crunching to really see if requestAction() is actually as slow as we all seem to think it is. So onto the testing method and the results.

Testing method

To test this theory I used a small CakePHP application and the SVN head (revision 8064) of CakePHP. I used a simple sample application with 2 controllers and 2 models. My model method directly returned the results without touching the database, so that database retrieval time and model processing would not be a factor in these tests. As I was only interested in the performance implications inherent in requestAction() itself, I wanted to remove the variance created by connecting to a database. I set debug = 0, and used basic file caching. After warming up the cake core caches, I tested 4 different controller actions.
  • Using Relations / ClassRegistry::init() - The method I originally proposed, and often touted as the 'best' solution to requestAction()
  • Using RequestAction with a string URL
  • Using RequestAction with and Array URL
  • Using a cached RequestAction - This more accurately simulates how we use requestAction at CakeDC.
Benchmarks were generated with Siege I used 10 concurrent users with 110 reps each. My local development web-server is running Apache 2.2/PHP 5.2.6 o n a 2.6GHz Core 2 Duo iMac with 2GB of ram. I ran each test 3 times and took the best result of each. Using model relations / ClassRegistry::init() First up was my originally proposed solution of using model relations to access the correct information. I used the following command and got the following results. siege -b http://localhost/benchmark/posts/using_relations Transactions: 1100 hits Availability: 100.00 % Elapsed time: 63.21 secs Data transferred: 1.50 MB Response time: 0.55 secs Transaction rate: 17.40 trans/sec Throughput: 0.02 MB/sec Concurrency: 9.60 Successful transactions: 1100 Failed transactions: 0 Longest transaction: 1.76 Shortest transaction: 0.10 Using RequestAction with a string URL Up next was using request action with a string url. String URL's are often the slower way to perform a requestAction as parsing the URL string is one of the more expensive operations in request dispatching. I used the following command and the best results were. siege -b http://localhost/benchmark/posts/using_requestaction Transactions: 1100 hits Availability: 100.00 % Elapsed time: 64.60 secs Data transferred: 1.51 MB Response time: 0.57 secs Transaction rate: 17.03 trans/sec Throughput: 0.02 MB/sec Concurrency: 9.72 Successful transactions: 1100 Failed transactions: 0 Longest transaction: 1.76 Shortest transaction: 0.11 RequestAction with an Array URL Up next is requestAction() witn an array url. Using an array URL is supposed to expedite the dispatching process as it bypasses much of the parameter parsing done by Router. This theory turned out to be true, as Array URL's clocked in marginally faster than their string counterparts. siege -b http://localhost/benchmark/posts/using_requestaction_array Transactions: 1100 hits Availability: 100.00 % Elapsed time: 64.08 secs Data transferred: 1.53 MB Response time: 0.57 secs Transaction rate: 17.17 trans/sec Throughput: 0.02 MB/sec Concurrency: 9.78 Successful transactions: 1100 Failed transactions: 0 Longest transaction: 1.66 Shortest transaction: 0.11 RequestAction using Array URL's and Caching In my mind this was going to be the most performant requestAction option, due to the cached nature. The results were as expected with this method clocking to be only slightly behind the relation call. It is important to note as well, that this test does not reflect the time savings earned from not having to make an additional query/ round of result parsing. In a real world situation, the savings of using a cached element would be magnified by the cost of the query. siege -b http://localhost/benchmark/posts/using_cached_requestaction Transactions: 1100 hits Availability: 100.00 % Elapsed time: 63.60 secs Data transferred: 1.52 MB Response time: 0.56 secs Transaction rate: 17.30 trans/sec Throughput: 0.02 MB/sec Concurrency: 9.62 Successful transactions: 1100 Failed transactions: 0 Longest transaction: 1.77 Shortest transaction: 0.09 Results Summary In case you quickly scanned through the full results here is a summary of what happened.
Method Requests per second (mean) Total time taken (seconds)
Using relations/ClassRegistry::init() 17.40 63.21
Using requestAction and string urls 17.03 64.60
Using requestAction and array urls 17.17 64.08
Using cached requestaction 17.30 63.60
In closing requestAction() can be slower than a direct method call. There are some benefits to using requestAction though.
  • You have the opportunity to reduce the number of repeated lines of code by putting the requestAction inside the element. In doing so, you create an encapsulated element, that can be included anywhere without having to worry about having the correct method calls in your controller.
  • You can more easily cache the element. By using requestAction in conjunction with element caching you have an easy to use, simple to implement caching. Getting the same results with model method calls in your controller requires additional caching logic in your models.
  • The potential for increased performance. As we saw in the benchmarks above, a cached element performed almost as fast as the direct method call. This margin will grow when a database query is added into the mix.
Now am I retracting my previous stance on requestAction? No, I still feel that there are many situations where requestAction is the incorrect solution and signals poor application design. However, when the need arises it is good to know that requestAction can be as fast or faster than other approaches when implemented properly.  

We Bake with CakePHP