RSS
 

Posts Tagged ‘Bootstrap’

CakePHP Tips

22 Jan

All new CakePHP tips collected over the last few weeks.

Dispatcher execution order

Tested on Cake2.3RC:

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

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

Resolve dispatcher conflicts

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

Callback execution order

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

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

Behavior/Model callbacks

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

Controller/Component callbacks

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

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

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

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

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

Helper callbacks

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

Using my test case for it, we get:

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

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

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

Test case callbacks

Please see this post for reference.

"Indirect modification" for pagination

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

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

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

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

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

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

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

Don’t use in_array with mixed integers and strings

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

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

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

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

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

Use STRICT comparison where possible, but ALWAYS for strings

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

Probably the most evident example is:

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

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

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

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

Some more examples:

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

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

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

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

PhpMyAdmin

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

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

Posted in CakePHP

 

CakePHP bootstrap goodies

21 Jun

A list of some things quite handy for every cake app and therefore best placed in the bootstrap.php as they are not (yet) part of the cake core.

# Useful when putting a string together in PHP
define('LF', PHP_EOL);
define('NL', "\n"); // new line
define('CR', "\r"); // carriage return
define('TB', "\t"); // tabulator
define('BR', '<br />'); // line break
# Make the app and l10n play nice with Windows.
if (substr(PHP_OS, 0, 3) == 'WIN') {
	define('WINDOWS', true);
} else {
	define('WINDOWS', false);
}
define('FORMAT_DB_DATETIME','Y-m-d H:i:s');	// used in date(...)
define('FORMAT_DB_DATE','Y-m-d');
define('FORMAT_DB_TIME','H:i:s');
define('DEFAULT_DATETIME',  '0000-00-00 00:00:00');
define('DEFAULT_DATE',      '0000-00-00');
define('DEFAULT_TIME',      '00:00:00');

/**
 * convenience function to check on "empty()"
 * does also work with methods
 * 2009-06-15 ms
 */
function isEmpty($var = null) {
	if (empty($var)) {
		return true;
	}
	return false;
}

/**
 * of what type is the specific value
 * useful for cake < 2 - since cake2.0 it is integrated in debug()
 * 
 * @return type: NULL, array, bool, float, int, string, unknown
 * 2009-03-03 ms
 */
function returns($value) {
	if ($value === null) {
		return 'NULL';
	} elseif (is_array($value)) {
		return '(array)'.'<pre>'.print_r($value,true).'</pre>';
	} elseif ($value === true) {
		return '(bool)TRUE';
	} elseif ($value === false) {
		return '(bool)FALSE';
	} elseif (is_numeric($value) && is_float($value)) {
		return '(float)'.$value;
	} elseif (is_numeric($value) && is_int($value)) {
		return '(int)'.$value;
	} elseif (is_string($value)) {
		return '(string)'.$value;
	} elseif (is_object($value)) {
		return '(object)'.get_class($value);
	} else {
		return '(unknown)'.$value;
	}
}

/**
 * uses native PHP function to retrieve infos about a filename etc.
 * @param string type (extension/ext, filename/file, basename/base, dirname/dir)
 * @param string filename to check on
 * //TODO: switch parameters!!!
 * 2009-01-22 ms
 */
function extractPathInfo($type = null, $filename) {
	switch ($type) {
		case 'extension':
		case 'ext':
			$infoType = PATHINFO_EXTENSION; break;
		case 'filename':
		case 'file':
			$infoType = PATHINFO_FILENAME; break;
		case 'basename':
		case 'base':
			$infoType = PATHINFO_BASENAME; break;
		case 'dirname':
		case 'dir':
			$infoType = PATHINFO_DIRNAME; break;
		default:
			$infoType = null;
	}
	return pathinfo($filename, $infoType);
}

/**
 * Shows pr() messages, even with debug=0
 *
 * @param mixed $content
 * @param string $class (optional)
 * 2009-04-07 ms
 */
function pre($array, $class = null) {
	$pre_array='';
	$pre_class='';
	if (is_array($array)) {
		if (!empty($class)){ $pre_class=' class="'.$class.'"'; }
		$pre_array='<pre'.$pre_class.'>'.print_r($array,true).'</pre>';
	} else {
		$pre_array = '<pre'.$pre_class.'>'.$array.'</pre>';
	}
	return $pre_array;
}
/**
 * Checks if the string [$haystack] contains [$needle]
 * @param string $haystack  Input string.
 * @param string $needle Needed char or string.
 * @return boolean
 */
function contains($haystack, $needle, $caseSensitive = false) {
  return (!$caseSensitive ? stripos($haystack, $needle) : strpos($haystack, $needle)) !== false;
}
/**
 * Checks if the string [$haystack] starts with [$needle]
 * @param string $haystack  Input string.
 * @param string $needle Needed char or string.
 * @return boolean
 */
function startsWith($haystack, $needle, $caseSensitive = false) {
	if ($caseSensitive) {
		return (mb_strpos($haystack, $needle) === 0);
	}
	return (mb_stripos($haystack, $needle) === 0);
}
/**
 * Checks if the String [$haystack] ends with [$needle]
 * @param string $haystack  Input string.
 * @param string $needle Needed char or string
 * @return boolean
 */
function endsWith($haystack, $needle, $caseSensitive = false) {
	if ($caseSensitive) {
		return mb_strrpos($haystack, $needle) === mb_strlen($haystack)-mb_strlen($needle);
	}
	return mb_strripos($haystack, $needle) === mb_strlen($haystack)-mb_strlen($needle);
}
/*** < PHP5.3 ***/
if (function_exists('lcfirst') === false) {
  	function lcfirst($str) {
  		return (string)(mb_strtolower(mb_substr($str,0,1)).mb_substr($str,1));
	}
}

TB and BR etc really help if you write PHP and don’t want to switch to HTML all the time:

echo $this->foo().BR.$this->foo2();

returns() helped me a lot to figure out the return value of functions. E.g. pr() doesn’t show if NULL or FALSE was returned.

echo returns($this->foo2());
//or
$res = returns($this->foo2();
die(returns($res));
 
No Comments

Posted in CakePHP