RSS
 

Posts Tagged ‘CakePHP1.3’

Forms and redirecting

20 Aug

Redirecting in your CakePHP app

My CommonComponent now contains three redirecting methods:

/**
	 * @param mixed $url
	 * @param bool $useReferer
	 * returns nothing and automatically redirects
	 * 2010-11-06 ms
	 */
	public function autoRedirect($whereTo, $useReferer = true) {
		if ($useReferer && $this->Controller->referer() != '/' . $this->Controller->params['url']['url']) {
			$this->Controller->redirect($this->Controller->referer($whereTo, true));
		} else {
			$this->Controller->redirect($whereTo);
		}
	}
	
	/**
	 * should be a 303, but:
	 * Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
	 * @see http://en.wikipedia.org/wiki/Post/Redirect/Get
	 * @param mixed $url
	 * TODO: change to 303 with backwardscompatability for older browsers?
	 * 2011-06-14 ms
	 */
	public function postRedirect($whereTo, $status = 302) {
		$this->Controller->redirect($whereTo, $status);
	}
	
	/**
	 * only redirect to itself if cookies are on
	 * prevents problems with lost data
	 * Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
	 * @see http://en.wikipedia.org/wiki/Post/Redirect/Get
	 * TODO: change to 303 with backwardscompatability for older browsers?
	 * 2011-08-10 ms
	 */
	public function prgRedirect($status = 302) {
		if (!empty($_COOKIE[Configure::read('Session.cookie')])) {
			$this->Controller->redirect('/'.$this->Controller->params['url']['url'], $status);
		}
	}

autoRedirect and postRedirect

Where do I use it? In pretty much every form (for instance edit action):

if (empty($id) || !($user = $this->User->find('first', array('conditions'=>array('User.id'=>$id))))) {
	$this->Common->flashMessage(__('invalid record', true), 'error');
	$this->Common->autoRedirect(array('action' => 'index'));
}
if (!empty($this->data)) {
	if ($this->User->save($this->data)) {
		$var = $this->data['User']['id'];
		$this->Common->flashMessage(sprintf(__('record edit %s saved', true), h($var)), 'success');
		$this->Common->postRedirect(array('action' => 'index'));
	} else {
		$this->Common->flashMessage(__('formContainsErrors', true), 'error');
	}
}
if (empty($this->data)) {
	$this->data = $user;
}

The autoRedirect automatically redirects back to the site the user came from if possible (if the user clicked a link). Otherwise it will use the provided fallback url.
The postRedirect is a wrapper for the future where one day 303 can be used without causing trouble (read further for details).

prgRedirect

Why is this necessary in some forms? Most search forms do a simply post. What they should do is a POST + GET afterwards. Thats called PRG pattern and is described here.
Especially after posting search forms or entering data you want to avoid a nasty message like some modern browsers produce if you then hit the back button. You want to graciously display the page prior to the post. Thats where this extra redirect comes into play. Always redirect after a post – quite easy to remember.

if (!empty($this->data)) {
	if ($this->Model->search($this->data)) {
		# save POST search to session, redirect and display the search result as GET
		$this->Common->prgRedirect();
	}
}
if ($search = $this->Session->read(...)) {
	...
}

As the comment in the method head as well as other sources explain one should NOT use 303 or you end up with broken forms for some users.

Note the failsafe with the cookie. If cookies are disabled this would result in empty sessions and therefore never work. For disabled cookies there cannot be a redirect and therefore needs the POST to display the data.
Therefore your forms should work with both POST and GET for this very same reason. The "prg" redirect is only an enhancement to provide better functionality in the normal use case (where users do use the back button).

Moved/deleted content

If you moved or deleted some content you can use the 301 redirect to tell search engines and browsers where to find the same content at the new location or where to go to instead:

if (empty($manufacturer)) {
	$this->Session->setFlash(__('Invalid Manufacturer', true));
	$this->redirect(array('action'=>'index'), 301);
}

In the example I use this to redirect from views back to index if no manufacturer (retrieved via slug) was found. This is necessary to avoid duplicate content for invalid slugs.

Complete list of browsers that are not capable of handling 303s

