RSS
 

Posts Tagged ‘Tips’

CakePHP Tips

22 Jan

All new CakePHP tips collected over the last few weeks.

Dispatcher execution order

Tested on Cake2.3RC:

  • webroot/index.php
  • Config/core.php
  • Config/bootstrap.php
  • dispatchers defined in core/bootstrap
  • Config/routes.php
  • 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
    TestController::beforeRender
    // rendering view
    [4] => TestComponent::shutdown
    [5] => 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 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.

 
2 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 = false;
}
 
2 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

 

Very useful CakePHP Code Snippets

06 Jun

A small collection of useful Cake Snippets for all projects made with Cake.

First of all: Keep it DRY

If you have several projects I recommend extending vendor classes.

require_once(VENDORS.'my_model.php');
class AppModel extends MyModel {}

Same for AppController, AppHelper, bootstrap and CO. This way you have a common library of those app files and don’t repeat yourself.

UPDATE 2012-04-28 ms

For 2.0 I recommend using Libs instead and App::uses() to include them.

Better feedback

Put this in your AppModel to be notified about mistakes in model associations:

public function __construct($id = false, $table = null, $ds = null) {
    parent::__construct($id, $table, $ds);
 
    # avoiding AppModel instances instead of real Models without telling you about it
 if (!is_a($this, $this->name) && $this->displayField  !== 'id' && !Configure::read('Core.disableModelInstanceNotice')) {
        trigger_error('AppModel instance! Expected: '.$this->name);
    }
}

It triggers a warning for each relation that is not properly set. $this->displayField !== ‘id’ prevents HABTM models to trigger this. The reason this is necessary is that Cake automatically (silently!) falls back to AppModel (instead the expected one!) for invalid models. This can cause callbacks not to be triggered – unnoticed, of course! This happens a lot with plugins. If you don’t use plugins a lot you might not run into this problem as much as I, for example, did. I moved some modules into a plugin and forgot the “Plugin.Model” syntax at some point and never really spotted the error.

SQL queries anywhere in the app?

Did you ever wish to debug SQL queries from find or save calls in actions which don’t have a view or redirect right afterwards? There are two options: log those queries into a file or dump it right in the controller and die. With my CommonComponent method here I can do both (using $die parameter to switch between them):

/**
 * quick sql debug from controller dynamically
 * or statically from just about any other place in the script
 * @param bool $die: TRUE to output and die, FALSE to log to file and continue
 */
function sql($die = true) {
    if (isset($this->Controller)) {
        $object = $this->Controller->{$this->Controller->modelClass};
    } else {
        $object = ClassRegistry::init(defined('CLASS_USER')?CLASS_USER:'User');
    }
 
    $log = $object->getDataSource()->getLog(false, false);
    foreach ($log['log'] as $key => $value) {
        if (strpos($value['query'], 'SHOW ') === 0 || strpos($value['query'], 'SELECT CHARACTER_SET_NAME ') === 0) {
            unset($log['log'][$key]);
            continue; 
        }
    }
    # output and die?
 if ($die) {
        debug($log);
        die();
    }
    # log to file then and continue
 $log = print_r($log, true);
    CakeLog::write('sql', $log);
}

Assuming that you at least got a (real) User Model of some sort – because it doesnt work with the AppModel. In my case I needed it in a behavior for example: CommonComponent::sql();

Displaying blob content

At some point it might be better to display the images inline as blob. Especially if there are a lot of very small ones or if they come from the DB or whatever:

/**
     * display image tag from blob content
     * enhancement for HtmlHelper
     * @param binary $content
     * @param array $options
     * @return string $html imageTag
     */
    function imageFromBlob($content, $options = array()) {
        $text = 'data:image/png;base64,' . base64_encode($content);
        $image = sprintf($this->tags['image'], $text, $this->_parseAttributes($options, null, '', ' '));
        return $image;
    }

Always redirect after posts which changed the database

Put this in a component:

