RSS
 

Posts Tagged ‘Tips’

CakePHP Tips 2017 Part 1

14 Apr

Whoops as ErrorHandler

I recently switched to Whoops in my apps.
It provides a better exception rendering for development mode for most cases.

Just load the plugin (gourmet/whoops) and modify the bootstrap line:

//(new ErrorHandler(Configure::consume('Error')))->register();
(new \Gourmet\Whoops\Error\WhoopsHandler(Configure::consume('Error')))->register();

If you already have a src/Application.php and run a middleware setup, you also want to make sure you either remove the ErrorHandlerMiddleware there, or provide a custom one for Whoops.

You can test it for example with my sandbox app.
Check it out locally, set it up in minutes and give it a test run.

Jump from browser to IDE

With the Whoops error handler the probably nicest feature is the "click to IDE" one.
The open PR from me enables this.

It allows to click on the class inside the displayed code piece and jump right into the IDE into the exact line of that file displayed in the browser stack trace.

Be more productive leveraging your IDE

Using the new IdeHelper plugin you can now not only bake annotations into new code, but also adjust
your existing code all the time.
The plugin provides a shell to quickly sync the existing annotations with your code.

This way you can maximize IDE compatibility and with that prevent obvious issues and mistakes.
The IDE will usually mark problematic code yellow (missing, wrong method etc).

It can also enable you to click through the classes and object chains which otherwise would just be plain text and have no typehinting.
Finally autocomplete will be available for all the "magically" provided classes and methods this way.

screenshot

This plugin is supposed to work with ANY IDE that supports annotations.
The plugin wiki contains even details and tips/settings for each of those IDEs – collected by the community.

Enable PHPStan in your app and plugins

PHPStan is a super useful static code analysis tool.
It will help you to find issues with your code way beyond what the test suite or IDE syntax checks can do.

For example, if you have two many different return types and only respect one of them, it will mark this as issue.
It will also tell you about interface mismatch or other similar code smells.

Adjust Travis

Add a new matrix row and a script call for that matrix entry:

...
matrix:
  include:
    ...
    - php: 7.0
      env: PHPSTAN=1 DEFAULT=0
...
script:
  ...
  - if [[ $PHPSTAN == 1 ]]; then composer require --dev phpstan/phpstan && vendor/bin/phpstan analyse -l 1 src; fi
...

Level 1 (-l 1) is the lowest level, once everything passes you can go up to level 5 currently. I recommend trying level 3, if possible.

If you need to ignore certain errors, you can use a custom phpstan.neon file in your /tests directory and then append -c tests/phpstan.neon into the command for travis.

vendor/bin/phpstan analyse -c tests/phpstan.neon -l 1 src

For me such a file looked like this, for example:

parameters:
	excludes_analyse:
		- %rootDir%/../../../src/TestSuite/*
	ignoreErrors:
		- '#Call to an undefined method Cake\\Datasource\\EntityInterface\:\:source\(\)#'

Don’t try to ignore too much, though – only "false positives" are recommended to be added here.

You can also write custom PHPStan extensions, see the current CakePHP core /tests/PHPStan folder for details.

Using it locally

I added this to my plugins’s composer.json files:

"scripts": {
    ...,
    "phpstan": "phpstan analyse -l 1 src",
	"phpstan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan && mv composer.backup composer.json"
}

This will allow local testing without messing up your composer file 🙂
For app instead of plugin development you also want to backup-restore the lock file.

Now just run

composer phpstan-setup
composer phpstan

I bet you will find a few useful issues to resolve right on the first run of this tool.

 
No Comments

Posted in CakePHP

 

CakePHP Tips Winter 2016

31 Dec

This is literally my last blog post for the year 2016 🙂

Use the right DateTime typehint

If you are passing DateTime objects around in CakePHP, they are usually Chronos objects,
and either \Cake\I18n\Time or \Cake\I18n\FrozenTime then.
Both those are not suited as typehint usually, as both are fine for most plugin solutions and also
limit the user in what kind of class he uses. At least FrozenTime would be a bit better then Time.

But what would be the correct typehint? Use DateTimeInterface from PHP.

public function addRow(DateTimeInterface $date, $content, $options = []) {
    $day = $date->format('d');
    ...
}

It is fairly simple to typehint that we need a DateTime object without unnecessarily restricting the plugin to a specific subset.

Note: This is only valid if you are not modifying the object, otherwise use the concrete one. But you really should try to code "immutable" and rather clone and then modify without altering the original value.

Nullable database fields

Nullable fields in the DB should usually either be null or have a concrete value. For foreign keys as well as most integer, float and even string fields that means, that an empty string as input coming in should be transformed to this null value then for data integrity.
Unfortunately, the CakePHP ORM doesn’t do that out of the box for some fields.

Why is it an issue? Well, imagine you want to query for all with an empty value for a field.
Instead of a simple 'field IS' => null you then need to do a more complex ['OR' => ['field IS' => null, 'field' => '']] all the time.
And if you forget it in a single query they results will be inconsistent. That’s kind of bad.

The Shim.Nullable behavior can help here.
Once attached, it will take care of those values when saving and make sure you don’t end up with different null values in your DB.

Leverage Composer scripts

The new cakephp/app skeleton contains some new interesting gotchas regarding composer scripts:

    "scripts": {
        ...
        "check": [
            "@test",
            "@cs-check"
        ],
        "test": "phpunit --colors=always",
        "cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests",
        "cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests"

That basically standardizes and simplifies the way the test and sniff calls are executed.
Before it was php phpunit and a long vendor/bin/phpcs ... call, now it is always just composer test or composer cs-fix etc.
Even doing together now is easy with composer check.

My apps and plugins use the PSR2R sniffer version, but from the outside the commands are completely identical, which is nice.

Note: The vendor/bin/ prefix part (as you might have expected) is not necessary as Composer will still find the scripts just fine.

Testing different databases

Locally, testing the CakePHP core for example defaults to SQLite.
The other databases, as they would need to have a valid connection, are skipped.

But if you are working on a specific feature that would need those to test if it is working, you would want to use that one instead.
Thanks to getenv() usage we can easily exchange the connection via db_dsn.

In your console:

export db_dsn="mysql://root:secret@127.0.0.1/cake_test"

This expects a "cake_test" database set up, and an access with the username root and the password secret (vagrant default for CakeBox here).

For Postgres it would probably be something like postgres://postgres@127.0.0.1/cakephp_test.
CakeBox also ships with that out of the box.

When you now run php phpunit.phar from now on, it will use that connection until you switch it again (for that session).

 
2 Comments

Posted in CakePHP

 

CakePHP Tips Fall 2016

18 Oct

Always use proper version check method

Be aware of version checks that are using the inferior and even wrong </> comparison.
This can lead to wrong conditions with higher minor versions.

I had this in my code from the early 2.x days:

if ((float)Configure::version() < 2.4) {
    ...
}

With the upcoming 2.10 release this will now suddenly start to return true. Luckily my 2.next tests already revealed that and I was able to fix it up properly:

if (version_compare(Configure::version(), '2.4') < 0) {
    ...
}

This will now work in all minors of 2.x.

The same is true for 3.x, of course, even though we are "only" at 3.3.6 at this point in time yet.

Order your flash messages semantically

You can use the Flash plugin to enhance your flash messages a bit.
It works out of the box with your current setup, but adds a few useful things on top:

  • Prevent size and output issues when running into a redirect loop boiling up messages in the session (>> 50) by limiting the stack to 10 by default.
  • Use ordered output (error first, then warning, success and lastly info). Errors should be visible first in case multiple types are to be displayed.

Switch away from basic log files

I would switch away from basic log files to database – even if it is just a simple SQLite one like DebugKit uses.
This way you can more easily navigate and filter through them.
Check out the DatabaseLog plugin which can do exactly that in just a few minutes of configuration.
For not just personal fun apps it is advised to look into a bit more sophisticated approaches, like using Monolog and services like NewRelic to write to. But for smaller projects it can be enough to have a small admin backend to filter through the logs, especially error and warning types.

Keep your controller code lean

That specifically includes all those verbose allow statements to make certain pages public:

use Cake\Event\Event; 
...
 	/**
	 * @return void
	 */
	public function beforeFilter(Event $event) {
		parent::beforeFilter($event);
		$this->Auth->allow('index');
	}

See TinyAuth which since recently allows to more cleanly declare this with a single INI file in your src/config folder.
The complete code above goes away for every controller 🙂

As long as not needed (custom dynamically made decisions based on session or alike) this kind of noise should be kept out of the controllers.
This makes it also possible to modify the public access to actions without the need to be a developer. The next step could be to make the input for TinyAuth database-driven and alike.

 
No Comments

Posted in CakePHP

 

Use isset() and empty() wisely

01 Jun

This is another part of the series "How to write better PHP code".

Sometimes it is the small things than can make all the difference. In this case simply using the correct way of checking
for variable content can avoid quite a few bugs in the long run.

What is the issue here

Very often people abuse empty()/!empty() and isset()/!isset() where they shouldn’t. I have seen this in many frameworks,
and in retrospective also in my own code for quite some time.

The main problem is that it does two things at the same time:

  • Checks if that variable exists at this point in time, if not silently bail early
  • If it exists it also checks of the content is not empty or set

Now, that sounds like "so who cares" – but it in fact has quite some impact in the correctness of code, especially when refactoring is involved.

Concrete issue