//TODO – does anyone have infos on that matter?

 
No Comments

Posted in CakePHP

 

Cakephp Console on Linux systems

24 May

You could hard-wire the Cake path into the environment.
With multiple cake core versions on a single system, sometimes it is better not to do that, though.
So I just follow the following guidelines:

  • Only add the PHP path to the system environment path (not the cake one!)
  • Always navigate to your app folder.
  • Call your shells from there, either with Console/cake (if you have an app Console folder with cake file) or ../lib/Cake/Console/cake (core cake file).

Cronjobs

The documentation in the cookbook is pretty well written.

Here with a little bit more detailed explanation:

# m h dom mon dow command
*/5 *   *   *   * cd /full/path/to/app && Console/cake myshell myparam

The tool Crontab is used here. This config file contains an example with a cronjob that is executed every 5 minutes.
Basically, you just tell the script to navigate into your app folder and call your shell as you normally would as real user (always from inside your app folder).

If you don’t have an app cake file (I don’t for some older projects), you can also use the core cake file just as fine:

# m h dom mon dow command
*/5 *   *   *   * cd /full/path/to/app && ../lib/Console/cake myshell myparam

You can edit the crontab for the www-data user, for example, using

crontab -e -u www-data

Using the user "www-data" (or a user that is affiliated with it) is advised to prevent running into issues with file access when creating or modifying files.
It is also a security issue using a user with more or different rights than he should have or needs.

Also note that your "cake" file (either app or core) needs to be executable, so make sure you have set the appropriate rights for it.

CakePHP 2.x and windows

See the updated windows post for the correct path here.
Also note the way more comfortable quick-links if you need to use 1.x and 2.x parallel.

 
1 Comment

Posted in CakePHP

 

Unit-Testing in CakePHP

03 Apr

Warning: Some information my be deprecated! This is still from 1.3 and SimpleTest. CakePHP now uses PHPUnit in 2.x – please see the updated article here.

Some tips you might not have read about yet

Since the cookbook is quite detailed as far as basic testing is concerned I will not unroll that part too much. Please read those pages if you never wrote unit tests before.

Use your own wrapper

If you use your own testcase-wrapper you will be able to add some common functionality for all test cases – similar to AppModel, AppController or AppHelper.
I called it "my_cake_test_case.php" and put it in the vendors folder:

class MyCakeTestCase extends CakeTestCase {
	# added some time calc stuff - like calculating how long a test needed
	//
	# added some more custom stuff
	//
	# printing a header element like <h3> at the beginning of each test function
	function _header($title) {
		if (strpos($title, 'test') === 0) {
			$title = substr($title, 4);
			$title = Inflector::humanize(Inflector::underscore($title));
		}
		return '<h3>'.$title.'</h3>';
	}
	# overriding the assert rules in order to customize it (like adding detailed descriptions and before/after output)
	function assertEqual($is, $expected, $title = null, $value = null, $message = '%s', $options = array()) {
		$expectation = 'EQUAL';
		$this->_printTitle($expectation, $title, $options);
		$this->_printResults($is, $expected, $value, $options);
		return parent::assertEqual($is, $expected, $message);
	}
	function _printTitle($expectation, $title = null) {
		if (!$this->_reporter->params['show_passes']) {
			return false;
		}
		echo $this->_title($expectation, $title);
	}
	function _printResults($is, $expected, $pre = null, $status = false) {
		if (!$this->_reporter->params['show_passes']) {
			return false;
		}
		if ($pre !== null) {
			echo 'value:';
			pr ($pre);
		}
		echo 'result is:';
		pr($is);
		if (!$status) {
			echo 'result expected:';
			pr ($expected);
		}
	}
}

And much more

Now we can use it in the test cases

App::import('Vendor', 'MyCakeTestCase');
class ColorLibTest extends MyCakeTestCase {
	function testColDiff() {
		echo $this->_header(__FUNCTION__);
		$was = 'ffffff';
		$expected = 1;
		$is = $this->Color->colDiff($was, '000000');
		$this->assertEqual($is, $expected, null, $was));
	}
}

