Callbacks in CakePHP forms using events

If you ever have the need to inject fields or other things into a form, for example when building a blog or CMS system, you can use the event system that comes with CakePHP. The basic concept can be used every where within the views and is not limited to forms only, but a form is a good example.

You can find the complete sample as working application in the cake2-form-events branch of my Github cakephp-sample repository.

Please note that this sample code is using php 5.4 specific notations.

The form in a view file is dispatching two events, the View.Form.someFormStart and View.Form.someFormEnd event. The places where these callbacks are fired are the places where custom fields or other output is going to be injected by the triggered event listener callbacks.

The event listener class that will inject something to your form events.  I would not write a lot view code in the event listener itself, it is just done for demonstration purpose in this sample, I would always put the additional things I would like to display in an element and only echo that element from the event, to keep the view pieces it will inject separated from the event listener to respect separation of concerns.

You can put your event listeners into App/Event for example. After you’ve created your event listener and saved it to App/Event/FormListener.php you have to attach your listener to the CakeEventManager. In your App/Config/bootstrap.php add this to attach the listener to the global event manager.

The additional_fields element will output the user we fetched in the first event for demonstration purpose to give you an idea of what is possible and how it works and data is made available between the different steps.

I hope you enjoyed the article and learned something. Any kind of critic to improve it is welcome!

The Imagine plugin

The Imagine plugin for CakePHP, which has been available from the CakeDC account on GitHub, was written as a separate plugin, but complementary to my FileStorage plugin.

In the past, the good old CakePHP 1.3 times, we’ve had our Media plugin at the CakeDC which we never released. The main purpose of the plugin was to deal with file uploads, but only to the local system and to process images.

I’ve had to work with a few client projects that used phpthumb, which I’ve learned to dislike because it had its faults and issues. Before reinventing the wheel I simply tried to ask if somebody knows about something modern, OOP, PHP 5+, with unit tests, and had some luck.

Imagine is a modern PHP 5.3 library that provides an interface to different image processing back-ends like gd, imagick and imagick shell. Others can be implemented as well. I really recommend this lib over phpthumb, and suggest you to replace it with Imagine if your project is moving on.

The plugin for CakePHP is basically a wrapper around the Imagine library, that will autoload the library (because CakePHP 2.0 does not have it’s own autoloader until 3.0) and make it available as a behavior. The behavior provides some methods that handle commonly used image operations, like cropping and thumbnails.

Besides that, the plugin comes with a helper and a component, which allows it to generate versions of images on the fly. This was more done for backward compatibility for some apps, rather than being a concept that should be used. In fact, it is not the recommended way to handle this. I’ve written two other articles related to image processing and file storage that explain the issues with that.

Instead of creating images on the fly, which is just putting load on the server all the time, you should create the versions of an image after upload. The Imagine plugin works together with the FileStorage plugin, which can use Imagine to generate whatever images you want, right after upload.

I originally contributed this plugin, which I developed in my own free time, to CakeDC. But now, it is finally going back to my GitHub account, to pull some maintenance work away from the company. I’ll continue to maintain it, and hope you take some time to check it out, and contribute if possible.

File uploading, file storage and CakePHPs MediaView class (CakePHP 1.3)

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:

 

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:

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.

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.

You simply have to set autoLayout to false and the view class to media.

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;

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.

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.

If you want to send a mime type that does not already in the MediaView class you can set it.

If you don’t set it, the class will try to determine the mime type by the 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.

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!