RSS
 

Posts Tagged ‘Upgrade’

CakePHP 3.0 Migration Notes

06 Jun

Trying to migrate my CakeFest app for this year’s event, I made some notes regarding the upgrade process from 2.x to 3.x.
I completed them during the upgrade of my Sandbox app to 3.0. And adjusted them after RC2 and 3.0.0 stable again while upgrading 2 more apps and 5+ plugins.

Initial steps and requirements

The following were given in my case:

  • The app was already composered (as it should be for all CakePHP 2 apps) and thus "composer" was already available
  • I used a 2.x setup with ROOT dir as APP dir directly (which seemed to cause some trouble later on)
  • I used GIT for version control and supervise each upgrade step (which also caused some trouble)

Also make sure you covered the following reading material:

As a side note: It is also wise to follow the 3.0 development, read the PRs and proposed as well as recent changes.

Let’s go

By the time I wrote this I still had to tweak and fix the Upgrade tool along with it, so bear with me if something is not perfectly in sync here.

I first made sure I got a clean 2.x app with the latest (right now 2.6) changes, as outlined in previous posts regarding "how to prepare 2.x apps for 3.x".
I also used a "AppUsesCorrection" tool I have written to move all inline App::uses() statements to the top of the file. This was a left over from earlier days and will
screw up the files if not taken care off.

I also started to use the Shim plugin a while back for all my 2.x apps to make sure I already shimmed as much as possible towards 3.x, so the now required changes are minimal. If you already know you want to upgrade sooner or later, save time and shim "ahead".

Then I basically downloaded and initialized the Upgrade tool and ran the all command on my app and plugins separately.
In case something goes boom, better run the commands individually and verify the changes after each command (and commit them away).

Afterwards I adjusted my composer.json file for 3.0 and used composer update to get all new dependencies.
Here you should also make sure all dependencies like plugins are available as 3.0 versions, otherwise temporally remove/exclude them for now if possible.
Also don’t forget the new autoload and autoload-dev parts in the composer file.

A first try run revealed that I had still a lot of manual work to do in /config first:

  • config/app(_local).php
  • core.php (deprecated) => bootstrap.php
  • database.php (deprecated) ) => app(_local).php
  • routes.php

A tip here: IMO it is wise to not directly modify app.php as changes along the line will be harder to spot.
Instead I keep the file as it is (default) and just use a second one app_custom.php on top to overwrite/complete it where necessary.
I also use a third app_local.php, which is not under version control (.gitignore), to store non-commitable stuff like keys, salts, passwords etc.

Configure::load('app', 'default', false);
...
Configure::load('app_custom', 'default');
Configure::load('app_local', 'default'); // Not under version control

I found a lot of namespaces to be missing, as a lot of App::uses() have been left out in 2.x. It still worked there, as without namespaces it only needs it once per dispatching. But now it fails hard. So if you didn’t add the missing ones back in 2.x, you need to do that now at least.
I developed a tool to do that, the opposite of the unused_use fixer pretty much. This is very complex though. as it is not
always clear what package the use statements need to come from. It needs some config overhead.

I also had to remove the table prefixes as they are not supported in 3.x using my new CakePHP 3 Setup plugin DbMaintenance shell command cake Setup.DbMaintenance table_prefixes. It removed them in a few seconds. Afterwards my Table classes were able to find the tables again.

Afterwards I already tried to access a public page. Got quite a few things I had to manually take care of now:

Manual changes

Change public $uses = array('User'); to public $modelClass = 'User';. If there are multiple statements, this has to be resolved afterwards on top, using
$this->loadModel() etc.

Routes

The routes file will most likely also have to be adjusted by hand. The (admin) prefixes are the change that sticks out most.
But it is more repetitive than difficult to adjust the routes.
Bear in mind that you can easily set the fallback route class to InflectedRoute here first to handle them just as 2.x did:

Router::defaultRouteClass('InflectedRoute'); // Use DashedRoute for new 3.x projects

URLs

All the URLs usually are now more case sensitive (and CamelCased/camelBacked)

// Before
Router::url(['admin' => true, 'plugin' => 'my_plugin', 
	'controller' => 'my_controller', 'action' => 'my_action'])
