Freshly baked Cake2.0 tips

With 2.0 there are many new features available.
Some of them I want to introduce here.

Using the console for 2.0

I once wrote about how to use the Cake1.3 console.
The 2.0 shell is not so much different. The Console folder was only moved to /lib/Cake/Console/.
Make sure you use this new path. Also note, that it now seems to be important to have a "base app".
You can’t just use the console anymore anywhere you like. It needs to have an app to get configuration and additional information on.
It also might be important which direction you are using the console – at least in 2.0 now. Being in the Command path with -app param might result in sth different than using the default way of using the console from within your current app directory.
So you should always go with the latter:

E:\...\trunk\app\>..\lib\Cake\Console\cake [command]

And if you put your "…/Console" path in your System Environment path (see the other article for details), you can use "cake" as standalone again:

E:\...\trunk\app\>cake [command]

Cronjobs

Cronjobs now work this way (using crontab):

*/30 * * * * /srv/www/.../trunk/vendors/cakeshell app -cli /usr/bin -console /srv/www/.../trunk/lib/Cake/Console -app /srv/www/.../trunk/site >> /tmp/log.log

This runs a the AppShell (defined in your /App/Console/Command/ folder) every 30 minutes and logs errors/results to the given log file.

Note: cakeshell is a file containing the script from the cookbook.
Also note: If you use windows you might need to run "dos2unix /…/Cake/Console/cake" in order to make the cake console working.

Upgrade using the upgrade shell

cake upgrade all -params

I use an enhanced version of the upgrade shell which provides more functionality.
For params I use –svn. It cakes care of the moving process if you use subversion. git is also supported.
Even if you already upgraded you should run it. Just in case you missed anything. The shell also auto-corrects some styling errors or deprecated syntax.
For a complete run through all your files with one click:

cake Upgrade.Upgrade all

Also note that I added a CorrectShell which goes beyond anything the Upgrade shell provides. It additionally takes care of

  • request (everything to do with the new request object)
  • vis (public/protected attributes)
  • forms
  • reference (corrections)
  • i18n (translation stuff)
  • amp (& removal)
  • php53 stuff

You can run this with for all tasks at once:

cake Upgrade.Correct all

Make sure you have a backup of everything! I also commit/push everything before running any such shells.

I also opened a ticket for it, but it seems my enhancements will not be made available for everyone.

Rendering elements

If you used to render elements/view ctps from the same view folder, you need to respect the new CamelCase folder structure:

// in the view templates
$this->element('../MyControllerName/my_element');

Rendering custom views

If you used to render custom elements from other controller namespaces inside your action you also need to respect the new CamelCase folder structure:

// in some controller action
$this->render('/MyViewFolder/my_view');

Rendering elements from helpers

This is now possible because the View is available in the helpers now.
So inside a helper method just do:

$elementContent = $this->_View->element('...');

Note: element() now has three params. The second still is the one where you can pass variables to the element scope. But the third param is now the options param (cache, plugin, callbacks, …).

It is even possible to alter the output from a helper directly:

$output = $this->_View->output; // contains the current rendered output

If you need any information on the current url or params:

$this->_View->request // contains all information about the current request

Overriding plugin views in the app

Another feature that finally arrived. Now plugins don’t have to cover all possible cases.
For specific apps you can override the plugin view locally.
Place the view in /app/View/Plugin/PluginName/Controller/action.ctp. The same goes for layouts, of course.

Enhancing objects and aliasing

public $helpers = array(
    'Html' => array(
        'className' => 'Tools.HtmlExt' // Tools Plugin - HtmlExtHelper
    )
);

Declared anywhere in the code the HtmlExt helper will then be used instead of the core Html helper.
The important change is, that it will use the old namespace, though. $this->Html is still the right way to use it. So you can enhance every helper, component, … to suite your needs without having to change the existing code itself.

This opens whole new possibilities. Until now you would have used a FormExt helper and manually called it with $this->FormExt->foo().
For 2.0 I use and recommend the following structure:
Add Ext to helpers/components etc from the core you want to Extend:

  • FormExt(Helper)
  • RequestHandlerExt(Component)
    alias them as above to the original one.
    Best to place them in a plugin all your apps have in common (Tools in my case).
    Now if you want to go even further, you can override that one again specifically for this app by prefixing it with My:
  • MyForm
  • MyFormExt

    All app specific helpers are not in a plugin but the app dir structure and start with My.
    And using aliasing you can still call the helper with $this->Form->foo().
    Same goes for components, behaviors, …

Working with exceptions

$post = $this->Post->read(null, $id);
if (!$post) {
    throw new NotFoundException();
}

The exception should then display a 404 error page.

Including scripts

Before 2.0 you always used App::import();
But with the switch to App::uses() the files do not get included anymore at startup due to the Super-lazy-loading. This should only be used for real classes. Also start to group by PackageName:

