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;
Another interesting case I stumpled upon once:
Did you read the "Probably the most evident example is" part and the example below? Thats exactly your example.
It’s pretty simple: The second one will be casted to int – and results in the same 0 as what you are comparing against.
Hi!
How did you get the array to print out the order of the dispatcher?
Simply by putting CakeLog::write() statements in there.. 😉