CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

CakePHP Migrations plugin: easily vers...

This article is a quick introduction to the Migrations plugin, open sourced a few weeks ago by our company. You will see how simple it is to use the plugin and what you could do with it. I hope this article will show you the benefits of using migrations in your CakePHP applications and make you give it a try right after the reading! Here is a one-sentence description of the plugin: the Migrations plugin allows developers to easily version and automate the creation / update process of any database schema and application data from the command line. For information, CakeDC uses this plugin on its project since several years to make team collaboration and deployment easier. The plugin has been entirely rewritten a few months ago and fully tested (code coverage >95% as always at CakeDC) before being open sourced under the MIT license. It is now available to the community along with its documentation... and it is free!

Why is it useful?

It has been a while since companies integrated Source Code Management in their development process and CVS, SVN, Mercurial or Git are now common tools. Inspired from the open source movement it is also a good practice for single developers to version application source code. As you might know, an application almost always depends of the database schema it is aimed to use... however it is not easy to version both the source code and database schema with a SCM. Let's take the example of a CakePHP application: until now the only way to do was to version a single file, either a sql dump or a CakePHP schema.php file generated with the cake schema shell. These two approaches are not very convenient to use on a daily basis, the first one forcing the developer to drop and recreate the whole database every time! Moreover, a web application development is never really finished (there are always new features to add, software updates or bug fixing to do...) and deploying these change on a test or production server is always a delicate task. Here comes the Migrations plugin! It provides a simple and easy way to version a database... and to perform many other different tasks thanks to its callback system. Here are some features:
  • keep a local database schema up-to-date: you just have to run all non applied migrations to update the local database schema to the latest version
  • make team work easier: when several developers work on the same application it is important that all of them work with the same database schema during all the development cycle. With migrations every commit is tied to the database schema at this precise instant, which makes easy switching branches and resetting a branch to a specific commit.
  • make installation and updates easier: ready to push the new version of your application live? You will only have to push the sources on the server and run all non applied migrations!
  • migrate more than database schema: the callback system allows you to do everything you want before (or after) applying (or reverting) each migration. Here are some examples: creating an initial admin account, add initial or test data to the application (lorem ipsums, categories, content...), update values from the database, send an email if debug > 0... The only limit may be your imagination ;)

Where can I find the code?

Announced a few weeks ago, a packaged version of the plugin can be downloaded from the new "Downloads" section of CakeDC.com. This page contains a link to download the 1.0 version, the plugin documentation and the Codaset project url for tickets and direct Git access to the repository. To make people aware of the need to show their support to the Cake Software Fundation by donating a few bucks (this is unfortunately not done enough), the plugin was first available to donors only. The "Download without donation" button was added later, when the repository was made public! However, if you find this plugin useful please consider making a donation to the CSF... that is the best thing you could do for thanking us. Click here to lend your support to: cakephp1x and make a donation at www.pledgie.com ! Even better! A sample application was also released for those who want to see how migrations could be used and integrated in an application. To play with it, Download the code or git clone the project using: git clone git://codaset.com/cakedc/sample-migrations-application.git sample_migrations You will only need to create a database.php configuration file and update CakePHP's core location to make the application work. Git users, run git submodule init git submodule update to automatically add the migrations plugin as a submodule!

What do I need to use it in my application?

Note: the packaged plugin is for the CakePHP 1.3 version only. You can either download the 1.3-beta package of the framework, or use the 1.2 branch available in the Git repository. Adding the plugin to an existing application is very simple. If you downloaded the archive containing the plugin code, unzip it in the "/plugins/migrations" folder of your application. Git users can add it as a submodule with the following command: git submodule add git://codaset.com/cakedc/migrations.git plugins/migrations To check that it is installed correctly, execute the following command from your application root (it will display the available command to use the plugin): cake migration help If you encounter any problem here, please read the official documentation about CakePHP's console usage.

How does it work?

This post is not aimed at providing a comprehensive tutorial on how to use the plugin, thus I will just introduce the most useful commands along with some use cases. For a complete documentation, please read the official documentation provided on the plugin page. For a simple (but useful for understanding purpose) use case you can take a look at the sample application introduced above. Going through the commit history will allow you to understand how migrations could be used in a development process.

Create a migration

To generate a new migration, type the following command cake migration generate The tool will ask you to give a name to the migration and suggest to do a dump of the current database schema. If a "schema.php" file is found in the application, it will ask you if you want to generate a diff between this schema and your current database one. Generated migration files will be added to the "/config/migrations" application directory.

Apply / Revert migrations

When you pull an application containing migrations, several commands are available to apply or revert migrations. The simplest one is: cake migration It will display all the found migrations along with their status (applied or not applied) and id number. Just enter a migration number to update your database to the correct version. Some convenience commands are also available. You can use: cake migration up, down, all or reset These commands will respectively:
  • apply the next migration
  • revert the latest applied migration
  • apply all non applied migrations (and thus update the schema to the most recent version)
  • revert all applied migrations (and empty the database)

Migrations for plugins

Adding plugins to an existing application often implies adding new tables to the database or altering existing ones. The Migrations plugin brings a quick and efficient way to automate this installation. On the one hand developers can easily add necessary migrations to their plugin (making upgrades easier), on the other hand users can apply them as easily. The only difference compared with commands introduced above is the parameter "-plugin pluginname" that needs to be added. Here is how the user will install the database for the newly added / updated plugin "test": cake migration run all -plugin test I would like to highlight the fact that callbacks allow the developer to do everything they want before / after each migration. It is convenient for adding initial data, and one can even implement a callback method opening the bootstrap.php file to append plugin's configuration entries there (it is just an example ;)).

... going further

Of course, feel free to add any remark or example of migrations use in the comments. As this post is not aimed at providing support for the plugin, I recommend you to use the official tools available:
  • If you found a bug or want to suggest enhancements: open a ticket!
  • An installation problem or a question about the plugin usage? Ask your question to the community on CakeQs!
  • You would like a custom version of this plugin, or professional related services... contact us, it is our job ;)
I hope you enjoyed this post, it is now time for you to start playing with the Migrations plugin...

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. http://api.cakephp.org/class/media-view 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.

We Bake with CakePHP