App::uses('GeolocationLib', 'Tools.Geo');  # in /Plugin/Tools/Geo

It will only be included by Cake if actually needed. One reason why Cake2 is so much faster.

If you have a file without a class (containing functions or constants or whatever) you still need to use the old method:

App::import('Lib', 'Tools.Bootstrap/MyBootstrap'); # in package "Bootstrap"

Using own classes instead of core ones

Let’s say, you want to apply a fix to a core file without overriding the core folder. Or you want to replace a file altogether. Simply use the same folder structure inside the /Lib folder.
For your own "FormAuthenticate"
/app/Lib/Controller/Component/Auth/FormAuthenticate.php

Cleanup!

If you are upgrading to 2.0 – or already have – you should get rid of old PHP4 chunk.
For starters, the &+object needs to get eradicated once and for all.

Old style:

public function setup(&$Model, $config = array()) {}

A correct piece of a behavior now looks like:

public function setup(Model $Model, $config = array()) {} # type Model and no &

Same goes for components, controllers, helpers, …
Note: You can do this for 1.3 already, as well. At least if you are using PHP5 (and you probably are).

You should also change all "var $x" to "public $x" and add "public" to all methods in classes which don’t have a visibility attribute yet.

It can be found in the above "CorrectShell" which extends the UpgradeShell. It does take care of almost all of those changes.

Things I did find out the hard way

Most of my libs and helpers which use the Xml class had to be modified quite a bit.
The class itself is not accessed statically, and more important does return only lowercase keys.
This was some piece of work to find and correct all those necessary changes.

If you make use of the internal HttpSocket class you might want to be interested in the fact, that it now returns an object as response. So you need to use $response->body to access the result content.

You cannot really set the headers and content type in your layouts anymore manually using "header()". You need to do this using the response object:

$this->response->type('pdf'); //or as mimetype:
$this->response->type('application/pdf');

Either from the controller or the view/layout/helper.

The core.php, routes.php and bootstrap.php need new settings in order for your app to fully function under 2.0. Tip: Look at the original 2.0 app files and compare them to your upgraded files.
Add anything that is missing.

There seem to be dependency problems now with components and helpers. So the order in which you add them in your controller is now important (wasn’t in 1.3). If they depend on a class you should add them after that one to prevent the collection to fatal error. (UPDATE: Some if it seems to be fixed now).

Shell tips not mentioned in any upgrade guide

Shells are one of the most useful tools out there. I have dozens of them and those were the first classes I ported to 2.0. Therefore I ran into problems with those, first. So in case, you write your own ones, too:

The usage of shells has changed quite a lot. You don’t need a help method anymore. The consoleOptionParser object will take care of that using "-h".
For using params/args you will now need to set up a "getOptionParser" method (see the core shells for examples).
For string options you should set an empty default value: 'default' => '' (prevents you from warnings). For boolean values set 'boolean' => true.

A main() command should always be called explicitly ("cake Shell main -f" instead of "cake Shell -f") if you want to pass params or args. Otherwise it won’t run.

And there is more

Make sure you check all your forms. Especially the ones where you customized. Since 2.0 now adds HTML5 types automatically, it can get unwanted results when you use names for fields that are already matched in the schema. In my case many normal "text" fields suddenly ended up being "type"=>"number". Which screwed up the whole form (could not validate anymore).
Some normal selects (for search etc) suddenly ended up being "multiple"=>"multiple". Why? I am not yet sure 🙂

The FormHelper does not support some field type aliases anymore. So for textareas you have to use 'type'=>'textarea'. "textfield" doesn’t work anymore – if you have that still somewhere in your forms.

Custom Joins do not need the prefix anymore. In 1.3 you had to do:

array(
    'table'=>$this->Conversation->tablePrefix.'conversation_messages',
    'alias'=>'ConversationMessage',
    'type'=>'inner',
    'conditions'=>array('Conversation.last_message_id = ConversationMessage.id'),
), ...

In 2.0 it’s just:

        'table'=>'conversation_messages'

Otherwise it will prepend the prefix twice which, of course, breaks the query!

The Routing has changed quite a bit. Some to the better – some to the worse (or I coudn’t figure out yet how it is done in 2.0):
In 1.3 this was working to connect an url and all its prefixes:

Router::connect('/shortcut', array('controller' => 'overview', 'action'=>'some_action'));

But this will not in 2.0 anymore. Here all prefixes get treated as normal named params.
A quick fix (which does not enable the prefixed urls, though!) would be:

Router::connect('/shortcut', array('admin'=>false, 'controller' => 'overview', 'action'=>'some_action'));

At least the normal user url is working, again.
Although this doesn’t seem to work flawlessy, either…

Custom joins don’t need a model prefix anymore:

'joins'=>array(
    array(
        'table'=>$this->tablePrefix . 'conversation_users',
        'alias'=>'ConversationUser',
        'type'=>'inner',
        'conditions'=>array('Conversation.id = ConversationUser.conversation_id')
    ),
)