The header will be "ColDiff" – generated automatically using FUNCTION (magic constant).
The detailed messages are only displayed if "Show Passes" (url: show_passes=1) is activated. Otherwise it behaves as usual.

Skipping tests based on conditions

Sometimes tests depend on the system settings, enabled modules etc. If it doesn’t make sense to test certain methods, put this at the top of the test function:

# example with "magic_quotes_sybase"
function testSomething() {
	if ($this->skipIf(ini_get('magic_quotes_sybase') === '1', '%s magic_quotes_sybase is on')) {
		return;
	}
	... // this code will not be executed anymore
}

Always include object integrity first

Cake has a lot of automagic. Sometimes this can hurt, though, if unknown to the programmer.
If a model cannot be initialized it will usually fall back to AppModel – silently! Same with other classes.
So make sure, they are what they are supposed to be.

# this model is not an AppModel instance, but the real deal (Album instance of class Album)
function testAlbumInstance() {
	$this->assertTrue(is_a($this->Album, 'Album'));
}
# for a controller
function testAlbumsControllerInstance() {
	$this->assertTrue(is_a($this->Albums, 'AlbumsController'));
}

startCase() or startTest()

Most of the time startCase() is usually enough. This is triggered ONCE at the beginning of all tests. If the class itself does not remember or change anything, this is more than enough.
But as soon as your class (or object) internally keeps information about the last operation, you will get unexpected results. Use startTest() then to ensure, that the object is always a "fresh" one:

function startTest() {
	$this->Color = new ColorLib();
}

Note: setUp() is the same as startTest() but not officially used because it is SimpleTest specific.

 
 

GoogleMapsV3 CakePHP Helper

21 Dec

This is a helper to generate GoogleMap maps (dynamic and static ones) in your CakePHP views.

Note: Google Maps API V2 is marked "deprecated". API V3 is supposed to be faster and more compatible to mobile browsers. Details
Also new: A googlemaps key is not necessary anymore.

Preamble

You can either dynamically collect the geo data, or you can use my behavior to retreive and store them persistently. I prefer storing them in a table using lat/lng fields as it will make displaying the markers faster (no need to fetch at runtime) – it also makes it possible to make distance queries using radius and such.

Usage

I wanted to keep it as simple as possible (otherwise you could use the maps with plain js right away).

You may set up configs/defaults in your config file:

$config['Google'] = array(
	'zoom' => 6,
	'lat' => 51.1,
	'lng' => 11.2,
	'type' => 'H', // Roadmap, Satellite, Hybrid, Terrain
	'size' => array('width'=>'100%', 'height'=>400),
	'staticSize' => '500x450',
);

Please include the Tools Plugin in your bootstrap with CakePlugin::loadAll() or CakePlugin::load('Tools').

Now you need to add the helper to the controller (or the action):

// globally
public $helpers = array(..., 'Tools.GoogleMapV3');
// OR in the action:
public function map() {
	$this->helpers[] = 'Tools.GoogleMapV3';
	// rest of your code       
}

Since there are many possible ways to include the required javascript file I formed an own method for it:

<?php
// include jQuery >= 1.4
echo $this->Html->script('jquery'); // or wherever it is in your js folder
// include the Google js code
echo $this->Html->script($this->GoogleMapV3->apiUrl());
// OR include it manually without cake (or use your own asset stuff)
echo '<script type="text/javascript" src="'.$this->GoogleMapV3->apiUrl().'"></script>';

You may also let the helper include it automatically – just pass the option "autoScript" => true to the map() method in the next step.

Now we can use it.

Interactive Maps

// init map (prints container)
echo $this->GoogleMapV3->map(array('div'=>array('height'=>'400', 'width'=>'100%')));
// add markers
$options = array(
	'lat' => 48.95145,
	'lng' => 11.6981,
	'icon'=> 'url_to_icon', // optional
	'title' => 'Some title', // optional
	'content' => '<b>HTML</b> Content for the Bubble/InfoWindow' // optional
);
// tip: use it inside a for loop for multiple markers
$this->GoogleMapV3->addMarker($options);
// print js
echo $this->GoogleMapV3->script();

You can also add windows and (custom) events. See the code and its tests for details on that.

Buffering scripts instead of outputting them directly

