PaulMarshall

Programming

Migrating from CakePHP 1.3 to CakePHP 2.0

Posted by Paul Marshall on Sun, Aug 14 2011 11:20:00

With the recent release of CakePHP 2.0 Beta, I decided to upgrade ActionTracks to CakePHP 2.0. I thought this would be a popular topic so I am adding this article to document the process. The Cake console has and upgrade utility that will handle several of these tasks for you. I had already started the process when I discovered this utility. I did use it to do some find and replaces on things like helpers. While the utility is a great addition, some people may not be comfortable automating that much. While there are a lot of very cool new features in CakePHP 2.0, which I will be covering in my overview, this article is focused on what you need to do to get your CakePHP 1.3 application upgraded to CakePHP 2.0.

Installing CakePHP 2.0

This is pretty straight forward. Install Cake per the instructions. There isn't much difference between installing CakePHP 2.0 and previous versions. Unzip the downloaded file in your desired location.

Configure CakePHP

core.php

You should start with default core.php file. Update the configuration values to match those in the app you are migrating. Make sure you copy the Security.salt and Security.cipher_seed from your old core.php or you won't be able to login in addition to other possible security issues. Copy any custom configuration values you have added to your old core.php. Take notice of how errors, sessions, and caching has changed.

database.php

The database configuration has only a minor change in how you designate the database engine. Previously, you used this:

'driver' => 'mysqli',

now, you use this:

'datasource' => 'Database/Mysql',

routes.php

The Route file has been changes slightly. Cake's default routes are now optionally loaded at the end of the file. If you do not want to use Cake's routes, then you can comment this line. In addition, CakePHP 2.0 now has much better handling of plugins. One of those features is automatically loading routes for plugins. You can place your custom routes before or after CakePlugin::routes(), depending on whether you want your custom routes to be matched before or after plugin routes.

bootstrap.php

Other than loading plugins, the bootstrap file hasn't changed much. Copy any functions, constants, or other code from your old bootstrap into the new one.

Copy/Rebake Files

The directory structure for CakePHP 2.0 is a bit different from previous versions. Most notably, most files and folders are now CamelCased instead of lower_underscored. My understanding is that this was done so that files and directory names matched their class names. This should also provide a performance boost as file and folder names will not have to be run through the Inflector Class. As a result, you will either need to rebake your files and then copy code from your old files. While this might be the best bet if you are using a lot of baked controllers and views; for most, this would create a lot of work. Instead, I decided to simply copy all of my old files into their respective directories in the new app. I then went through and renamed the models, controllers, and component files to match the class name. Your users controller called users_controller.php becomes UsersController.php. While you need to rename the View folders to be CamelCased, the view files keep their naming conventions so that they match the action name.

Move App Files

The parent app_ classes have been moved and renamed as well. Instead of app_controller.php, app_model.php, and app_helper.php now become Controller/AppController.php, Model/AppModel.php, and View/Helper/AppHelper.php.

Copy Email Elements

Email elements have been moved from views/elements to View/Emails.

 

Ok, now let's make things work! At this point, I was actually getting a login screen. I couldn't login however. I disabled the Auth Component temporarily to see what worked. Surprisingly, most things were functioning and rendering ok.

Auth Component

The Auth Component has gone through quite a bit of changes and now boasts some very cool features. Auth now supports multiple authorization adapters that can all be used together. No longer are you limited to using actions, controllers, or crud as the sole method of authorization. Not only can you create custom adapters, but you can also use multiple adapters together. This will be incredibly powerful! As a result of these new features, the configuration of the Auth Component has changed. You will need to update this:

    $this->Auth->authorize = 'actions';

    $this->Auth->actionPath = 'controllers/';

    $this->Auth->flashElement = 'flash/error';

to this:

    $this->Auth->authorize = array(
        'Actions' => array(
            'actionPath' => 'controllers'
        )
    );
    $this->Auth->flash = array(
        'element' => 'flash/error',
        'key' => 'auth',
        'params' => array()
    );

 

Request Data

CakePHP 2.0 has consolidated some request related classes into the Request Class. This changes how you get data from forms and passed from other methods.

The Request Object does not have form element any longer. You will need to replace $this->params['form'] with $this->request['data'] or $this->request->data. In addition, $this->data needs to be replaced with $this->request->data.