/**
 * @see http://en.wikipedia.org/wiki/Post/Redirect/Get
 * 2011-06-14 ms
 */
public function postRedirect($whereTo, $status = 303) {
    $this->Controller->redirect($whereTo, $status);
}

Example:

if ($this->Model->saveAll($this->data)) {
    $this->Common->postRedirect(array('action'=>'index'));
}

Misc. snippets

Easier control over your page titles and meta tags Let your app automatically set the title via callbacks for pages which you didn’t manually specify.

This piece of code should be one of the last things of beforeRender() in your AppController:

# default title
if (empty($this->pageTitle)) {
    $this->pageTitle = __(Inflector::humanize($this->action), true).' | '.__(Inflector::humanize(!empty($this->params['controller'])?$this->params['controller']:$this->name), true);
}
$this->set('title_for_layout', $this->pageTitle);

Now you can manually set your page titles from the specific actions by using $this->pageTitle (as you used to in cake1.2) as well as manually in the view (overriding $title_for_layout).

Truncating a table

/**
     * truncate TABLE (already validated, that table exists)
     * @param string table [default:FALSE = current model table]
     */
    function truncate($table = null) {
        if (empty($table)) {
            $table = $this->table;
        }
        $db = &ConnectionManager::getDataSource($this->useDbConfig);
        return $db->truncate($table);
    }

This code goes in your AppModel and can be called from the relevant models with $this->truncate();.

Displaying current revision If you are working in a team it can be useful to know what the current version of your website is on staging server or the live server. Maybe it needs to be updated again. While in debug mode (staging server) this can be quite detailed this should be a harmless version number on your live site.

This is a small snippet for SVN reps available as a simple element:

<?php
if (!isset($svnFile)) {
    $svnFile = APP.'.svn/entries';
}
if (file_exists($svnFile) && ($svn = File($svnFile))) {
    $svnrev = $svn[3];
    $lastChange = trim($svn[9]);
    $lastUser = trim($svn[11]);
 
    if (isset($version) && $version === false || Configure::read('debug') > 0) {
        # display the revision right away
     $versionText = 'Rev. ' . $svnrev . ' (' . h($lastUser) . ' - ' . $this->Datetime->niceDate($lastChange, FORMAT_NICE_YMDHM) . ')';
    } else {
        # in productive mode we want to display a harmless looking version number
     if (strlen($svnrev) > 3) {
            $v = substr($svnrev, 0, strlen($svnrev) - 3) . '.' . substr($svnrev, -3, 1) . '.' . substr($svnrev, -2, 1);
        } elseif (strlen($svnrev) == 3) {
            $v = '0.' . substr($svnrev, -3, 1) . '.' . substr($svnrev, -2, 1);
        } else {
            $v = '0.0.' . substr($svnrev, -2, 1);
        }
        $versionText = 'Version ' . $v;
    }
?>
<div class="svn-revision">
<?php echo $versionText; ?>
</div>
 
<?php
}
if (isset($svn)) {
    unset($svn);
}
?>

Usage in your layout at the bottom:

<?php echo $this->element('svn_revision', array('plugin'=>'tools')); ?>

By passing 'version'=>false into the element you can force the detailed view even on debug 0. Note: it does use my custom datetime helper. I am sure you can improvise here :)

 
1 Comment

Posted in CakePHP

 

All new CakePHP Tips and Tricks

26 Mar

This is supposed to be a list of useful tricks gathered over many months.

First Templates, then Bake, then Adjustments

The usual workflow for a new project should be

This way you speed up your development while having all the advantages of custom templates. Follow the link to read more about the topic.

Don’t sanitize