With 2.x you can also write the JS to the buffer and output it combined anywhere you want in your layout.
Just call

$this->GoogleMapV3->finalize(); // replaces script()

instead of echoing the script() then. The script() call must not be used then. Don’t mix those two methods.

Also make sure you got echo $this->Js->writeBuffer(array('inline' => true)); somewhere in your layout then.
Most developers put CSS in the head and JS at the very bottom (before the closing </body> tag). This way, the layout renders very fast and the JavaScript will render last and does not impede the page loading process.
See the next chapter for details.

Directions (with or without additional text)

If you want to print directions from point A to point B, you can do so with:

echo $this->GoogleMapV3->map();
$from = 'Munich'; // needs to be geocoded at runtime
$to =  array('lat' => 50.51, 'lng' => 13.40);
$this->GoogleMapV3->addDirections($from, $to);
$this->GoogleMapV3->finalize();

You can either pass in a string to be automatically geocoded or pass an array with lat/lng coordinates.

Note: Geocoding on demand is possible, but slightly slower and less resourceful. It is recommended to geocode in the backend – using PHP and the Plugin classes listed below – and output the coordinates here directly.

Static Maps

These are just images. Very handy if you don’t need the js overhead.

// markers
$markers = array(
	array('lat'=>48.2, 'lng'=>11.1),
	array('lat'=>48.1, 'lng'=>11.2, 'color' => 'green', ...)
);
// paths
$paths = array(
	array(
		'path' => array('Berlin', 'Stuttgart'),
		'color' => 'green',
	),
	array(
		'path' => array('44.2,11.1', '43.1,12.2', '44.3,11.3', '43.3,12.3'),
	),
	array(
		'path' => array(array('lat'=>'48.1','lng'=>'11.1'), array('lat'=>'48.4','lng'=>'11.2')), //'Frankfurt'
		'color' => 'red',
		'weight' => 10
	)
);
// some options and image attributes
$options = array(
	'size' => '500x400',
	'center' => true,
	'markers' => $this->GoogleMapV3->staticMarkers($markers),
	'paths' => $this->GoogleMapV3->staticPaths($paths), 
);
$attr = array(
	'title'=>'Yeah'
);
// now display the map image
echo $this->GoogleMapV3->staticMap($options, $attr);
// you can even add an url to click on
$attr['url'] = $this->GoogleMapV3->mapUrl(array('to'=>'Munich, Germany'));
echo $this->GoogleMapV3->staticMap($options, $attr);

As you can see, we can now mix lat/lng and normal addresses (which get automatically geocoded in the background).

Map Links

If you want to redirect to maps.google.com (for directions etc) you can use this method.