Helpers

The Ajax, Javascript, and Xml helpers have been removed. You will need to replace these helper calls with appropriate alternatives. In addition, the old style of calling helpers in no longer supported. Helpers can no longer be called with $helper->function(). You need to update your helper calls to use $this->Helper->function(). I did run the utility to update the helper calls. I did notice, however, that it did not update the Paginator calls. I had to do that with a custom find and replace.

__() Function

The __() function no longer has the option to echo. It always returns. As a result, any calls that used the return argument need to be updated. This is often used by the Paginator when rendering the counter as well as Session flash messages. Change this:

echo $this->Paginator->counter(array(
    'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
));

to this:

echo $this->Paginator->counter(array(
    'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%')
));

PHPUnit Tests

CakePHP 2.0 has replaced SimpleTest with PHPUnit. Unfortunately, this is where I have had the greatest trouble. I initially tried installing PHPUnit from pear, however, I was running into errors that turned out to be related to MAMP's default pear configuration. Deleting the pear config file ended up solving that problem. I decided to rebake my tests and then copy the test methods from my old app. I am having trouble with some fixtures that use virtual fields on my Model tests. I can't seem to get any controller tests to run. Once I solve the testing dilemmas, I will come back and update this.

I still have a few CSS issues to resolve, but the application is up and running! Now I can't wait to dig into the new features!

CakePHP and UUIDs

Posted by Paul Marshall on Sun, Feb 13 2011 17:02:00

A UUID (Universally Unique Identifier) is a 36 character string comprised of 32 hexadecimal digits separated by 4 hyphens. UUIDs are typically used for primary keys when the key needs to remain unique across many tables, databases, or systems. This is often the case with database replication. UUIDs are designed to be "practically unique". You could generate 1 billion UUIDs every second for 100 years before your probability of generating a duplicate would be 50%.

CakePHP Support

CakePHP supports UUIDs and will automatically generate them for primary keys with the data type CHAR(36) or BINARY(36). In addition you can generate UUIDs in your app using String::uuid().

Why Use UUIDs?

Abstract Data

UUIDs can be used to provide some powerful data abstraction and interesting queries. In ActionTracks, I store additional attribute data for contacts, profiles, and users in one table. A field in the data table is called entity_id and stores the id of contacts, profiles and users. Using integer based primary keys would require generating the keys from a central source. This would be impossible using auto incrementing integer based primary keys.

Advanced Database Features

If you plan to use advance database techniques such as replication, partitioning, or clustering you will probably need unique primary keys. This is one of the most common reasons to use UUIDs. If you plan to use these features or think that your application may grow to the point of needing multiple database servers then you should consider using UUIDs from the beginning to avoid rekeying your application.

Obfuscation

UUIDs can help to obfuscate your application. While this is certainly not foolproof, identifying a 36 character string is a lot harder than identifying an integer.

Disadvantages

Unfortunately, the power of UUIDs comes at a price. Using UUIDs can have some performance implications especially when using InnoDB. A UUID is typically much larger than an incremental id. In many cases, the id is larger than the data it represents. In InnoDB, secondary keys always include the primary key, This can result in significantly larger indexes.

Tips

Although CakePHP does not support the 16 byte hex encoded UUID with the key type of BINARY(16), it does support BINARY(36) which is still better than using CHAR(36) which can be slowed down by collation.

It is sometimes useful to add a UUID generator to your site for initial development and testing.

jQuery In Place Editing with CakePHP