// After
Router::url(['prefix' => 'admin', 'plugin' => 'MyPlugin', 
	'controller' => 'MyController', 'action' => 'myAction'])

Also make sure, you dont use the prefix values directly (admin, …) anymore, but the prefix key itself:

// Before
'loginAction' => ['admin' => false, 'plugin' => false, 
	'controller' => 'account', 'action' => 'login'],
// After
'loginAction' => ['prefix' => false, 'plugin' => false, 
	'controller' => 'Account', 'action' => 'login'],

E.g. for the AuthComponent config here. Otherwise it will redirect you to the prefixed URL instead as admin is not recognized anymore.

Auth

The auth code in the AppController and login action needed to be adjusted.
In the controller, it is not via properties anymore, but Auth->config(). The login action needs identify() and setUser() now.

Array => Entity

With the array to entity changes a lot of view files cannot be fixed with the Upgrade shell, and stuff like echo $profile['User']['id'] has to be refactored into echo $profile->user['id'], for example.
As $user['User']['id'] would be $user['id'] now, there are changes across all MVC layers to be applied in order for the functionality to work again as expected.

Custom

For all my own custom replacements I collected them and made a Custom task over time to avoid having to do this all over again across multiple apps or plugins.
I therefore forked the Upgrade plugin.

When working with date(time) fields I also had to do some special refactoring, as some older apps had 0000-00-00 00:00:00 stored as null/default value.
This is quite unfortunate, as with Carbone and Time class, this would create negative values, which blows everywhere.
So I created a Setup.DbMaintenance dates command in the Setup plugin to refactor those fields and their content into the proper value.

The same goes for foreign keys and '0' stored in wrong DEFAULT NOT NULL columns. With the Setup.DbMaintenance foreign_keys command you can also clean those up (DEFAULT NULL + NULL value).

Validation

It would be quite the task to rewrite the whole validation with all the models and their $validate properties. So here I just used the Shim plugin from above and kept the old syntax to save time. The same for relations and a lot of other model properties. It then only needed minimal adjustments, like adding 'provider' => 'table' for isUnique rule or changing notEmpty to notBlank.

Virtual fields

Mixing them with the fields array itself is not so easy anymore.
You can use closures to help out:

// For your find('all', $options) $options
'fields' => function ($query) {
	return [
		'jobtype', // Normal field
		'num' => $query->func()->count('*'), // COUNT(*)
		'fetchdelay' => $query->func()->avg('UNIX_TIMESTAMP(fetched) - IF(notbefore is NULL, UNIX_TIMESTAMP(created), UNIX_TIMESTAMP(notbefore))'), // Something more complex
		'age' => $query->newExpr()->add('IFNULL(TIMESTAMPDIFF(SECOND, NOW(), notbefore), 0)'), // Custom expression
	];
},

Locales

The Locale folder is inside src, but the subfolders changed quite a bit. It is now flat, just two-letter country codes, for Germany the po file would be located in /src/Locale/de/ now (instead of .../Locale/deu/LC_MESSAGES/).

View ctps

These template files also have to change quite a bit.
For starters, the above array to entity conversion introduces a lot of change.
Also, all static calls now have to be handled by either importing the classes via use ...; statement at the top of each file, or you can wrap them with a helper.
A quickfix would be to just class_alias() them, e.g. the Configure::...() calls would need a lot of use statements you can omit if you put the following in your bootstrap.phpfile:

class_alias('Cake\Core\Configure', 'Configure');

Now, all Configure::read() calls work again in the ctps.

Assets

If you don’t directly output your inline assets, but add them to the "scripts" block to be outputted together in the layout, you will have to change the method calls.
It used to be 'inline' => true/false, now it is:

$this->Html->css('jquery/galleriffic', ['block' => true]);
$this->Html->script('jquery/jquery.galleriffic', ['block' => true]);

In your layout ctp you can then keep the echo $this->fetch('css'); and echo $this->fetch('script'); part as it was in 2.x.

Tricky ones

Tricky as in "not ease to spot"…

The !empty PHP bug I mentioned a while back.
I had a pagination index view where I iterate over all users and display something else if there are none (yet). This fails, now, though, as the empty check will always return false:

<?php foreach ($users as $user) {} ?>
<?php if (empty($users)) {} ?>

