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/Command/.
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:
{code type=php}
E:\...\trunk\app\>..\lib\Cake\Console\cake [command]
{/code}
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:
{code type=php}
E:\...\trunk\app\>cake [command]
{/code}
Cronjobs now work this way (using crontab):
{code type=php}
*/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
{/code}
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
{code type=php}
cake upgrade all -params
{/code}
I use an enhanced 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.
Rendering elements from helpers
This is now possible because the View is available in the helpers now.
So inside a helper method just do:
{code type=php}
$elementContent = $this->_View->element('...');
{/code}
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:
{code type=php}
$output = $this->_View->output; // contains the current rendered output
{/code}
If you need any information on the current url or params:
{code type=php}
$this->_View->request // contains all information about the current request
{/code}
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
{code type=php}
public $helpers = array(
'Html' => array(
'className' => 'Tools.HtmlExt' // Tools Plugin - HtmlExtHelper
)
);
{/code}
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
{code type=php}
$post = $this->Post->read(null, $id);
if (!$post) {
throw new NotFoundException();
}
{/code}
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:
{code type=php}
App::uses('GeolocationLib', 'Tools.Geo'); # in /Plugin/Tools/Geo
{/code}
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:
{code type=php}
App::import('Lib', 'Tools.Bootstrap/MyBootstrap'); # in package "Bootstrap"
{/code}
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:
{code type=php}
public function setup(&$Model, $config = array()) {}
{/code}
A correct piece of a behavior now looks like:
{code type=php}
public function setup(Model $Model, $config = array()) {} # type Model and no &
{/code}
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.
I wrote a "CorrectShell" which extends the UpgradeShell. It does take care of almost all changes. I will publish it soon.
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:
{code type=php}
$this->response->type('pdf'); //or as mimetype:
$this->response->type('application/pdf');
{/code}
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:
{code type=php}
array(
'table'=>$this->Conversation->tablePrefix.'conversation_messages',
'alias'=>'ConversationMessage',
'type'=>'inner',
'conditions'=>array('Conversation.last_message_id = ConversationMessage.id'),
), ...
{/code}
In 2.0 it's just:
{code type=php}
'table'=>'conversation_messages'
{/code}
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:
{code type=php}
Router::connect('/shortcut', array('controller' => 'overview', 'action'=>'some_action'));
{/code}
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:
{code type=php}
Router::connect('/shortcut', array('admin'=>false, 'controller' => 'overview', 'action'=>'some_action'));
{/code}
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:
{code type=php}
'joins'=>array(
array(
'table'=>$this->tablePrefix . 'conversation_users',
'alias'=>'ConversationUser',
'type'=>'inner',
'conditions'=>array('Conversation.id = ConversationUser.conversation_id')
),
)
{/code}
becomes
{code type=php}
'joins'=>array(
array(
'table'=>'conversation_users',
'alias'=>'ConversationUser',
'type'=>'inner',
'conditions'=>array('Conversation.id = ConversationUser.conversation_id')
),
)
{/code}
Cake is now capable of prefixing the table names automatically.
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/Command/.
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 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 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.
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.
I wrote a “CorrectShell” which extends the UpgradeShell. It does take care of almost all changes. I will publish it soon.
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.