Sanitizing is not always bad (see later on). But most of the times we don’t have to sanitize every bit of input. Its overhead and usually makes more harm then good. On save to database nothing is needed as Cake properly escapes data itself. What we DO need is some protection in the view. Use h($var) in the views to make sure all potential dangerous strings are now harmless. This is called “escaping”. Note: You would have to do this with any string that gets in contact with the user. So if you use database content in flash messages, you would have to escape before you call setFlash() – or escape the content in the session flash element (but if you want to be able to use HTML output, the second option is not going to work). Alternatively you could use BBCode for flash messages. That would allow you to use h() and any HTML markup together. To sum it up: Try to be less restrictive on the input but still make sure your site is safe. This makes the user happier and keeps the efforts for security as low as possible but as high as necessary.

BAD Example: Firstname/Lastname input fields validated with regexp or [A-Za-zäöüÄÖÜß] We can see that all major German signs are accepted. But what if a French guy wants to sign up. His name might be Aimé. He would be quite frustrated with the website and leave!

GOOD Example: Simply validate the length and escape the name on output. Every possible name is accepted :)

echo h($user['User']['first_name']);

So follow these tips and you will be fine!

Careful with the super-automatic methods

Methods like updateAll() and deleteAll() are a little bit different from the normal save() or find() methods. They accept expressions and therefore don’t automatically escape the content. You should only use them with own “controlled” input or after sanitizing the data thoroughly. Otherwise your SQL queries might break or can even be used in harmful ways against you and your site.

Those methods like updateAll() can be pretty handy or even necessary if you need atomic DB updates. Example:

$this->Model->updateAll(array('Post.view_count' => 'Post.view_count + 1'), array('Post.id' => $post['Post']['id']));

Even if two users trigger this the exact same moment, it will raise the count twice. If you use find() and saveField() you might end up overriding each other and raising it only once. But in this example the input is not from the user and can therefore be considered safe.

If you use user-input you should cast (string to int if applicable) or strictly sanitize to assert that sql injections are not possible!

Working with dates

I created some date constants in my bootstrap:

define('DEFAULT_DATE', '0000-00-00');
define('DEFAULT_TIME', '00:00:00');
define('FORMAT_DB_DATE','Y-m-d');
define('FORMAT_DB_TIME','H:i:s');
define('FORMAT_DB_DATETIME', FORMAT_DB_DATE . ' ' . FORMAT_DB_TIME);
 
// now I can use it everywhere (controller, models, views, ...)
$today = date(FORMAT_DB_DATE);
$yesterday = date(FORMAT_DB_DATE, time()-DAY);
$exactlySevenDaysAgo = date(FORMAT_DB_DATETIME, time()-7*DAY);

A very clean and readable approach.

Always try to use the SECOND, HOUR, DAY .. constants. Note: Everything above WEEK gets fuzzy (MONTH is always 30 days). So for everything from MONTH up you should use the PHP5 DateTime object to correctly add months/years.

Using debug mode 1

Right now you can usually switch between debug mode 0 (no debug) and 2 (3 is not used anymore). With debug mode 2 you usually display the debug bar or debug plugin etc containing the SQL queries and other stuff. My idea quite some time ago: Why not using 1 as well? 1 could mean debug output is on but no debug bar is displayed. It simply triggers all debug warnings/errors. Simply make sure that your debug level is > 1 at the bottom of your layout:

$debug = (int)Configure::read('debug');
if ($debug > 1 && Configure::read('Debug.helper')) {
    // display debug tabs with SQL queries etc
}

For debug 1 it will still display all debug messages. And with debug 0 it is still debug-free.

 
1 Comment

Posted in CakePHP

 

Frequent CakePHP problems and solutions

28 Oct

On Google Groups you read about some problems over and over. So i thought, why not putting the most frequent ones together.

Add/Edit Post Url

If you have an url like “/products/add/import:1/manufacturer:4″ and you want to preserve the url on POST, you will need this snippet:

echo $this->Form->create('Product', array('url'=>'/'.$this->params['url']['url']));

This way it will always post to the same url. The default behavior is to post to “/products/add” in this example above.

Adding user_id or other model related data on the fly