The empty check needs to be this way in order to work as expected:

<?php if (!count($users)) {] ?>

Or, when you know it is a query finder object:

<?php if ($users->count()) {] ?>

Same with:

while ($records = $this->_table->find('all', $params)) {}

This will run endless now. Here either add ->toArray() or use a streamable result.

UPDATE Since recently (3.0.4?) you can also use ->isEmpty() as check on any Query or Collection object:

$result = $this->TableName->find()->...;
if ($result->isEmpty()) {}

find()

I used the Shim plugin and the support for find(first) and find(count), but even then you need to make sure that for find(first) you don’t forget to adjust all those $options regarding keyField and valueField which are now required to be set if you plan on using non displayField values, as the "fields" list is ignored for it (used to work to filter on 2 fields and it automatically used those).

Magic/Dynamic finders

Careful with those, like findByUsername(). In 2.x. those returned find(first) results (limit 1 so to speak), in 3.x. those need an additional ->first() appended to have the same result.

Trait or Behavior?

You might run into this when refactoring your models and behaviors.
In 2.x behaviors had the problem that they didn’t work for non-primary models, and as such where often too limited and one probably tried to workaround it using traits.
In 3.x that limitation is gone.

I think the main idea behind behaviors keeps the same: If you want to dynamically attach and detach functionality to your models, this is the way to go. Traits are too static for this. Traits, on the other hand will be necessary if you want to cleanly overwrite Table methods, see the SoftDelete trait for an example. In that case you just can’t do this dynamically.

Additionally, behaviors can more easily be configured using built-in config() and they can be aliased easily. The downsite might be speed, which is neglectable, though.
So try behaviors first, then fallback to traits IMO.

Summary

All in all quite a lot of migration steps can be (partially) automated, which will help a lot for larger applications where it would just be super-tedious to do that manually on such a scale. But most of the ORM changes need manual code changes, which makes it really a time-intensive task for medium apps and above.
Using shims, coding wisely ahead of time, avoiding hacks or non-wrapper low-level functions, all those can help to ease migration. In the end you just have to swallow the bitter pill and get it over with. It is worth it!

 
2 Comments

Posted in CakePHP

 

CakePHP 2.6 – and the end of the beginning

15 Mar

I was first thinking about the title "and the beginning of the end" – but that sounded a little bit too Armageddon. In fact, 2.x will probably still be around for years – and at least 2.7 will still be released some day (it is not impossible that there might even be a 2.8 …).
The title "and the end of the beginning" fits much better as it allows fresh projects and early migrations to already leverage the new 3.x milestone while the rest just sticks to the 2.x one a while longer.

So what does 2.6 and 2.7 mainly bring?
First of all they benefit a lot from 3.x backports.
Many of the new 3.x functionality has been and will continue to be backported to 2.x.

They also allow the chance to further cleanup the code-base and make the code itself more "3.x-ish". Some of that can be done by looking at how the new core does things, some of it can be achieved using Shims (Code bridges between two versions).
Some of those things became already clear from the dev-preview versions of 3.x – and my article around it.

A few basic things that are very useful in light of the above:

  • Make your 2.x code (app, plugins) PHP5.4+ (maybe also use short array syntax). It will make the upgrade process smoother
  • Remove deprecations and outdated ways of doing things
  • Stay up to date with the 3.x developments and how to best use that information for future proof 2.x development

My recent doings

First I made sure, every app is now running on latest 2.6 stable, and added a few more tests along the road.
Further I made sure relevant changes or new features in 3.x core are backported to the 2.x core and that I plan to migrate to those in my apps ASAP.

Plugin cleanup

I started to extract my super-fat Tools plugin into smaller chunks. Most recent split off is the Shim plugin as I had to acknowledge the fact that I mixed too many fixes/shims and new functionality, which is usually not the best thing to do.
So there it is: A Shim plugin to contain all the bridge code towards 3.x and a few fixes along with it. And a Tools plugin that builds on top of it and adds the actual functionality.
This was the logical thing to do. Most of the shims are not needed beyond 3.x, and as such they shouldn’t be in a more persistent plugin.

Shimming

That brings me right to the next point: I looked into how to get 2.x apps closer to 3.x. Especially for lager code bases this really helps the migration to the next major version. Less necessary changes mean less change for breaking and faster upgrading results.

