RSS
 

Archive for August, 2010

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

 

Development vs. Productive Setup

17 Aug

If you want to deploy your cakephp app you usually have to change a few lines of code. But we want to minimize that.

My example setup:
- development: Windows (>= Vista)
- productive: Linux (Debian)

Database Setup

Create a library file and extend this from your DATABASE_CONFIG. This way you can let your app automatically select the correct database var at runtime.

class BASE_CONFIG {
	var $environments = array('default', 'sandbox', 'online');
 
	var $default = array();
 
	/**
	 * switch between local and live site(s) automatically by domain
	 * or manually by Configure::write('Environment.name')
	 * 2009-05-29 ms
	 */
	function __construct() {
		$environment = $this->getEnvironmentName();
		if ($environment && isset($this->{$environment})) {
			$this->default = array_merge($this->default, $this->{$environment});
		}
		$this->test = $this->default;
		$this->test['prefix'] = 'zzz_';
	}
 
 	function getEnvironmentName() {
		$environment = (String)Configure::read('Environment.name');
		if (empty($environment) && !empty($_SERVER['HTTP_HOST'])) {
			$server = (String)$_SERVER['HTTP_HOST'];
			if (!empty($server)) {
				foreach ($this->environments as $e) {
					if (isset($this->{$e}) && isset($this->{$e}['environment']) && $this->{$e}['environment'] == $server) {
						$environment = $e;
						break;
					}
				}
			}
		}
		return $environment;
	}
}

Example for your database.php:

App::import('Lib', 'BaseConfig');
 
class DATABASE_CONFIG extends BASE_CONFIG {
	var $default = array(	// localhost
		'name' => 'default',
		'environment' => 'localhost',
		'driver' => 'mysqli',
		'persistent' => false,
		'host' => 'localhost',
		'login' => 'root',
		'password' => '',
		'database' => 'cake_app',
		'prefix' => 'xyz_',
		'encoding' => 'utf8'
	);
	var $sandbox = array(	// online test
		'name' => 'test',
		'environment' => 'test.domain.com',
		'login' => 'root',
		'password' => '',
		'database' => 'cake_app_test',
	);
	var $online = array(	// online productive
		'name' => 'online',
		'environment' => 'www.domain.com',
		'login' => 'root',
		'password' => '',
		'database' => 'cake_app',
	);
}

The same config file on all 3 locations will now select the one corresponding to the environment url.
You could override this, though, by using Configure::write(‘Environment.name’) – but this is not necessary if the domain doesnt change too often.

Debug Mode

Put this in your core.php (it should ALWAYS be 0 by default!):

Configure::write('debug', 0);
# Enhancement
if (!empty($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] == 'localhost') {
	Configure::write('debug', 2);
}

Debug mode is 0 for all online sites and 2 for your local development site.

Custom “Overrides”

There are many variables you need to switch from local to live apps (google.maps key, other api keys, email server credentials, …)
The quick-and-dirty solution would be something like this:

if ($_SERVER['HTTP_HOST'] == 'localhost') {
	$config['Foo'] = array(1,2,3);
} elseif ($_SERVER['HTTP_HOST'] == 'www.domain.com')) {
	$config['Foo'] = array(1,2,4);
} else {
	$config['Foo'] = array(1,5,6);
}

Very fast very ugly.

A little bit cleaner is using two config files: “configs.php” and “configs_private.php”. The second one is not synched (or in SVN) – it contains passwords and environment specific content.
Include it in your bootstrap AFTER you included the default configs.

As i just mentioned, it has another upside: Beeing a environment based tmp-file it does not store any sensitive information in the SVN (or whatever other backup tool you use).
This way you can easily set up configuration “stubs” in your configs.php and insert the passwords in your private config file.

Folder Setup and .htaccess

You can use the same folder setup:
- cake
- app
- vendors
With …/app/webroot/ as “visible folder” and …/app/webroot/index.php as dispatching script.

In my htaccess file i use “!^localhost” to avoid redirects locally:

RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} !^localhost [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]

This way the same file can be used for both environments.

Linux/Windows differences

Use the constant DS (= Directory Separator) anywhere you can. If you hardcode it with / (Linux) oder \ (Windows) it might break some code. Usually it doesn’t, but it is cleaner to use DS anyway.

Example:

file_put_contents(TMP.'xyz'.DS.'file.txt', 'some text')

Set up a WINDOWS constant in your bootstrap.php – this way you can dynamically decide what function to use or to not use specific lines of code (like console “exec” commands).

if (substr(PHP_OS, 0, 3) == 'WIN') {
	define('WINDOWS', true);
} else {
	define('WINDOWS', false);
}

Example:

if (WINDOWS) {
		//whatever
} else {
		exec($something);
}

Uploading your changes