Some use hidden inputs for that. Very bad style! Never ever do that unless you have a damn good reason :) . It can easily be manipulated (the security component will not help here at all!) and is absolute nonsense. Just add it to the array before passing it to the model:

if (!empty($this->data)) { 
        $this->Product->create(); 
        $this->data['Product']['user_id'] = $this->Session- 
>read('Auth.User.id'); 
 
        if ($this->Product->save($this->data)) { 
                ... 
        } 
        ... 
}

Uploading Files

echo $this->Form->create('Model', array('type' => 'file'));
...
echo $this->Form->input('field', array('type' => 'file'));
...
echo $this->Form->end();

Both form and input need to be “file” in order to upload files.

Productive Server: “Error: Page not found”

If you just upload your files to your webserver, you might result in this error message. Although everything is supposed to work. Make sure, that you erase all tmp files after you uploaded/modified files (especially if model-changes are involved). Otherwise it will try to work with the wrong files, which of course fails.

UTF8

Warning (2): htmlspecialchars(): Invalid multibyte sequence in argument in [/var/www/html/cake/basics.php, line 207] I assume your application runs with utf8 as app encoding.

Usually this happens, if you forgot to save this file as utf8. It will then pass an invalid string to h(). In most cases it is a view (.ctp). So make sure all views (and maybe even controllers for session flash messages and models for validation rules) that contain special chars are saved as utf8. Tip: If you don’t know where it happens, use a stack trace to find the file causing this. Either manually by adding the stracktrace to the file_log.php or by using my cakephp addon “Proper logging feature”.

Cannot modify header

Cannot modify header information - headers already sent by (output started at C:...\cake\cake\basics.php:305) [CORE\cake\libs\controller\controller.php, line 646] This usually happens if you have ANY character printed out before the view actually renders. It can be a single space after the closing php tag of a php file (controller, model, …). Therefore you should not use ?> at the end of php files (CakePHP took the same path in summer 2010). It prevents this from happening.

Not found

It is very important to clear the “models” cache after manually changing anything on the database (tables). Also clear the “persistent” cache folder. Otherwise it tries to use the old cached files which result in errors. With debug > 0 this is done automatically.

Ajax File Upload

You cannot upload files with AJAX itself. This will not work. Everything except for binary data. There are workarounds, though. Jquery, for instance, uses hidden iframes. So don’t use the ajax helper, but Jquery plugins or any other JS package to upload ajax files. Those usually use the above technique to workaround this problem.

Misc

… coming up

 
No Comments

Posted in CakePHP

 

About PHP basics and pitfalls

09 Oct

AND, && and &

Usually you use && in favor of AND. At least, I prefer! Cake code conventions state the same. But they do exactly the same: They “intelligently” match the conditions and abort as SOON as the first condition fails. This is important to know.

if ($cond1 && $cond2 && $cond3) {}

in this case $cond2 and $cond3 are only checked if the first one returns true, $cond3 only if both 1 and 2 return true. So it does make sense to order them from “fastest” to “most complicated” etc.

So why is there “&”?

if ($this->checkOne() & $this->checkTwo()) {}

& can be useful if you want to make sure ALL conditions are checked and the final result is returned afterwards. But this should only be used if the content of all conditional functions is vital to the following code in both cases (if any condition fails or if all pass).

Example for “&”:

if ($this->cleanup() & $this->cleanupSomeWhereElse()) {...}

We do want to return the result, but AFTER all cleanup functions did their job. Using “&&” we would not call the second function if the first only already returned false. But they work independently – so why not trying to get everything done the first time? The result will show that something didn’t work and can be manually deleted or whatever.

An example for “&&” would be validation: Why checking 4 other validation rules if the first one (maybe “notEmpty”) already failed? This speeds up the process.

Careful with ! (NOT)

BAD:

if (!$time = $this->Session->read('Board.time')) {
    $time = time();
    //some code
}

GOOD:

if (!($time = $this->Session->read('Board.time'))) {
    $time = time();
    //some code
}