// leave "from" empty for the user
$url = $this->GoogleMapV3->mapUrl(array('to'=>'Munich, Germany'));
echo $this->Html->link('Visit Me', $url, array('target'=>'_blank')
// coming from a posted form or whatever
$url = $this->GoogleMapV3->mapUrl(array('to'=>'Munich, Germany', 'from'=>$from));
echo $this->Html->link('Directions to me', $url, array('target'=>'_blank')

Last but not least

Other updates:

  • multiple paths and markers
  • visible scope
  • custom icons possible

For more examples check out the test case. It contains several more sophisticated examples.

Helper Code

The code can be found in my github rep:
Tools Plugin GoogleMapV3 helper

Update 2012-02

All urls/links are now HTTPS sensitive. So if you display the map on a secure site (https://...) it will also use the same connection for all the google stuff (images, js, …). This is necessary for HTTPS to be valid.

Update 2012-09: GoogleMapsV3Helper v1.3 – not backwards compatible

The helper is now E_STRICT compliant. The methods url() and link() are now mapUrl() and mapLink().

Update 2013-02

directions() has been added to print directions from point A to B. Also basic geocoding capabilities have been added for this method as well as addMarker().

Update 2013-10

The "open" option for markers allows you to display their infoWindow opened at loading. This was a feature request on github. It is available for single and multi windows mode. Note that for single window mode (default), only one marker can be shown as open at once (the last one declared).

 
106 Comments

Posted in CakePHP

 

Preventing Brute Force on Login

11 Nov

With default cake login procedures a user could try unlimited passwords to one specific account.
That means, if you write a bot that tries every possible combination (thousand times per minute!), this bot could eventually gain access to the account. To cloak it, he could use several bots on several different locations or proxies.

Some websites use the session to count the attempts and display a captcha after x failures. Or they use the IP address to block this user for a few minutes. Both is not very secure.
A user could change his session and his IP frequently (depending on the skills of the attacker of course).
Then he would still have unlimited trials.

More secure mechanisms

Just some ideas to discuss here – i didn’t yet test the ideas in real life scenarios.

I don’t like the captcha stuff. I would prefer the login timeout. Either way we need to make sure that everybody – no matter if human, bot or both – gets the same result.

Lets say, our "attacker" targets "benni123" and wants access.
He tries several combinations and suddenly he cannot login for a few minutes. No matter what he does.
How?

My idea: use the database for it. Store the information based on the record the user wants to validate against.
Every failure the database stores it with the User.user_id of "benni123" and the time-stamp.
If the amount is greater as 5 for example, the login is always aborted for this user account. The real user cannot login as well, of course. But after a few minutes everything is fine again – and he even knows somebody tried to access the account.

No bot would ever find the right key. Its just not reasonable to write a bot that only tries to access the account every few minutes. It would need hundreds of years with medium-sized passwords.

You could also display a captcha (google does it this way) after x failures. But they "could" be cracked using a specific captcha reader for your site. once this is in place, they provide not much of security anymore. Meaning: The captcha must be complicated enough for the bot but still readable for humans. reality shows that this is not easy to accomplish. Therefore I tend to use captchas only to slow too active users down (prevent spamming, message flooding, etc).

Additionally we want a php script timeout (wait x seconds to respond) after unsuccessfull attempts.
This way the user doesn’t really notice but any brute force script would need remarkably longer for a chain of attempts.

In the end we could do this:
Display a captcha after maybe 5 wrong trials. After another 5 correct captcha inputs but incorrect password inputs (important! we dont want this to happen if the catpcha is wrong – otherwise an attacker could easily shut down all accounts) shut the account down for a specific timeout. Of course, if the valid user tries to access the account in that time he needs to get a proper error message.

Implementation

Using the timeout method we extend the core auth component:
Component AuthExt:

/**
 * override auth login
 * @override
 */
function login($data = null) {
	if (!$this->loginExt($data)) {
		# security timeout?
		sleep(1); # 1 second for right now
		return $this->_loggedIn;
	}
	$model = $this->getModel();
	// do final stuff (getting roles and additional session infos, raise login counter, ...)
	return $this->_loggedIn;
}
function loginExt($data = null) {
	$this->__setDefaults();
	$this->_loggedIn = false;
	if (empty($data)) {
		$data = $this->data;
	}
	# check against database entries if user is temporary banned
	if ($this->floodProtectionActive($data)) { # TODO: implement
		return $this->_loggedIn;
	}
	if ($user = $this->identify($data)) {
		// here we can check if other reasons prevent returning true (suspended, account not active yet, ...)
		return $this->_loggedIn;
	}
	# seems like we need to log this attempt as a failure
	$this->floodProtectionUpdate($data); # TODO: implement
	
	return $this->_loggedIn;
}

Security Layers

Level 1: Javascript (Timeouts, …)

  • LOW!
  • Can easily be bypassed

Level 2: Captchas, IP/Session based login monitoring, Sleep-Timeouts

  • MEDIUM
  • Can be spoofed/hacked (with some expertise anyway)

Level 3: Database based monitoring (Errors per user and login timeouts)

  • HIGH
  • Can be used against members (blocking access for valid users)

Final words

You would need a garbage collector for the database table. The last x+1 records are more than enough (except you want to debug the information or transform them into statistics).

Using only captchas:
Assuming that captchas are still as secure as we would like them to be, this is the easiest implementation.
After x failures, an additional captcha has to be entered.
The real user would get a captcha right away, if an attacker just tried to access his account. So no delay at all.
The bot, though, should fail due to the captcha in place.

Using a script timeout:
As mentioned above, the script timeout (sleep command/function) is really helpful in order to slow down brute force attempts.
A few seconds are more than enough. Normally 100 attempts need 5 seconds, with this timeout they will need more than 200.
Assuming that the attacker doesn’t use 100 parallel connections, of course.

Using a block timeout:
The real user could be identified by cookies, specific browser fingerprint etc.
This in combination with the correct password in the first place could override the above protection mechanism. allowing the user to enter EVEN if the timeout is in place due to too many failures.
The attacker should not be able to guess the fingerprint, though.
Maybe the "browser user agent" combined with a cookie information (maybe even with the IP, although it could already have changed). Even if the attacker knew that both are used for the override, he would have too many combinations ABOVE the main problem: the correct password.
He would need to know the content of the encrypted cookie as well as the exact browser version used from the real user the last time he logged on.

Combining them:
This way it is most secure on application level. Even if a working captcha reader is found for your captchas, the attacker will not be able to brute force login.

One advantage of the database log tables:
You can easily monitor the fails and get a pretty good picture how many "brute force attacks" have actually been commited on your website.

What do you think? And is the possible "override" maybe the key for vulnerabilities again? At least for a specific target this might be plausible.

 
4 Comments

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

 

Complete CakePHP naming conventions

08 Oct

For details into some aspects take a look at my old "coding standards" summary.

Database

Database tables are plural: "comments" or "user_comments" etc.

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

Models

Model names are singular and camelCased: "Comment or "UserComment"
filename: "comment.php" or "user_comment.php"

Controllers

Model names are plural and camelCased: "Comments or "UserComments"
filename: "comments_controller.php" or "user_comments_controller.php"

Controller actions should be underscored! "function admin_import_from_xml(){}"

Views

Views have an own folder for each controller – plural: "comments" or "user_comments".
Inside are the templates for the specific actions- underscored.
filenames for example: "index.ctp" or "admin_index.ctp" (with prefix admin) or "admin_order_entries.ctp"

Libs

As long as namespaces are not an issue, they might conflict with existing model classes (they have no "Model" appended!) and other cake core classes or even some vendor files.
I recommend to use the controller syntax here:
Name – camelcased: "GoogleTranslateLib"
filename: "google_translate_lib.php"

This way it won’t interfere with any Google class – or "FileLib" won’t interfere with core "File" class.

Behaviors

Name – camelcased: "GeoPlugin"
filename: "geo_plugin.php"

They should not be similar to model names. Otherwise they might interfere with existing models.
So use adjectives, verbs or plural names: "Ratable", "Geocoded", "Configurations", …

Components

Name – camelcased: "GoogleTranslateComponent"
filename: "google_translate.php"

They could interfere with models (and maybe behaviors). So it might make sense to use plural forms or forms that are not likely to be a model name.

Helpers

Name – camelcased: "GoogleHelper"
filename: "google.php"

With the new 1.3 "$this->Helper" style they can now be anything you want. With a little modification of the core, at least.
I like to call my helpers "MyHelper" if there are specific to a controller/module and it is not yet a plugin.
Example:
Conversations controller + Conversation model + MyConversation helper (as it is only needed inside this specific controller).
Other helpers like "GoogleMap" might be used in several controllers, for instance.

Plugins

They should not be similar to controller names. Otherwise they might interfere with existing controllers.
So use adjectives, verbs or singular names: "Rating", "Setup", "Configuration", …

Constants

No matter if they are class constants or global constants, they are always uppercase and underscored:
"MIN_USER_AGE" etc.

Variables

camelBacked: $userComment

The first letter is uppercase if the variable represents an object:
$File = new File();
$GeoPlugin = new GeoPluginLib();

Layout/Script

CSS classes/ids:
I use camelBacked style here, too: <div class="someClass" id="someId"></div>
But thats purely a matter of taste.
I like to use it in IDs because I can easily attach "-x" (the record id) and split it afterwards on "-" to get the record id in JS:
… id="someId-2"…
comes in handy sometimes

Test Cases

Same as the original file – only with ".test" appended to it: "google_translate_lib.test.php" for instance.

 
No Comments

Posted in CakePHP

 

CakePHP Beginner Tips

07 Oct

Based on CakePHP1.x. Valid for 2.x (see updates below).

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, e.g. in sorting. I found the speed argument to be negligible and not measurable in most cases.

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 exactly the same default value – no matter what the user changed. But that’s not common sense. Usually, if an error occurred 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:

// This suffices
$this->Form->create('OtherModel');

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.

Note that in 2.x you should move away from named params ASAP and use query string instead.

The rest should still be valid.

Update 2014 Cake2.5

It is now possible to have primary keys, foreign keys and tinyint(1/2) as well as some of your other int fields as "unsigned". This can be useful as it saves a little bit of space in the long run, but also ensures there are no negative values to care about. It is good practice to do so. The schema fully understands that now.

 
4 Comments

Posted in CakePHP

 

Console for CakePHP

03 Oct

A Cake 2.x warning: Read the bottom part ("Hot Tip") – and do NOT add Cake to the system path anymore.

Windows and CakePHP 1.x

Nothing easier than that! 2 minutes and it works.

I prefer WAMP (XAMPP sucks). But I describe it for both.
WAMP has the advantage that you can easily switch php versions (5.2.10, 5.3.0, 5.3.1, …).
For the path it is annoying to edit the version number all the time.
So (WAMP ONLY!) copy your desired php version from /bin/php/php5.x.x to /bin/php/php_for_console.
If you now need to switch the php version you can simply replace the files in this folder. You don’t have to edit the system path. For XAMPP there is usually only one php directory, anyway.

We need to add these two paths to the System Path:
C:…\xampp\php; (for XAMPP)
OR
C:…\wamp\bin\php\php_for_console (for WAMP with copied folder)
OR
C:…\wamp\bin\php\php5.x.x (for WAMP without the above trick – not recommended)

AND
C:…\project\cake\console (your project is usually inside www or htdocs folder)

Here you could set up a second cake folder, as well, which only serves the console (e.g. "C:…\wamp\bin\cake"). This way the CakePHP version for the console can easily be managed – separately from any CakePHP app.

We need to open "my pc properties" -> "environment variables".
In "path" we need to append the 2 paths from above.

Separate them with semicolons:
...;C:...\xampp\php
Done! It should now work right away on any Windows (XP, Vista, 7). Not sure if a restart is necessary, though.

For WAMP it’s usually something like

...;C:\...\wamp\bin\php\{php_folder}

Please note:
If you have multiple partitions, don’t put your cake/php stuff (WAMP, XAMPP) on C:. There might be some user rights issues. You are better off using D:/ or any other partition. And you shouldn’t have any persistent data on the system partition, anyway. I usually have windows and installed programs one C:, everything else is on other partitions.

Don’t just open a console. In order to use the console for any of your apps you need to navigate to the APP folder. Otherwise "cake …" etc will not find the shell scripts in your app:
C:...\project\app>

Hot Tip:
To quickly open a cake console on any of your apps, simply press SHIFT + right click on the app folder.
You will now see a new button "Open console here" / "Eingabeaufforderung hier öffnen".
That’s it. With "cake" you should see the welcome screen and a list of all possible shells.

And – if you did everything right – you should be able to execute your shells like so:

cake command args -params

For example to bake a model:

cake bake model

Linux

See my new article about it

Important

If you need to run both 1.x and 2.x on the same system (or multiple cake apps/cores on the same system for that matter), don’t append anything besides the pure PHP path to the System path.
You can then create two keyboard shortcuts (like I did) for the different versions with a tool of your choice.

I created
cw1# ("cake windows v1)" for ..\cake\console\cake
cw# for ..\lib\Cake\Console\cake
So I am in the app path and just type cw# bake which automatically translates cw# into the above path and then, of course, starts up the bake shell.
With the ..</code> syntax CLI always uses the correct CakePHP version (in /trunk/lib if your project app is in /trunk/app).

Note: On 2.x you usually also have an APP Console/ folder which you can leverage as a shortcut:

  • Console/cake then suffices for unix
  • .\Console\cake for windows

So in short: DO NOT add the cake path to the system environment path.

 
3 Comments

Posted in CakePHP