I use a shell tool i wrote – which uses “rsynch” and only uploads the delta (changes made so far).
Make sure you DON’T upload any tmp stuff or even worse, override environment based files like uploads, cached files etc.
Using batch scripts you can usually exclude some directories.

Final Tips

Remember that linux needs explicit folder permissions. So you need to manually (or per batch script) set /tmp to 777 recursivly as well as any other folder which you want to write into from your scripts.

Also clear the cache after updating! Otherwise your model relations as well as some constants will be outdated and cause errors or complete failure.

 
5 Comments

Posted in CakePHP

 

How to implement Captchas properly

09 Aug

What is a captcha?

They protect forms on websites from spammers and bots (@see Wikipedia for details). The main idea: Display some kind of code a human can easily read and submit but a computer can not.

How NOT to implement captchas

This part is even more important, because there is not only one correct way, but even more wrong ways to go here.

Don’t use sessions unless you really have to.
Pages that use something like $_['Captcha']['field'] and override this one on every form, really freak me out! It makes working with two or more tabs impossible, because they override each others captcha values all the time, resulting in a ****** mess.
You could use an array like structure, but your captcha session array can get pretty big in a short amount of time.

More helpful are captchas which use the current form and some hash values based on the fields + current timestamp. It should not be possible to “guess” or “calculate” the hash value. So there is no way to use future “hashs”.
You could use older hashs (like from last week), though. But most bots are programmed to just post right away. They would have to save possible valid “scenarios” for later usage.
Mabye we can come up with something fail-prove later on. For now we want to effectively prevent spam bots to submit their crap without annoying normal users.
The second part is always the hardest. Me – for example – I really hate those image captchas which you can barely read. I often times have to repeat or reload it twice in order to succeed. Annoying comes not even close.

Why do we need captchas

First of all, they make sure that there is no bot (automatic program) posting “spam” or whatever.
But sometimes you just want to add captchas to prevent users from doing some action too often (like friendship requests in a community site etc).

A good example what happens if you dont use captchas, is bakery.cakephp.org.
Some articles have like 36 comments, of which all 36 are SPAM. This is a desaster.
And in this case you even have to be logged in to submit a comment.
The argument that forms for which you need to be logged in don’t need captchas is not contemporary anymore.

Passive Captchas

I already talked about using some hash values based on the fields + current timestamp.
This can be used to generate passive captchas. They are similar to the cake core component “security” which adds some hidden fields to make sure that the fields have not been tempered with.

Both are invisible to the user – they dont even notice the passive captcha. But bots will soon discover that they are facing a wall.
The difference is, that passive captchas should only valid in a specific timeframe. Too fast (less than 2 seconds) is usually a sign for the work of a bot – humans cannot type that fast.
Too late (> 12 hours?) means you need to revalidate anyway, so we would render the form invalid as well. Well designed forms will keep the posted content, so nothing gets lost.

Another aspect to improve security is to use other user specific fields for the hash value like browser agent (cannot change during posts, but is less secure because it can be modified), IP address (can only be modified by very skilled hackers and therefore is pretty secure), …

Active Captchas

Those are the most commonly used ones. Users either have to read an image, calculate numbers or
interpret a sentence. The first one is not suitable for handicapped people.

Usually they are build as extension on top of passive captchas. We first validate the passive one. If the form is OK, we then validate the user input. If validation passes we render the captcha valid.

I decided to use math captchas. They keep you mentally fit and do what they are supposed to. The only important issue is to make it easy enough. I saw pages using / [division] or numbers above 20 in multiplication or even above 100 for summation – which is total overhead.
But other ones could be used as well – simply by changing configuration settings.

Captcha Behavior

Ok, to sum it up, we want captchas that
- don’t annoy users
- protect as good as absolutely necessary
- can be used with tabs
- can be easily implemented and configured

The idea is, that we want to add a single form field as well as attach a single behavior to our model.
Thats all there is to it.
That’s why a behaviour in combination with a helper does the trick perfectly.

The code is in my github tools plugin:
captcha behavior
captcha helper
and functionality that both classes use is in captcha lib.

Note: The links are for 1.3 – if you want the 2.0 stuff, you need to switch to this branch.

Helper usage (in the view):

echo $this->Captcha->input(); // or input('Modelname') if model is different from the form model

Behavior usage (in the controller):

$this->User->Behaviors->attach('Captcha');
	if ($this->User->save($this->data)) { ... }

Pretty straight forward, isn’t it?

Current weaknesses (apart from its strenghts):
- possible “hash extraction” with unlimited use of those valid hashs (session or db to prevent?)

Final notes

Right now it is mainly used for math captchas (active captchas) and just passive captchas.
Feel free to update the missing parts like providing more captcha types (image, sentence, …) or processing types (session, cookie, …).

UPDATE 2011-10-13
The i18n translations are now commited, as well: /locale

 
10 Comments

Posted in CakePHP