I ported flash messages to my 2.x version of FlashComponent and FlashHelper – including the syntactic sugar of $this->Flash->success($message).
This code will not have to be modified again at all when upgrading then.

Instead of the "mocking the hell out of it"-ControllerTestCase class I backported the IntegrationTestCase to 2.x which has a more sane approach on actual controller testing.
Using the syntax of 3.x in 2.x allows me to add a lot of new integration tests that will flawlessly work after the major version jump some day.

Password hashing

I migrated my 2.x apps from sha1 to state of the art PHP5.5+ password hashing (and the default in 3.x) – which can already work in 5.4, as well, thanks to shims. But I also needed to support existing passwords to provide BC.
So basically, I use the Shim.Fallback password hasher along with Shim.Modern and Simple ones to have a graceful fallback on old accounts and an auto-hash migration upon login. Each time a user logs in the new hash replaces the old sha1 one. Over time all users will be fully migrated and I can switch back to just Shim.Modern hasher directly (which is the Default hasher in 3.x by the way).

So after migrating to 3.x it will be:

  • Shim.Modern => Default
  • Simple => Weak

The latter is only relevant in case not all passwords have yet been migrated.

See my other article for details on how to implement them (via Passwordable behavior for example) or directly visit the Shim plugin documentation.

 
2 Comments

Posted in CakePHP

 

CakePHP 2.5 arrived + CakePHP Tips 2014

13 May

You probably read my last tip sections.
And I started to move some of them to my sandbox app.

But once in a while it might also be nice to publish a few selected tips, here as well.

Oh, and CakePHP 2.5 is out! Get it – now 🙂
See the last chapter of this post about why you really should upgrade ASAP. And it doesn’t matter if you
are on 1.x or 2.x.

URLs in CLI

In CLI and your shells there is no HTTP HOST and therefore usually no absolute URLs.
But in order to properly work with links/urls (e.g. sending batch emails), we need that to work.
So I use Configure::read(‘Config.fullPageUrl’) here.
My configs.php file then contains:

$config['Config'] = array(
	'fullPageUrl' =>  http://www.myapp.de // The primary URL
)

In case you have a different domain for local development like http://myapp.local and you want that to be the fullPageUrl, make sure you overwrite the default in your configs_private.php file:

Configure::write('Config.fullPageUrl', 'http://myapp.local'); // Usually defaults to the (live) primary URL

And in the end of my bootstrap file (after including all config files), I simply do:

if (php_sapi_name() === 'cli') {
	Configure::write('App.fullBaseUrl', Configure::read('Config.fullPageUrl'));
}

To test the current domain/fullBaseUrl, you can use my Setup plugin command cake Setup.TestCli router.
It will output both relative and absolute URL examples generated by the Router class with your current settings.

So on the live server then it will output http://myapp.local instead of http://localhost when generating Router::url()s in your shells.

Careful with running shells in CLI

Most are probably not aware, but running shells in CLI needs to have a proper user management around them in most cases.
Imagine yourself running your apache as www-data (default) and log in as root or any other user not affiliated with that www-data user/role (bad idea).
Once you execute a shell and tmp cache data are (re)built, your www-data user cannot access them anymore, losing the ability to cache and triggering a lot of errors.
So make sure you only log in with a user that shares the role of www-data at least, so that both can access each others’ generated files.

A popular example is the ClearCache shell which re-builds your cache dirs in debug 0 (when changing files or db schema makes this necessary).

PS: Of course you could also switch to another cache system than the default File engine. But most probably didn’t do that yet, either.

Merging arrays

Ever wondered what Hash::merge(), array_merge and array_merge_recursive have in common – or don’t have in common?
Check out these merge comparison examples.

See what the requirements are – and use the appropriate merge method then.

There is also the + operator, which is quite useful when merging flat arrays and string key based options. This is quite commonly used in the core to merge
options and defaults:

$defaults = array(
	'escape' => true
):
$options += $defaults;

In this case the $defaults are merged on top of $options, but only if that key has not been specified yet.
This kind of merge is really efficient and fast (4-5x faster than array_merge() itself) – but should really only be used if all keys are definitely strings.

Paginating and merging