The extra () brackets make all the difference in the world here. In the first scenario time gets first “inversed” before trying to become the assigned value whereas in the second scenario we first assign time the correct value and afterwards check on “NOT condition”. So the bad example will not work or result in an unexpected outcome. The good one, though, will work as expected.

isset(), !empty(), …

Some coders just check on variables by using “if ($var)” or “if (!$var)”. Of course one can suppress the notices. But it’s really bad style. Many PHP scripts circulating the internet are written in such an unclean style.

If you are not sure whether a variable exists, use isset() or !empty(). Examples:

if (!empty($this->params['named']['xyz']) { ... }
if (isset($this->SomeModel) { ... }
if (!empty($myNewArray) { ... }

The main difference is that !empty() should not be used with integer values IF 0 is a valid input. I find it interesting that it even works with arrays (empty arrays return false).

Result Table for those two:

$var1  =  0; 
$var2  =  null; 
$var3  =  false; // boolean 
$var4  =  true;   // boolean 
$var5  =  ''; 
$var6  =  ' '; // whitespace 
$var7  =  'something'; 
$var8  =  array(); // empty array
$var9  =  '0';
$var10 =  "0";
 
# isset()
// var1: true
// var2: FALSE
// var3: true
// var4: true
// var5: true
// var6: true
// var7: true
// var8: true
// var9: true
 
# !empty()

You could ask “Why not always using isset()?”. Well, most of the time we want to make sure that the variable contains some kind of information – not that it exists. So the rule to remember is: First try to use !empty(), then fallback to isset(), is_null(), …

And remember: good code never throws any notices. And if it does you know that something unexpected out of the ordinary happened and can respond and maybe fix the bug etc.

For details on this topic, see the type comparison tables.

Conclusions for CakePHP: As you can see i also included ” “, a single whitespace. It will return true even for !empty(). So always trim your post data. Otherwise notEmpty validation rules are easily overriden by nonsense-whitespaces.

Tip: Use the following snippet in your app_controller()

function beforeFilter() {
    parent::beforeFilter();
 
    /** DATA PREPERATION **/
    if (!empty($this->data)) {
        $this->data = $this->Common->trimDeep($this->data);
    }
}

and put this snippet in your “CommonComponent” or any other component you are using in every controller:

function trimDeep($value) {
    $value = is_array($value) ? array_map(array(&$this, 'trimDeep'), $value) : trim($value);
    return $value;
}

normal functions and “mb” functions

Those mb (multi byte) functions are usally used/needed with UTF8 as app encoding. The validation, for instance, needs them to check on minLength/maxLength. They count characters. For example: mb_strlen(‘abc’) equals 3 The normal functions, like strlen() count bytes. They are usually used if non-utf8-characters are used or in combination with file operations.

What happens if you use them incorrectly? Let’s say, we want to make sure the user input is exactly 4 characters long. So strlen('über') => 5 (!!!) returns false results but mb_strlen('über') => 4 will do it correctly. Even if you don’t think anybody would ever input such special chars like “umlaute” (äöüÄÖÜ) or other UTF8 chars, you still should implement it so that it would work with them.

Summary: Usage of strlen() - how long a string is in bytes (actual file size) - string operations where the length is irrelevant (e.g. replacing) Usage of mb_strlen() - validation of string lengths (chars) - cutting out pieces of a string

In general, mb_ functions are slower than the normal functions. But they should still be used wherever it makes sense.

Interesting: If you wish to find the byte length of a multi-byte string when you are using mbstring.func_overload 2 and UTF-8 strings, then you can use the following: mb_strlen($utf8_string, 'latin1'); (source)

 
2 Comments

Posted in PHP

 

CakePHP Beginner Tips

07 Oct

Based on CakePHP1.x

Apache Server

For Windows use WAMPServer2 and select “Apache” => “Apache Modules” => “rewrite_module”. Thats all there is. Cake should now work out of the box without any other modifications.

Database

Use UTF8 and utf8_unicode_ci (not “utf8_general_ci”!) for all tables. general might be a little faster, but is also more inaccurate in sorting.

I don’t use NULL as default value for normal text fields, if I can avoid it. There are some issues one needs to work around, otherwise. So always try to use default ’0′ for int fields, default ” (empty string) for varchar/char etc. For a list of database related issues see problems-with-null. For foreign keys you should use NULL, though (as 0 or empty string do not make much sense here).

Use tinyint(1) ONLY for boolean (=toggle) fields (0/1) – represented by checkboxes. For anything else (select box with 0,1,… and more elements) you need tinyint(2) or even int(x). Always use “id” as primary key”. This convention helps in the long run. It should be int(10) UNSIGNED – a so called AIIDs. Another option would be to use UUIDs. They are unique across the whole database and are char(36).

The date fields “created” and “modified” are handled by cake automatically if present on every INSERT/UPDATE. So don’t call them anything else if you don’t have to. Just make them of type date or datetime and default them to NULL.

The label fields “name” or “title” will automatically be used by CakePHP as “displayField” if found as table fields. If you do not specify one of those two, you should manually declare the displayField used for find(list) etc;

public $displayField = 'label';

Other automagic fields are of type text/mediumtext/longtext which result in a textarea.

I recommend to stick to conventions and underscore + lowercase all table fields: “last_login” (instead of “lastLogin” or even worse “last login”) etc.

Do not use “enums” as they are not supported. There are actually even better ways to workaround this.

The “length” of a CHAR/VARCHAR field gets read out by Cake with “DESCRIBE” and is stored in the cache. It is used to set the “maxlength” attributes for <input type="text"> form fields, which are created by the form helper. If you want the username only to be 20 chars, the form will not allow 21. Check it out. You still need to validate this, though, in the model (as it can be hacked).

UPDATE: Some NULL issues can actually be resolved. NULL / NOT NULL makes a difference for validation as they will add “required” to the form fields. So make sure you also check this out and maybe do use NULL where applicable.

General tips

Always develop with debug > 0 (usually 2 for detailed sql queries at the bottom). Always deploy with debug = 0.

Find conditions

This doesnt work:

->find('all', array(
    'conditions' => array('user_id' => '', 'user_id' => NULL)));

same with

->find('all', array(
    'conditions' => array('OR' => array('user_id' => 2, 'user_id' => 3))));

You cannot use array keys twice in the same array. Either put them in a subarray inside the array or combine them right away:

->find('all', array(
    'conditions' => array(array('user_id' => ''), array('user_id' => NULL))));
->find('all', array(
    'conditions' => array('OR' => array('user_id' => array(2, 3)))));

For the second example CakePHP with automatically use IN (…) instead of =.

Forms

If you need to pass default values to select fields or any other input, do NOT use inline params like ‘selected=”xyz”‘. As soon as you submit your form it will restore extactly the same default value – no matter what the user changed. But thats not common sense. Usually, if an error occured and you have to correct your input you want everything to be same as before – only changing the required field. So the correct approach is to pass it from the controller:

if (!empty($this->data)) {
    // validate and save
} else {
    // now thats the important part here
    $this->data['Model']['field'] = 1; // or whatever your default value is supposed to be
}

For details see here.

If you want to submit the form to the same url, use:

$this->Form->create('OtherModel', array('url' => '/' . $this->params['url']['url']));

It will preserv all params like “/controller/action/123/uid:xyz” (usually you would submit to “controller/action” or “controller/action/123″).

Links

Always use arrays for internal links:

$this->Html->link(array('controller'=>'x', 'action'=>'y', 'some_pass_var', 'some_named_var'=>'z'));

This way your application is flexible and you can always route the url. If you hard-code it as string this won’t be possible.

AppController callbacks

beforeFilter and beforeRender as well as afterFilter are available for app wide stuff to be handled. Be careful though: If you want to use whatever you do there in the layout (on all views), you need to use beforeRender() only. That’s because only beforeRender() is triggered if an error occurs. The other 2 not! Especially if you need the variables passed to the view in some elements etc you will otherwise get notices and errors.

UPDATE 2012 Cake2.x

For Cake2.x the form will always submit to itself by default. No need to overwrite the url here.

You do not check on if (!empty($this->data)) {} anymore, but use if ($this->request->is()) with post/put instead. Also use $this->request->data instead of $this->data in the controllers.

The rest should still be valid.

 
4 Comments

Posted in CakePHP

 

Useful Windows 7 Tricks

28 Sep

We all agree that Windows 7 is the most sophisticated version so far. WinXP is totally outdated, ME and Vista were crap. But despite the more stable workstation and some really good new features windows still lacks some basic stuff or at least kept its annoyances :) I will also point out some useful tricks and tips.

No forced restart after updates

The most annoying thing still remains the (forced) restart after updates all the time. … English: Deactivate German: Deaktivieren

Tips and Tricks

Using 2 monitors you can easily send active windows from one to the other by using the combination [WINDOWS]+[SHIFT]+[ARROW left/right]. With [WINDOWS]+[SHIFT]+[ARROW up/down] on the other hand you can minimize/maximize the current window.

For opening programs with more rights simple press [STRG]+[SHIFT] while clicking on it.

Minor probs

The thumbnail previews of the super bar are usually coming up really slow. Change to “HKEY_CURRENT_USER\Control Panel\Mouse” and lower the value from “400″ to “0″ or anything in between.

Missing tips?

Tell me and I will add it!

 
No Comments

Posted in Common

 

Useful hidden functions

28 Aug

There are some handy cake functions you might not have used or even heard of, yet.

Debugging

Most beginners use print_r() to debug variables. Without <pre> tags its really hard to read, though. Use pr($var) or debug($var) to debug this variable and output its content. very useful inside functions, or in the view (if you don’t know what your sql result array contains, for instance). If you are looking for a more powerful pr() function in order to display not only the content but also what type it is (int, bool, string, array, object or simply NULL), check out my returns() function in the bootstrap goodies section.

Models

save, saveField, saveAll(), deleteAll(), … return usually true/false or the record itself. Sometimes it would be nice to know how many records have been deleted/modified.

$this->Model->getAffectedRows(); // works for INSERT, UPDATE, REPLACE or DELETE query

This will do the trick. The integer result can then be used in the flash message.

By the way: There is also ->getNumRows(); for SELECT or SHOW queries.

Logging

If you want to log certain events, you can easily use the build in functions:

CakeLog::write('geocode', 'Address \''.$address.'\' has been geocoded');

In this example every time the geocode webservice geocodes an address string, it will be logged in /tmp/logs/geocode.log. Quite handy in some cases – especially for debugging purposes.

Logging Fatal Errors as well By default CakePHP cannot log fatal errors. They are especially helpful for finding more serious coding bugs, though. You can easily override the shutdownFunction in your bootstrap.php

register_shutdown_function('shutdownFunction');
 
/**
 * custom shutdown function
 */
function shutDownFunction() { 
    $error = error_get_last(); 
    if ($error['type'] == 1 && class_exists('CakeLog')) { 
        CakeLog::write('error', 'Fatal Error in '.$error['file']. ' (line '.$error['line'].'):' . $error['message']);  
    } 
}

Retrieving the CakePHP version

Configure::version();

Basic Functions

What if you need to the class name and you are not sure if the plugin name is passed as well, like “Plugin.User”? I always did this myself, until i stumbled upon the cake function for it: pluginSplit()

list($plugin, $class) = pluginSplit($name);

Other

It helps to open the core files and have a look inside. You might discover some functions you might have written yourself although it is available in the core. Examples are string.php with tokenize() and insert() or set.php with filter(), flatten() or countDim().

 
No Comments

Posted in CakePHP