Posted by Paul Marshall on Sat, Dec 04 2010 10:48:00
In 2008, I wrote an [article on using the Scriptaculous InPlaceEditor and InPlaceCollectionEditor](http://bakery.cakephp.org/articles/abalonepaul/2008/07/07/using_the_ajax_helper_for_inplaceeditor_and_inplacecollectioneditor_fields "article on using the Scriptaculous InPlaceEditor and InPlaceCollectionEditor"). I recently updated that project and converted the editors to jQuery. This is a brief tutorial on using the [jQuery In Place Editor](http://code.google.com/p/jquery-in-place-editor/ "jQuery In Place Editor"). This is much easier than using Prototype and Scriptaculous. I am assuming that you are using CakePHP 1.3 or greater and the Js Helper with jQuery. For our example, we are going to use a simple contact form with fields for the contact's name and the contact's state. The first thing you need to do is download the jQuery In Place Editor Plugin from [here](http://code.google.com/p/jquery-in-place-editor/downloads/list "here"). Unzip the file and put jquery.editinplace.js into your /webroot/js/ folder. You can include the plugin in your layout or in the view file as you will see below. Create your tables and models. The contacts table needs a state_id field and name field. The states field needs an id and a name field. Your Contact Model should look something like this.
class Contact extends AppModel {

    var $name = 'Contact';
    var $belongsTo = array(
            'State' => array(
                                'className' => 'State',
                'foreignKey' => 'state_id'
            )
        );
}
And your State Model should look something like this:
class State extends AppModel {

    var $name = 'State';
}
Now let's move on to your Contacts Controller which should start out looking something like this:
class ContactsController extends AppController {

    var $name = 'Contacts';
    var $uses = array('Contact', 'State');
    var $helpers = array('Html', 'Form', 'Js');

    function view($id = null) {

        if (!$id) {
            $this->redirect(array('action'=>'index'));
        }
        $this->set('contact', $this->Contact->read(null, $id));

        // Build the states array and set the view variable
        $states = $this->State->find('list');
        foreach ($states as $key => $value) {
            $options[] = $key . ':' . $value;
        }
    
        $this->set('stateListAjax', implode(',',$options));
    }       

}
Now lets add the method to your Contacts Controller that will handle the update. This method is responsible for updating the database. Let's add the ajax_update method and then discuss it.
 
class ContactsController extends AppController {

    var $name = 'Contacts';
    var $uses = array('Contact', 'State');
    var $helpers = array('Html', 'Form', 'Js');

    function ajax_update($id){ 
    //Step 1. Update the value in the database
    $value = $this->params['form']['update_value']; //new value to save 
    $field = $this->params['form']['element_id'];
    $this->Contact->id = $id; 
        if (!$this->Contact->saveField($field,$value,true)) { // Update the field
            $this->set('error', true); 
        } 
        $contact = $this->Contact->read(array($field), $id); 

        //Step 2. Get the display value for the field if the field is a foreign key
        // See if field to be updated is a foreign key and set the display value
        if (substr($field,-3) == '_id'){
        
            // Chop off the "_id"
            $new_field = substr($field,0,strlen($field)-3); 

            // Camelize the result to get the Model name
            $model_name = Inflector::camelize($new_field);

            // See if the model has a display name other than default "name"; 
            if (!empty($this->$model_name->display_field)){
                $display_field = $this->$model_name->display_field;
            }else {
                $display_field = 'name';
            }
        
            // Get the display value for the id
            $value = $this->$model_name->field($display_field,array('id' => $value));
        }

        //Step 3. Set the view variable and render the view.
        $this->set('value',$value);
        $this->beforeRender();
        $this->layout = 'ajax';
    } 
}
Now, we need to include the RequestHandler Component and disable debugging output for the AJAX calls, so modify your App Controller so it looks like this:
class AppController extends Controller {
   
    var $components = array('RequestHandler');

    function beforeRender() {
        if($this->RequestHandler->isAjax() || $this->RequestHandler->isXml()) { 
            Configure::write('debug', 0); 
    } 
    }
}
Now we can move onto the views. We need two views. One for the actual form and one to return the value from the ajax call. ajax_update.ctp

e($value);
Our contact form should look like this:
    echo $this->Html->script('jquery.1.4.3.min',array('inline' => false));    
    echo $this->Html->script('jquery.editinplace',array('inline' => false));
    echo $this->Html->scriptBlock('
        jQuery(function () {
        jQuery(".textField").editInPlace({
            url: "\/path\/to\/ajax_update\/'. $contact_id . '\/",
            default_text: "" //Disable the default text
        });
        jQuery("#state_id").editInPlace({
            url: "\/path\/to\/ajax_update\/'. $contact_id . '\/",
            default_text: "",
            field_type: "select",
            select_options: ' . $stateListAjax . ',
                    success: function(html) { 
                       this.dom.html(html);
                    }
        });
            });
',array('inline' => false));

Now just browse to the url of the view, passing a contact id to see the form. When you click the text box and change the value, the value is updated and pushed back to the view. When you click the State field, the select box appears and you select a state. The table is updated with the state id and state name is pushed back to the view. Sweet!! As long as you add the textField class your text field divs, the editor will be enabled. Select boxes need to be done for each select box as you need to generate the option lists for each. There is a fork of this plugin that supports dynamic selects, however, I don't like how it handles some other situations. Occasionally, you will have a field that is empty. By default, empty fields will be filled with "Click to Edit...". I don't like the way this looks when there are many empty fields. I disable the default text in the plugin configuration. You can simply remove the default_text configuration option, or you can Add the text of your choice. This will usually make the empty fields difficult to find. I use CSS to set the height or min-height of that element. I then add a title attribute to the field divs with "Click to Edit" as the value. Now you don't see "Click to Edit" in every empty field, however you see it as a tooltip when you hover over the empty field. You would do the Name field like this.


Implementing Jared Hoyt's Wizard Plugin for CakePHP

Posted by Paul Marshall on Sat, Nov 27 2010 15:08:00

I recently refactored one of my projects and decided to upgrade the Wizard Component written by Jared Hoyt to his new Wizard Plugin. Simply upgrading the component to the plugin would not have been difficult, however, I am using the Wizard in a jQuery modal box with AJAX form submission. This article is about the issues I encountered and how I fixed them.

History

When I began developing ActionTracks in 2008, I used Jared Hoyt's Wizard Component to manage the creation of Action records. The initial Wizard Component did not support plot branching so I had to modify the Wizard to support my plot branching. The Wizard ran in a modal window using an iframe.The Action creation process uses five steps with two separate branches. Each branch has 4-8 options. The branching looked like this.

action_details (Select the Trigger)

    trigger_xxx ( If the Trigger is not Immediate, Enter the Trigger Details )

select_task (Select the Task to Perform)

    task_xxx (Enter Task Detaisl)

confirm

This worked flawlessly.

Update

I have been doing a major rewrite of ActionTracks both  on the back end and front end. On the front end, I have been converting all off the Scriptaculous and dhtmlx widgets to jQuery. In addition, I have been adding more AJAX where it makes sense. In addition to my rewrite, Jared has done a major rewrite of the Wizard Component converting it to a plugin and adding plot branching.

Not only am I upgrading to the Wizard Plugin, but I am now using a jQuery UI Dialog for the modal and AJAX instead of standard forms inside an iFrame. This has been one of the most difficult and frustrating conversions.

I have managed to get it working with only a few tweaks.

I had to prefix my callbacks with an underscore.

I need to disable the defaultBranch with:

$this->defaultBranch = false
I do this because the first trigger option is Immediate, so there are no Trigger Details. This allows the Immediate option to skip to the Select Task step.

My biggest issue was dealing with Wizard Completion. Previously, there was no _afterComplete callback. Saving the data was handled in the final step callback. Everything worked for the most part with the completion code in the final step callback. As soon as I moved it to the _afterComplete, things went haywire.

There are a couple issues with saving the data.

$this->Wizard->read()
did not work in the _afterComplete callback because the data has been moved from the session key (Wizard.ControllerName) to the Wizard.complete key. I was able to retrieve it with $this->Session->read() and then save it.

Now the final issue was with redirection. As others have reported, there is a problem redirecting to $wizardAction without the controller name. I tried adding the action, but it didn't work for me. I fixed this by changing

$this->redirect($this->wizardAction)
to
$this->process() 
calling it directly instead of redirecting. This can cause a PHP Warning because you are redirecting to the process method without passing the step, so I changed

function process($step) {

to

function process($step = null) {

After all of this, it works. I need to do some thorough testing especially with backing up through steps, but so far so good.

The next step is getting the Wizard to handle editing of records. In my situation, it may be easier to delete or soft delete a record and simply create a new record. The reason is that if I edit a record there are fields with existing data that would create unpredictable results unless they were cleared.

Dynamically Binding Associations with CakePHP

Posted by Paul Marshall on Sun, Nov 07 2010 08:23:00

Over the last couple years of working with CakePHP, I have seen various architectures and strategies implemented. One of the techniques that I have been interested in is how different applications handle associations and binding. Last year, I worked on a project that had essentially disabled associations and binding by default. The associations were always bound on-the-fly. This often resulted in inconsistent and redundant Cake queries and code. In addition, one of the developers simply passed SQL queries to Cake and then processed everything manually. One of my responsibilities was refactoring the application to bring it into compliance with common web and CakePHP standard which meant converting many of these SQL queries to Cake queries. ###Why is this important? Bake is a wonderful tool and I regularly use it when starting new apps or prototyping. This is an incredible time saver. Unfortunately, the default associations can often create performance problems in a production environment. The reason is that the default associations result in much more data being returned than you need. Initially, this article was about one specific solution, that I thought would work in most cases. I realized that one solution doesn't apply to all situations and we need to use different strategies depending on the application. Now this article is about different solutions that you can use and where they make sense. ###bindModel unbindModel Solution This is the solution that is suggested in the CakePHP Book. I am not going to go into detail into this solution as I feel that it is the least desirable solution. My problem with this is that it can result in a lot of reduntant code as many of the bind calls will use the same associations. This also makes the code error prone if one of those binds isn't written correctly. Testing also becomes more complicated as well. The only time I suggest this method is if you need to destroy the association or you need to create an association without the default associations. ### Model Bind Method Solution My first attempt at dynamic binding involved a method in each model that handled the binding and used a hard coded array of associations. It looked like this:

class User extends AppModel {
	/**
	 * Binds the model to its associated models. This method optionally takes an array of model names
	 * and contains the binding to any models given in the an array parameter.
	 *
	 * @param array $models An array of model names that the bind will be restricted to.
	 */
    public function bind($models = null) {
        $result = $this->bindModel(array(
            'hasMany' => array(
                'Recipe' => array(
                    'className' => 'Recipe',
                    'conditions' => array('Recipe.approved' => '1'),
                    'order' => 'Recipe.created DESC'
                    )
            ),
            'hasOne' => array(
                'Profile' => array(
                    'className' => 'Profile',
                    'conditions' => array('Profile.published' => '1'),
                    'dependent' => true
                    )
                )
            )
        );
        if (!empty($models)) {
             $this->contain($models);
        }
         return $result;
    }
}

This worked well, however, I was only using it in models that no one else was working with. This will allow you to easily bind on-the-fly. Optionally, you can include an optional array to add containable options if you are using the containable behavior. If you are a team member on a project and do not have the authority to make application wide changes, this is a good solution because you can contain this implementation within the Model you are working on. ### AppModel Bind Method Solution More recently, I have updated this so that the method was inside the app_model.php and the association array was in the model.

class AppModel extends Model {
    /**
     * Binds the model to its associated models. This method optionally takes an array of model names
     * and contains the binding to any models given in the an array parameter.
     *
     * @param array $models An array of model names that the bind will be restricted to.
     */
    public function bind($associations = null, $models = null) {
        $result = $this->bindModel($associations);
        if (!empty($models)) {
            $this->contain($models);
        }
            return $result;
    }
}

The Model looks like this now:

class User extends AppModel {
    $associations = array(
        'hasMany' => array(
            'Recipe' => array(
            'className' => 'Recipe',
            'conditions' => array('Recipe.approved' => '1'),
            'order' => 'Recipe.created DESC'
            )
        ),
        'hasOne' => array(
            'Profile' => array(
                'className' => 'Profile',
                'conditions' => array('Profile.published' => '1'),
                'dependent' => true
                )
           )
       );
}

You call it like this:

$this->User->bind($this->User->associations,array('Profile'));

This is a great solution for working with an existing application where the associations were not created. While this works fine on existing models, this tends to break things when using Bake as Bake is not able to recognize the associations. ### Default Binds, Containable, Negative Recursion Now I have come up with a new solution, but it has not been thoroughly tested. I think This could be the easiest and most flexible solution of all. You should be able to effectively disable all association binding with one line in your app_model.php:

$this->recursive = -1;

You no longer need the bind method in the app_model.php. The only difference is that you need to set the Recursive property on each model find or save method. Optionally you can use the Containable Behavior to further filter your data. The additional benefit here is that your default binds remain intact which will not break Bake.