A propos merging: When setting up paginate settings in your controllers, try to prevent

public function index() {
	$this->paginate = array(...);
}

This way you kill all defaults you might have set via public $paginate or in your extending controllers (AppController’s beforeFilter() callback for example).

So it is better to use:

$this->paginate = array_merge($this->paginate, array(...));
// or
$this->paginate = array(...) + $this->paginate;

In my 2.x code for example I have this snippet in all my AppControllers to have query strings for paginations:

public function beforeFilter() {
	parent::beforeFilter();
	$this->paginate['paramType'] = 'querystring';
}

This will only work with proper merging of defaults and custom settings.
I prefer the latter because the settings are string based and here the + operator is the fastest and easiest way of doing things.
Once the key is already set in your method, the default will be ignored right away (with array_merge() and nullish values this can be different/unexpected).

And remember to not mix controller and component pagination.

Pagination and sort default order

Adjust your bake templates so that some fields like created/modified are ordered DESC instead of ASC per default in the index actions.
For those fields the first click on the header row should display them DESC right away as one would then most likely be interested in the
latest changes. Same goes for most date fields like "published" as well as fields like "priority", "rating", …

That’s how the baked code (or manually adjusted one if done afterwards) could then look like (index.ctp):

<th><?php echo $this->Paginator->sort('name');?></th>
<th><?php echo $this->Paginator->sort('amount');?></th>
<th><?php echo $this->Paginator->sort('priority', null, array('direction' => 'desc'));?></th>
<th><?php echo $this->Paginator->sort('status', null, array('direction' => 'desc'));?></th>
<th><?php echo $this->Paginator->sort('publish_date', null, array('direction' => 'desc'));?></th>
<th><?php echo $this->Paginator->sort('modified', null, array('direction' => 'desc'));?></th>
<th><?php echo $this->Paginator->sort('created', null, array('direction' => 'desc'));?></th>

Using modified model data in the form again

Some of you might have had the wish of posted data that was modified in the model due to beforeValidate/beforeSave to appear modified in the view again (so
that the reason for validation errors might be more clear etc).
So let’s say you have a beforeValidate callback to clean the input of a textarea:

public function beforeValidate($options = array() {
	if (isset($this->data[$this->alias]['comment']) {
		$this->data[$this->alias]['comment'] = $this->_clean($this->data[$this->alias]['comment']);
	}
	return true;
}

So in this case it could easily be that _clean() removes some invalid content and thus the minLength rule is suddenly triggered.
Which is weird, since we posted at least twice the length of text.
To clarify to the user what is going on, one could adjust the error message – but one could additionally return the modified (ready to save) data instead of the
actually posted data.

public function add() {
	if ($this->request->is('post')) {
		$this->{$this->modelClass}->create();
		if ($this->{$this->modelClass}->save($this->request->data)) {
			$this->Session->setFlash(...);
			return $this->redirect(array('action' => 'index'));
		} else {
			// Here we assign the modified model data back to the request
			// object and therefore to the view/form
			$this->request->data = $this->{$this->modelClass}->data;
			$this->Session->setFlash(...);
		}
	} else {
		// Default values for the form
		$this->request->data[$this->modelClass]['status'] = true;
	}
}

The input field will now contain the content that was served to beforeValidate(). And combined with a good error message this will probably clear things up.

PS: You can also directly use the model’s name instead of {$this->modelClass}, e.g. ‘Comment’.

MySQL – MyISAM vs InnoDB

InnoDB is a little bit more robust as it allows transactions. Especially with CakePHP and "multiple queries" most of the time (per default) this can be quite helpful in keeping the DB in a valid state.
Also read myisam-versus-innodb on pros/cons for each.

One additional problem with InnoDB, though: Per default it creates a file that always increases in size, never decreases again. This can soon be a nightmare with backuping when it becomes >> xx GB of size.
See how-to-shrink-purge-ibdata1-file-in-mysql how to avoid that by not using that file, and instead using innodb_file_per_table.

Testing

Testing Controllers

I stumbled upon a few issues with testing controllers – especially plugin controllers.
For plugin controllers to be testable unfortunately you always need to use $uses, even if it wasn’t necessary due to conventions.
So for your Tools.TinyUrls Controller you would need

public $uses = array('Tools.TinyUrl');

Otherwise it would always try to look for the model/fixture in your app dir, which eventually always fails.

Do not forget to use --stderr when testing in CLI. Otherwise all tests that use the session (and which work fine in webtest runner) will fail:

cake test app AllController --stderr

Test Coverage

If you want to generate a HTML overview of all your locale test coverage:

cake test app AllApp --stderr --log-junit tmp/coverage/unitreport.xml --coverage-html tmp/coverage --coverage-clover tmp/coverage/coverage.xml

The report index.html will be in your /tmp/coverage folder.

More on testing – especially controller testing – can be found on the corresponding Tools plugin Wiki page.

Upgrade (deprecated) code

It is always good practice to upgrade to the current master. Not only for CakePHP, but there especially. It will save you a lot of time in the long run, as migration will be easier and faster in small steps instead of one huge step. It will also make it easier to use the new features and more importantly will also come with a lot of fixes and method corrections that the older versions didn’t get anymore. Those outdated versions usually only get security-bugs fixed. So if you look hours for an error that is already fixed in the current master, it was just a huge waste of time. I have seen that a thousands times – on IRC and elsewhere.

So in case you are not using the current master (2.5), do it now. Internally, upgrading 2.x is a "piece of cake".
Upgrading from 1.x is also not that big of a deal – just needs a little bit more manual adjustments. For most things you can use the core UpgradeShell as well as my Upgrade plugin.

In case you are already upgraded to 2.5, you can and should also remove deprecated functionality in favor of the right one.
Those deprecated things will only add to the file of upgrades for the next 2.x release or 3.x. And using the upgrade shell it is usually just one single command to execute.
So for 2.5, you should have removed all the "easy" stuff that will clearly be switched with a different way of doing things as it is mentioned in the migration guide, e.g.

  • loggedIn() in favor of Auth::user(‘id’)
  • CakeRequest::onlyAllow() in favor of CakeRequest::allowMethod()
  • Use first argument (string|array) instead of var args for CakeRequest::allowMethod(), AuthComponent::allow(), etc
  • $title_for_layout in favor of $this->fetch(‘title’) and all other deprecated view vars

From 2.4 and below there are also a few left overs that could easily be corrected:

  • FULL_BASE_URL, DEFAULT_LANGUAGE, IMAGES_URL, JS_URL, CSS_URL to Config variables
  • Remove CAKEPHP_SHELL, IMAGES, JS, CSS usage
  • Simplify HtmlHelper::css()
  • Remove Sanitize class usage in favor of a more sane approach
  • Simplify CakeRequest and PaginatorHelper usage with param() if applicable
  • Don’t use request shortcuts anymore – like $this->action in favor of $this->request->action etc
  • Get rid of Model::read() calls – in 3.x this will be get() – I use my Tools plugin MyModel::get() method here already for years
  • Use the new Model::save() syntax for options
  • Completey get rid of named params in favor query strings (Use my Tools plugin CommonComponent and Configure value App.warnAboutNamedParams to detect left-overs)
  • Replace all Set calls with Hash calls, as Set is deprecated. Make sure it is tested thoroughly (as the functionality of Hash might be slightly different).
  • Prevent using Validation::range() in favor of a custom validation rule for inclusive ranges to easier migrate to 3.x – or simply use my Tools plugin MyModel::rangeInclusive() method.
  • Further deprecations in favor of the 2.5+ way to do things

and so on.
For some details see Tips-Upgrading-to-CakePHP-2.x.

My Tools plugin also contains a few further tweaks that can help ease migration from 2.x to 3.x See the full list on the corresponding Wiki page.

This will help making sure any future upgrade is less painful.
Think about it: When you do that now the remaining TODO list will only be half the size and look a lot less intimidating. When the time comes to upgrade to 3.x it will look quite more doable.

My codez is now officially all 2.5 – and as 3.0 ready as it can get 😛

And what always helps a lot is to code clean and structured. A code mess will always be difficult to maintain. So use coding standards and enforce them. Use best practice approaches. Refactor once in a while to prevent a mess from happening.
Happy coding 🙂

 
1 Comment

Posted in CakePHP

 

Freshly baked Cake2.0 tips

31 Oct

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.

 
9 Comments

Posted in CakePHP