There was an actual bug in the CakePHP framework because of this.
The class attribute $compiledRoute was renamed, but a single use of that one was checked with !empty(), so that part of the code inside this check became unreachable and dead forever. Since this one piece of code didn’t have a test for it, it also remained dormant for a very long time.

Buggy behavior with methods

Since PHP 5.5+ it is now even possible to check expressions and more, so !empty($this->foo()) would be possible.
This should never be used though as this can have side-effects depending on the implementation of the class and is absolutely not necessary.

So what to do

The basic rule is: Always prefer non-silent checks.
Also always try to compare with === checks and expect the variable or attribute to be declared at run-time.

Only ever use empty()/isset() if that is not possible, that means if there is a chance that the variable or attribute could be not set at this point in time.
Same goes for array keys and alike.
But for variables and attributes you might want to ask yourself it that is maybe not a code smell after all if you do not know that for certain.

Luckily IDEs these days provide a lot more help than a few years ago, but there is still lot of room for error.
Let’s look into more concrete examples now.

Examples

Method arguments

public function read($key, $context = null) {
    ...
    if (!empty($context)) {
        // Do something
    }
    ...
}

What happens now if you refactor $context in the signature, but forget the one inside the if check?
Using this approach would have 100% identical behavior while making sure you couldn’t have forgotten it:

public function read($key, $context = null) {
    ...
    if ($context) {
        // Do something
    }
    ...
}

Ideally you would also look into more strict checks if applicable, but this is already way better than before.

Class attributes

protected $foo;
public function read($key) {
    ...
    if (isset($this->fooo)) { // Notice the typo
        // Do something (oh no, this will never happen now)
    }
    ...
}

While coding, we made a typo and never notice it. If you also don’t bother to write a test for this case, you might only discover it very late in the process, if at all.
So it should look like:

protected $foo;
public function read($key) {
    ...
    if ($this->foo !== null) {
        // Do something
    }
    ...
}

Renaming only one of the two would immediately trigger an error and the developer gets notified about his coding mistake.

Array keys

protected $config = [
    'recursive' => false
];
public function read($key) {
    ...
    if (!empty($this->config['recursive'])) {
        // Do something
    }
    ...
}

We can see that the key must exist. If not, we screwed up. So why using the wrong check that would only cloak spelling mistakes.

Better code like:

protected $config = [
    'recursive' => false
];
public function read($key) {
    ...
    if ($this->config['recursive']) {
        // Do something
    }
    ...
}

Optional array keys

public function read($key, array $options = []) {
    ...
    if (!empty($options['recursive'])) {
        // Do something
    }
    ...
}

In this particular case the recursive option is optional, thus the key could be empty here.
This would be OK.

One way to maybe improve this would be to merge the $options array with some $defaults which contain all required keys, then you can also use the proper non-cloaking checks again.

More examples

Don’t wrap methods, especially for "object or null" return values:

// Bad
if (!empty($this->getSomeObjectOrNull()) {}
// Good
if ($this->getSomeObjectOrNull() !== null) {}

I also often see the redundancy that makes no sense:

// Bad
if (isset($x) && !empty($x)) {}
// Identical (but maybe we also don't need the !empty here?)
if (!empty($x)) {}
// Better (maybe even strict checks?)
$x = null;
...
if ($x) {}

Usually variables should not just sometimes exist. Declare them as null first if you conditionally populate them somewhere.

Bear in mind here, that 0 and '0' would not work with either way and in that case you need to be strict in your checks anyway.
There also many buggy comparison cases around the 0 integer and a reason more to try to be more strict in general wherever possible.
That includes in_array() by the way, one of the functions probably most (framework and project) bugs have been discovered the last months due to this implicit casting issue leading to false positives.

Further reading

There was an RFC discussion in CakePHP around the same issue, as direct result of a bug that would never have been there if the above was applied properly.

Please share your thoughts on the topic as comments.

 
No Comments

Posted in PHP

 

CakePHP Tips 2015

13 Sep

Most tips are for 2.x and 3.x as they have been applied to both versions.

Complex radio inputs

A while back I posted a tip about deep array options for select input form fields.
With my recent path for 2.6 and 3.0 (>= 3.0.8) this is now also possible for radio() fields:

echo $this->Form->input('Model.field', [
	'type' => 'radio',
	'legend' => false,
	'div' => false,
	'options' => [
		'1' => ['name' => 'Option A', 'title' => 'A Title'],
		'2' => ['name' => 'Option B', 'data-foo' => 'bar']
	]
]);

Filtering

As of now, there is no way to directly disallow sort fields of pagination views.
There is a hack in 2.x to workaround this, though. Just set the whitelist to a non-existent field:

['foo']

This way the whitelisting is triggered and no valid field can be found. Mission accomplished.

In 3.x (>= 3.0.8), finally, we have an easy way of doing this, by passing an empty array for sortWhiteslist:

'sortWhitelist' => []

Not passing any whitelist will just not trigger the whitelisting at all.

Using the 3.x Migrations plugin for your 2.x app

I have some rather large 2.x apps that will not be migrated any time soon. But I at least want to leverage some of the 3.x stuff already where possible.
So I can include subsplits of the 3.x version via composer.
But for CLI console scripts and the migrations plugin this does not work.
I don’t want to use plain SQL upgrade files, though. And for any other migration solution I would probably also need some third party tool.
Why not going with what CakePHP 3.x seems to be perfectly bundled with? The Migrations plugin 🙂

So inside my 2.x app I create a subfolder upgrade where I put a very basic composer.json:

...
"require": {
	"cakephp/cakephp": "dev-master",
	"cakephp/migrations": "dev-master",
	"cakephp/bake": "dev-master"
},
"require-dev": {
	"cakephp/debug_kit": "2.*"
},
"autoload": {
	"psr-4": {
		"App\\": "src"
	}
},
"autoload-dev": {
	"psr-4": {
		"App\\Test\\": "tests",
		"Cake\\Test\\": "./vendor/cakephp/cakephp/tests"
	}
},
...

I basically check out the 3.x version in a subfolder along with the 2 plugins I need.
I also make sure, the cli folder and the config folder are present and working.

Then I modify the app.php to automatically pull the DB credentials from my root Config/database.php.
Since I don’t want to duplicate the configs.

$file = dirname(dirname(__DIR__)) . DS . 'Config' . DS . 'database.php';
if (!file_exists($file)) {
	throw new \Exception('Cannot find database.php');
}
$content = file_get_contents($file);
preg_match('/\'database\'\s*=\>\s*\'(.*)\'/i', $content, $matches);
$database = !empty($matches[1]) ? $matches[1] : '';
preg_match('/\'login\'\s*=\>\s*\'(.*)\'/i', $content, $matches);
$username = !empty($matches[1]) ? $matches[1] : '';
preg_match('/\'password\'\s*=\>\s*\'(.*)\'/i', $content, $matches);
$password = !empty($matches[1]) ? $matches[1] : '';
preg_match('/\'host\'\s*=\>\s*\'(.*)\'/i', $content, $matches);
$host = !empty($matches[1]) ? $matches[1] : 'localhost';
return [
	...
	'Datasources' => [
		'default' => [
			'host' => $host,
			'username' => $username,
			'password' => $password,
			'database' => $database,
		],
	],
	...

I can verify if it all works by running bin/cake bake migration_snapshot Init to make a snapshot of the current DB schema in the new migration code.
This will also automatically mark this migration file as migrated.
Now I can simply add more migration files with bin/cake migrations create {Name} and then migrate the DB via bin/cake migrations migrate.

Voila!

 
No 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

 

CakePHP Tips – Summer 2013

30 Jul

While CakePHP 2.4 is now just around the corner (already beta), a few tips gathered the last few days.

Don’t mix controller and component pagination

Currently, if you mix controller pagination setup and call the Pagination component then, it can easily backfire as the two different
settings are not always merged as you would expect it to. So don’t mix em.

Either use the controller only:

public $paginate = array(...);
// and
$this->paginate = array(...) + $this->paginate;
$results = $this->paginate();

or the component only:

$this->Paginator->settings(array(...));
$results = $this->Paginator->paginate();

My recommendation: Stick to controller pagination for now (as we all have the last years) as it seems it won’t be deprecated after all. It is a nice
convenience access to the component anyway.

PS: If sticking to controller pagination, add this rejected PR snippet to your AppController to avoid the notice thrown.

Test your code

Even if you don’t assert the outcome.

"But that is time consuming. I dont have time!"

Yeah, sometimes you are in a hurry and can’t properly assert all tests. Still no excuse.
Just be running the code via "dummy call" and having the test not through any notices/warnings/errors you already have a very basic level of testing. And it really doesn’t need time to set such a basic test run up.
As soon as there is time you can then still address the proper result. But until then you at least know that no line of code is suddenly gone missing or variables have
accidentally been renamed etc.
Such a basic "run-through" test is still better than no test at all:

/**
 * Test myMethod()
 * 
 * @return void
 */
public function testMyMethod() {
	$this->MyClass->myMethod();
	//TODO: assert return value etc
}

Return your redirects

I always used to bake my templates with simple redirect() calls:

if ($this->Model->save()) {
	$this->redirect(...);
}

But, especially with testing your code, this might have unexpected results, as there you cannot exit, but need to return early (by overwriting the redirect method in your test case file).

The cookbook now states this, as well:

When testing actions that contain redirect() and other code following the redirect it is generally a good idea to return when redirecting. The reason for this, is that redirect() is mocked in testing, and does not exit like normal. And instead of your code exiting, it will continue to run code following the redirect.

So it’s wiser to cleanly return here, as well:

// in your controller action
if ($this->Model->save()) {
	return $this->redirect(...);
}

Even if for your productive environment the return will be superfluous, the test cases will not return wrong results.

I just recently adjusted my bake templates and it has also been changed in the 2.4 branch of the Cake core. I advice you to update your files, as well. You can use my Upgrade shell task "controllers" to do that as a batch job for your app and plugins:

// for your app
cake Upgrade.Upgrade controllers
// for a plugin FooBar
cake Upgrade.Upgrade controllers -p FooBar

Return your shell errors

Same applies here: Testing does not halt the code. Therefore you also need to return early here:

public function some_command() {
	if (!$this->foo()) {
		$this->error('Abort');
	}
	...

becomes

public function some_command() {
	if (!$this->foo()) {
		return $this->error('Abort');
	}
	...

This is also covered by my Upgrade shell now.

Debug SQL queries by throwing exceptions

Ever worked on an ajax, csv or even a redirect action where there will be no debug SQL bar at the bottom etc?
You can easily display your SQL queries up to a specific point by throwing an exception:

$someRecord = $this->Model->find(...);
 // We just add this snippet here to halt and see the actual SQL query before we continue:
throw new Exception();
if (!$someRecord) {
	return $this->redirect(...);
}
return $this->redirect(...);

This halts the code, shows the error layout with all the queries listed below. Neat, isn’t it? And it is quite fast and works anywhere in your application.

Switch from constants to a more flexible way of configuration

In our case we can leverage the Configure class and its read() and write() methods to do so. Better than constants anyway as they can only be defined once and not changed anymore.
So it’s only reasonable that the 2.4 core deprecated quite a few of those constants like FULL_BASE_URL, DEFAULT LANGUAGE, CAKE_SHELL etc.
Using Configure we can store a default value and overwrite it any time after that with a different value if we need/want to.

In the past I also caught myself using constants where Configure would have sufficed. Partly to make myself believe my app will be faster this way.
The speed advantage is so minimal though that it does not outweigh the reduced flexibility that comes with it.

So instead of using define('FULL_BASE_URL', 'http://example.com') you can now use Configure::write('App.fullBaseURL', 'http://example.com'); and overwrite it again later on.

Same goes for any settings you might want to set. Chose a meaningful namespace to group them, though:

Configure::write('Config.someKey', 'somevalue'); // with Config as namespace

You can read it anywhere in your app as always, of course:

$key = Configure::read('Config.someKey'); // in this case probably boolean (1/0 alias true/false)
if ($key) {
	// some code here
}

Change your default validation error message

It will be changed in 3.x from "This field cannot be left empty" to "The provided value is invalid".
This makes sense as in most cases it would display this error message when a value is provided but does not pass the specific validation rule (like numeric or email).
This is highly confusing then.

'email' => array(
	'notEmpty' => array(
		'rule' => array('email')))

This example would just fallback to "This field cannot be left empty" here – both on empty submit and if you input some invalid email.

So you should already address that in 2.x by always defining the error message for your rules with the above wording. Clearer validation error messages will help your users to correct their mistakes and submit the form properly. Only use "This field cannot be left empty" for actual "notEmpty" cases then:

// in your model
public $validate = array(
	'email' => array(
		'notEmpty' => array(
			'rule' => array('email'),
			'message' => 'The provided value is invalid' // or even 'This is not a valid email address'
		)
	),
	'postal_code' => array(
		'notEmpty' => array(
			'rule' => array('notEmpty'),
			'message' => 'This field cannot be left empty',
			'last' => true
		),
		'exists' => array(
			'rule' => array('validatePostalCodeExists'), // custom validation rule
			'message' => 'The provided value is invalid', // or even 'This postal code is not valid'
		)
	)
);

Also try to use the verbose array declaration and don’t forget 'last' => true for consecutive validation rules (as in postal_code above).

Text(area) to paragraphs

Use the new Cake2.4 method TextHelper::autoParagraph() to display your textarea input. It will automatically format single line breaks to <br /> and double ones to <p> blocks.
If you are still on 2.3, you can copy this method over into a TextExt helper, for example, and use aliasing to map it back to "Text" helper alias. When upgrading you won’t have to change your code then a bit 🙂

Important: Do not forget that this method does not escape your output. Thus you need to use h() for (potentially unsafe) user input:

echo $this->Text->autoParagraph(h($text));

h() will effectively remove any XSS dangers as well as defuse any HTML chars like < or > that could otherwise destroy the layout (but are totally valid chars for quoting in some languages).

Read more about output and security in CakePHP here.

Check if you need the hardcoding of your core lib path

By default CakePHP "bake project" for new apps will hardcode the paths in your index.php and test.php – if you don’t have CakePHP in your include_path. Which most don’t, especially when maintaining multiple different Cake versions and apps.
So you might want to (I do anyway) comment those lines out again after baking the project:

define('CAKE_CORE_INCLUDE_PATH', '/some/hardcoded/path');
// to
//define('CAKE_CORE_INCLUDE_PATH', '/some/hardcoded/path');

You will stumple upon that when deploying your scripts. So better take care of it asap.

Check out the 2.4 core

While it is still beta, you can test it already, though. I even use it for new apps (Disclaimer: don’t do that if you are new to CakePHP!).
You can test the new features or how your 2.3 app would run with it. You can realize quite early this way where potential pitfalls are.
In the past I often discovered some "bugs" this way that then could easily be fixed or adjusted before your code actually some day depends on it.
While still in beta you might also be able to add some additions to new features before you will have to wait for 2.5 again or something.
So it’s worth to at least play around with it now.

Also keep an eye on the 2.4 migration guide and maybe even 3.x migration guide as well as 3.x chatter and how your code would be affected or would have to be modified. It might save you some time in the upgrade process if you can already eliminate some of the necessary changes in advance.

A very good example is the removal of the named params in favor of query strings. Don’t start to use those for new apps. Use the future proof query strings even for your 2.x apps.

One example for 3.x, that is not yet officially mentioned yet, is the possible deprecation and removal of Model::read(). It is a method I have always been preaching to avoid. Especially if you are a beginner, but also if you can (and you always do) use find() instead. The cookbook now also states this in a warning.

I never used it once and it seems that there is very high possibility that it will be removed in 3.x. So if you catch yourself using it today, think about this and if you might be able to replace it with the find() wrapper from here on 🙂 To be on the safe side.

 
 

CakePHP Tips

22 Jan

All new CakePHP tips collected over the last few weeks.

Dispatcher execution order

Tested on Cake2.3RC:

  • APP/webroot/index.php
  • CORE/bootstrap.php
  • APP/Config/core.php
  • APP/Config/bootstrap.php
  • dispatchers defined in CORE bootstrap
  • APP/Config/routes.php
  • APP/Config/database.php
  • controller and all the rest

It is important to know that the dispatchers will be triggered before your routes are even loaded.
If you enabled a dispatcher like the CacheDispatcher, the last three elements might not even be triggered anymore (if the cached view file can be found) and the content might directly get sent at this point.

Resolve dispatcher conflicts

So if you implemented some routing for subdomains or other domains locked on the same application you need to make sure that the CacheDispatcher, for example, does not create a conflict. You can use the new Cache.viewPrefix (cake2.3) here in your bootstrap to not serve the wrong cached file here.

Callback execution order

Sometimes the execution order can be pretty important. At this point it is good to know what callback is triggered at what point – and the order is what you need/expect.

Note: Don’t trust what somebody tells you. Do it yourself. So instead of just believing my post here, execute the code yourself, if possible. You can have yourself some neat little callback execution tests for times where you need them.

Behavior/Model callbacks

// on save
Array
(
    [0] => TestBehavior::beforeValidate
    [1] => TestComment::beforeValidate
    // validate
    [2] => TestBehavior::afterValidate
    [3] => TestComment::afterValidate
    [4] => TestBehavior::beforeSave
    [5] => TestComment::beforeSave
    // save
    [6] => TestBehavior::afterSave
    [7] => TestComment::afterSave
)
// on find
Array
(
    [0] => TestBehavior::beforeFind
    [1] => TestComment::beforeFind
    // find
    [2] => TestBehavior::afterFind
    [3] => TestComment::afterFind
)
// on delete
Array
(
    [0] => TestBehavior::beforeDelete
    [1] => TestComment::beforeDelete
    // delete
    [2] => TestBehavior::afterDelete
    [3] => TestComment::afterDelete
)

Controller/Component callbacks

Array
(
    [0] => TestComponent::initialize
    [1] => TestController::beforeFilter
    [2] => TestComponent::startup
    [3] => TestController::test // action itself
    [4] => TestComponent::beforeRender
    [5] => TestController::beforeRender
    // rendering view
    [6] => TestComponent::shutdown
    [7] => TestController::afterFilter
    // output
)

This makes sense. The components first initialize themselves and might modify the controller’s "init" state prior to its dispatching of beforeFilter.
Then the components do their work before and after the action itself and at the end afterFilter is invoked prior to outputting the result.

When actually redirecting the following callbacks will not be executed anymore, though:

// in case of a redirect:
    [0] => TestController::redirect();
    [1] => TestComponent::beforeRedirect();
// redirect if not returned false, otherwise continue

Note: The redirect will only happen if the component’s callback does not return false here.

Helper callbacks

This is a little bit more tricky. The templating in cake uses a two-pass-rendering. First the view will be rendered and then it will be "injected" into the layout. So you cannot just echo debug output, you need to log the execution order if you want correct results here.

Using my test case for it, we get:

Array
(
    [0] => TestHelper::beforeRender
    [1] => TestHelper::beforeRenderFile
    // render view
    [2] => TestHelper::afterRenderFile
    [3] => TestHelper::afterRender
    [4] => TestHelper::beforeLayout
    [5] => TestHelper::beforeRenderFile
    // render layout and insert rendered view into "content" block
    [6] => TestHelper::afterRenderFile
    [7] => TestHelper::afterLayout

Interesting is, that beforeRenderFile and afterRenderFile are invoked for each file. If you include elements they will also be invoked for them. They are quite handy if you want to directly modify the rendered result in some way. This can also be a decision maker on whether to make some "markup snippet" an element or a helper method. Elements can also be cached, as well.

Note: beforeRender and afterRender call also be additionally invoked for each element. But you would need to manually enable those (default is false).

Test case callbacks

Please see this post for reference.

"Indirect modification" for pagination

If you still happen to use the 1.3 syntax in your 2.x apps, you might have run into something like this:

Notice (8):
Indirect modification of overloaded property PostsController::$paginate has no effect [APP/Controller/PostsController.php, line 13]

I wanted to fix this in the cake core controller, but it seems, it might be wiser to upgrade to the new PaginatorComponent syntax. If that is not possible, you can easily avoid this notice by adding this to your AppController:

/**
 * The paginate options for this controller
 *
 * @var array
 */
public $paginate = array();

This way the array is defined and accessing it the "old" way will work smoothly.

Some new PHP "flaws" I stumpled upon (and how they affect your cake app)

Yeah, there really are some ugly truths to PHP sometimes. Most can be avoided or resolved easily, though (if known!).
The following one for example has been in Cake for years (until 2.3 stable) in the FormHelper – until someone finally ran into the issue. And there will still be lots of other places where PHP itself creates a mess if not handled accordingly.

Don’t use in_array with mixed integers and strings

$result = in_array(50, array('50x'); // returns TRUE (usually unexpected)
$result = in_array('50', array('50x'); // returns FALSE (expected)
// the other way is the same (shit):
$result = in_array('50x', array(50); // returns TRUE (usually unexpected)
$result = in_array('50x', array('50'); // returns FALSE (expected)

As you can see, using the first argument without any casting can result in some unexpected results if you do not know it.
You can also use true as third param to make the comparison strict:

$result = in_array(50, array('50f5c0cf-5cd8', true); // returns FALSE (expected)

There might be some case where the above "strange" result is the expected – but in most cases probably not. It sure is one the most stupid PHP flaws I have ever seen. The string 50x is never numeric and therefore should never be casted to a numeric value here IMO.

You can also use yourself a neat little Utility method that actually works as expected here (at least the test cases pass now): Utility::inArray().

Use STRICT comparison where possible, but ALWAYS for strings

list($var1, $var2) = array("1e1", "10"); // clearly two different strings
var_dump($var1); 
var_dump($var2);
var_dump($var1 == $var2);
var_dump($var1 === $var2);
// Result:
string(3) "1e1"
string(2) "10"
bool(true) // !!!
bool(false)

Probably the most evident example is:

$x = 0;
$y = 'foo';
if ($x == $y) {
    echo 'same';
} else {
    echo 'NOT same';
}

What do you think this echos? Yeah, "same" due to the implicit int cast happening here!

Just to be clear: This is not just a bug (although I see it as one – as its the root cause of the above in_array issue!) – this is "expected behavior" in all PHP versions (including PHP5.4 and above). Unfortunately even more advanced (cake) programmers do not see the need to use strict comparison for strings. Even though, there are no downsides at all. The opposite, it is slightly faster and always more correct. I started to revise my own code regarding this just a few weeks back and also went ahead and fixed quite a few of those flaws in the core and some plugins lately.

For other types like integers or arrays it is also wise to switch to strict comparison wherever possible. It usually improves code quality in a language that is sometimes just to lose regarding type-safety. And it triggers errors/warnings earlier than it would without it if you do something wrong. So it also might cut down development time in the long run.

Some more examples:

$count = count($users); // returns int
if ($count === 1) {} // always an integer

The $count must be of the type integer (does not come from user input or database) here and therefore can be uses with strict comparison.

public function foo(array $someArray) {} // you cannot pass strings, booleans, integers, ... now

If you always expect an array you can use array typehinting. Just bear in mind it will also reduce the flexibility to pass objects witch implement ArrayAccess interface. If you only use it internally you are pretty safe to use the typehint here, though.

PhpMyAdmin

Quite a useful tool most of us use probably, locally and on the webserver.
But the default settings are usually quite annoying.
Open up /etc/phpmyadmin/config.inc.php (or similar path on other systems) and edit it accordingly:

// Maybe raise the timeout value from 5 min to at least a few hours
$cfg['LoginCookieValidity'] = 36000; // In seconds 
// The default of 30 rows per page is also a little bit low maybe
$cfg['MaxRows'] = 100;
 
4 Comments

Posted in CakePHP

 

Some new crazy (Cake)PHP tricks

01 Mar

Crazy – because these tips are not used that often probably. But for some rare occasions they might come in handy.

Inserting some rows into an array

$array = array(
	'one',
	'two',
	'three'
);
$newValue = 'one-point-five';
//with 0 as 3rd arg we can actually add values at the position of arg 2
array_splice($array, 1, 0, array($newValue));

Note that the 4th argument must be an array for this to work.

Using the same value for multiple select options

With a simpel key/value list and select() you won’t get far. You cannot use the same key twice in an array. That is a common beginner mistake.
To overcome this you need a bit more verbose array here:

$options = array(
 	...
 	array('name' => 'United states', 'value' => 'USA'),
	array('name' => 'USA', 'value' => 'USA'),
 );
 
 echo $this->Form->input('test', array('type'=>'select', 'options'=>$options));

This will then print

...
<option value="USA">United states</option>
<option value="USA">USA</option>

See the docs for more infos on select().

Setting additional attributes for some select options

You might want set some option elements to a specific class. The FormHelper::select() is powerful enough to support this:

// normal options
$options = array(1 => 'One', 2 => 'Two', 3 => 'Three');
// advanced (second elements has a class "extra")
$options = array(
	1 => 'One', 
	2 => array('name' => 'Two', 'value' => 2,  'class' => 'extra'), 
	3 => 'Three');
echo $this->Form->input('test', array('type' => 'select', 'options' => $options));

The result:

<div class="input select">
	<label for="ModelTest">Test</label>
	<select name="data[Model][test]" id="ModelTest">
		<option value="1">One</option>
		<option value="2" class="extra">Two</option>
		<option value="3">Three</option>
	</select>
</div>

You might want to check this site here if the styles you want to apply work in all browsers. Usually only some styles like background-color are apply-able cross-browser.

Autofocus

Not so much CakePHP as maybe HTML5 – but pretty useful for login forms:

echo $this->Form->input('login', array('autofocus'=>'autofocus'));

All new browsers support it out of the box. The user can start typing right away (no additional click necessary).

More HTML5 out-of-the-box

In general HTML5 is great, but still not fully supported by all browsers – or users just don’t update to a new version.
So we shouldn’t completely rely on those features. But where – as the above autofocus – the absence in unsupportive browsers doesn’t bother anyone we can easily apply those features for users with modern browsers.

Additionally, we can use fallback-js to "simulate" the behavior if not natively supported. I put together a well-working HTML5-fallback script for jquery. It checks if the browser supports a certain feature and if not it creates a JS workaround.

I bet there are lots of other similar implementations. The important part for those placeholders is that on submit the content needs to be cleared again. The placeholders must not be posted (otherwise your validation rules might not work as expected anymore). I used the $('form').live('submit', function() {} event to clear the forms in time.

echo $this->Form->input('url', array('placeholder'=>'http://'));

All these snippets work in all browsers. In those modern ones a little bit faster. But that’s about all. You can already use it without any worries. And some day you can just turn of the fallback.

Structure your controllers

UsersController is often one of the most bloated controllers having tons of actions and views.
At some point this might get very confusing with so much code in a single file.

You don’t have to put all 40ish actions that are related to the User model in a single UsersController. Divide them to keep things clean and clear. So for me this worked pretty well:

  • AccountController (login, logout, register, change_pw, edit, …): $uses = array('User')
  • MembersController (index, search, profile, mail, friend, …, admin backend actions): $uses = array('User')
  • OverviewController (index, admin index, … – specially routed): $uses = array('User')

As you can see they all use the user model but they separate the actions by category. This way the file stays kinda short and is better understandable in the long run. Imagine all those actions in a single controller. No way 🙂

But also bear in mind that you should not start dividing everything into tiny little pieces. Only do it where it makes sense.

Controllers without models

Until today you coudn’t just set $uses to false in Cake2. What used to work in 1.3 will fail hard in 2.x. It was a pretty annoying problem I reported right away (but it needed a second one to actually get things into motion). If you just used isset($this->Model) it would try to load a non-existent model and die. The current head now contains the new fixed version which works now the way one would expect it to work:

  • true Use the default inflected model name.
  • array() Use only models defined in the parent class.
  • false Use no models at all, do not merge with parent class either.
  • array('Post', 'Comment') Use only the Post and Comment models. Models will also be merged with the parent class.

The default value is true.

So basically, if we don’t want to use any models (or at least no default model):

class TestController extends AppController {
	public $uses = array();
}

Note: only use false if you are certain the extended controllers (the AppController usually) don’t rely on any models. Otherwise it will break this functionality. That is why array() usually suffices.

 
7 Comments

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