becomes

'joins'=>array(
    array(
        'table'=>'conversation_users',
        'alias'=>'ConversationUser',
        'type'=>'inner',
        'conditions'=>array('Conversation.id = ConversationUser.conversation_id')
    ),
)

Cake is now capable of prefixing the table names automatically.

0.00 avg. rating (0% score) - 0 votes

9 Comments

  1. About Enhancing objects and aliasing : have you really tried it by your own ? I’m not able to use it… it crashes the applications, cause the new methods are not found.

    App::uses('HtmlHelper', 'View/Helper');
    class HtmlPublicHelper extends HtmlHelper {
        public function media(){ ... }
    }
    
    App::uses('Controller', 'Controller');
    class AppController extends Controller {
        public $helpers = array(
            'Html' => array(
            'className'=>'PublicHtml'
            ),
        );
    }

    the call to $this->Html->media() crashes … what’s wrong ? if i load $this->HtmlPublic->media and this helper, everything works.
    thanks for your advice :o)

  2. Yes, with the form helper, anyway:

    public $helpers = array('Session', 'Html', 'Form'=>array('className'=>'Tools.FormExt'), ...);

    It could be a bug – but I tried your code and I could not reproduce it (worked fine!). So there must be sth wrong on your end, I think. Do you use the current 2.0.3 head?

  3. @Emmanuel, @Mark

    I have found that using the alias classname on helpers in 2.0 the methods are sometimes not available.

    After some trial n error tinkering and stuff that if you have the Html => array(className=>PublicHtml) in AppController

    and Html is listed in other controllers this will mean that the other controllers the extended methods are not avilable

  4. Not sure if you guys have noticed this yet, or if it’s really the issue, but your code examples are completely broken. Including the follow-up comments:

    The class name is HtmlPublicHelper and you guys are refering to it as PublicHtml. I have been using helper aliasing and have had no problems whatsoever.

  5. The Helper suffix is not necessary – at least that is my experience. Feel free to provide a test scenario that proofs otherwise. Until then I use (successfully) the ‘className’=>’Tools.FormExt’ etc

  6. Hi, I was trying to use your upgrade shell from your github, but even after putting the code in app/Plugin folder and enabling all plugin load in app/config/bootstrap.php

    CakePlugin::loadAll();

    , whenever I run the command

    cake Upgrade.Correct all

    or any other command, the console shows error "Error: Plugin Upgrade could not be found.".

    Below is the complete console output:
    Error: Plugin Upgrade could not be found.
    #0 F:\wamp\www\stormme-main\lib\Cake\Core\App.php(332): CakePlugin::path(‘Upgrade’)
    #1 F:\wamp\www\stormme-main\lib\Cake\Core\App.php(217): App::pluginPath(‘Upgrade’)
    #2 F:\wamp\www\stormme-main\lib\Cake\Core\App.php(510): App::path(‘Console/Command’, ‘Upgrade’)
    #3 [internal function]: App::load(‘CorrectShell’)
    #4 [internal function]: spl_autoload_call(‘CorrectShell’)
    #5 F:\wamp\www\stormme-main\lib\Cake\Console\ShellDispatcher.php(216): class_exists(‘CorrectShell’)
    #6 F:\wamp\www\stormme-main\lib\Cake\Console\ShellDispatcher.php(167): ShellDispatcher->_getShell(‘Upgrade.Correct’)
    #7 F:\wamp\www\stormme-main\lib\Cake\Console\ShellDispatcher.php(69): ShellDispatcher->dispatch()
    #8 F:\wamp\www\stormme-main\app\Console\cake.php(41): ShellDispatcher::run(Array)
    #9 {main}

    Please help. Thanks in advance.

  7. Additional Information on my installation:

    The "Console" folder was put under this path:
    app/Plugin/Upgrade

    So the resulting structure is:
    app/Plugin/Upgrade/Console/Command/CorrectShell.php
    app/Plugin/Upgrade/Console/Command/UpgradeShell.php

    "Lib" folder’s content was put under this path:
    app/Lib/Lib.php

    Please let me know if my installation is correct. I’m new to CakePHP so please bear with me :).

  8. Sorry for more comments, I forgot to mention that initially I kept all files under the path:
    app/Plugin/Upgrade

    But as the error showed up, trying to fix the issue I moved the files as mentioned above.

  9. Sorry for yet another comment. But I thought I would share, I solved my problem. The issue was, I was running the "cake" command from the app/Console folder instead of "app" folder. When I ran the command from "app" folder, it worked perfectly. Also for other newbies like me out there, put the complete folder structure under "app/Plugin/Upgrade".

    Thank you soo much for your hard work Mark. Your console solved many upgrade issues that would have taken me a long time. Still a few issues left. Those have to be done manually, like the Authorization component. Superb job, a great help!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.