Assert the yummyness of your cake
Posted on 22/10/07 by Felix Geisendörfer
Hey folks,
if you liked my last post on how to use PHP5 Exceptions in CakePHP, then here is a little addition to it:
When reading over my code, I saw that I had a lot of statements like this in there:
Now there is nothing really wrong with that. However, I suddenly remembered what assertions are and how they could make my code more readable, while making it shorter. I took a quick look at PHP's assert function, but decided that I don't like it. For one because it takes a string value as a parameter that is than eval()'d and also because there is no way to "catch" failed assertions. Now don't get me wrong, I don't think you should normally catch either assertion or exceptions, as they are not meant for flow control. However, I hate limiting myself. The ability to quickly *hack* a piece of software without having to modify any of its actual code is something I really enjoy. So what I ended up doing was basically to create a class called Assert, and abuse it as a name-space for a whole bunch of useful assertion functions. If an assertion succeeds, it simply returns true. If it doesn't, it throws an AppException which will render a nice page to the user telling him what a crappy programmer I am : ).
But enough talk, lets look at how the above code could be re-factored using assertions.
Or another example, this time rendering a 404 error page if the assertion fails.
if (!$this->Task->exists()) {
throw new AppException('404');
}
// turns into
$this->Task->set('id', $id);
Assert::true($this->Task->exists(), '404');
Ok, ok ... here is the actual code that you'll need to try it out:
* undocumented class
*
* @package default
* @access public
*/
class Assert{
/**
* undocumented function
*
* @param unknown $a
* @param unknown $b
* @param unknown $info
* @param unknown $strict
* @return void
* @access public
*/
static function test($val, $expected, $info = array(), $strict = true) {
$success = ($strict)
? $val === $expected
: $val == $expected;
if ($success) {
return true;
}
$calls = debug_backtrace();
foreach ($calls as $call) {
if ($call['file'] !== __FILE__) {
$assertCall = $call;
break;
}
}
$triggerCall = current($calls);
$type = Inflector::underscore($assertCall['function']);
if (is_string($info)) {
$info = array('type' => $info);
}
$info = am(array(
'file' => $assertCall['file']
, 'line' => $assertCall['line']
, 'function' => $triggerCall['class'].'::'.$triggerCall['function']
, 'assertType' => $type
, 'val' => $val
, 'expected' => $expected
), $info);
throw new AppException($info);
}
/**
* undocumented function
*
* @param unknown $val
* @return void
* @access public
*/
static function true($val, $info = array()) {
return Assert::test($val, true, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function false($val, $info = array()) {
return Assert::test($val, false, $info);
}
/**
* undocumented function
*
* @param unknown $a
* @param unknown $b
* @param unknown $info
* @return void
* @access public
*/
static function equal($a, $b, $info = array()) {
return Assert::test($a, $b, $info, false);
}
/**
* undocumented function
*
* @param unknown $a
* @param unknown $b
* @param unknown $info
* @return void
* @access public
*/
static function identical($a, $b, $info = array()) {
return Assert::test($a, $b, $info, true);
}
/**
* undocumented function
*
* @return void
* @access public
*/
static function pattern($pattern, $val, $info = array()) {
return Assert::test(preg_match($pattern, $val), true, am(array('pattern' => $pattern), $info));
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function isEmpty($val, $info = array()) {
return Assert::test(empty($val), true, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function notEmpty($val, $info = array()) {
return Assert::test(empty($val), false, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function isNumeric($val, $info = array()) {
return Assert::test(is_numeric($val), true, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function notNumeric($val, $info = array()) {
return Assert::test(is_numeric($val), false, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function isInteger($val, $info = array()) {
return Assert::test(is_int($val), true, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function notInteger($val, $info = array()) {
return Assert::test(is_int($val), false, $info);
}
/**
* undocumented function
*
* @return void
* @access public
*/
static function isIntegerish($val, $info = array()) {
return Assert::test(is_int($val) || ctype_digit($val), true, $info);
}
/**
* undocumented function
*
* @return void
* @access public
*/
static function notIntegerish($val, $info = array()) {
return Assert::test(is_int($val) || ctype_digit($val), false, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function isObject($val, $info = array()) {
return Assert::test(is_object($val), true, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function notObject($val, $info = array()) {
return Assert::test(is_object($val), false, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function isBoolean($val, $info = array()) {
return Assert::test(is_bool($val), true, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function notBoolean($val, $info = array()) {
return Assert::test(is_bool($val), false, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function isString($val, $info = array()) {
return Assert::test(is_string($val), true, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function notString($val, $info = array()) {
return Assert::test(is_string($val), false, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function isArray($val, $info = array()) {
return Assert::test(is_array($val), true, $info);
}
/**
* undocumented function
*
* @param unknown $val
* @param unknown $info
* @return void
* @access public
*/
static function notArray($val, $info = array()) {
return Assert::test(is_array($val), false, $info);
}
}
This is probably still missing a couple kinds of assertions, I'll add whatever ones you guys think are needed to this. I'm also interested in what you guys think about this stuff in genera. I mean are there any opinions on whether you think applications should crash hard if something unexpected happens, or would you promote the idea of trying to recover from failures by providing default values and things as much as possible?
-- Felix Geisendörfer aka the_undefined
You can skip to the end and add a comment.
Felix Geisendorfer's Blog: Assert the yummyness of your cake...
...
[...] Felix Geisendorfer has posted an addition to his previous look at exceptions in CakePHP with a modification that uses assertions instead of an if to check the value of a variable. I suddenly remembered what assertions are and how they could make my code more readable, while making it shorter. I took a quick look at PHP’s assert function, but decided that I don’t like it. […] What I ended up doing was basically to create a class called Assert, and abuse it as a name-space for a whole bunch of useful assertion functions. [...]
[...] Assert the yummyness of your cake. Exceptions in CakePHP Posted in October 25th, 2007 by admin in PHP, Programming Felix Geisendorfer has posted an addition to his previous look at exceptions in CakePHP with a modification that uses assertions instead of an if to check the value of a variable. Hey folks, [...]
[...] Then you're in for a surprise. Because as of revision 5895 Model::save() now returns Model::data on success if its not empty. Now most of us do not use strict comparison for checking the return value of Model::save(), but I was stupid enough to do it as part of my new "fail hard fast" strategy : ). So suddenly I had stuff blowing up all over the place. [...]
PHP's assert() doesn't actually need to take a string argument, that's just for when you have debugging turned off with the assert ini options so that the code in the asserts isn't needlessly executed.
Although I don't quite agree with the 'Assert' name, the implementation is elegant. It definitely lets you remove some annoying/redundant code.
Peter Goodman: What name would you propose instead? We are planning on releasing our Exception handler that makes use of the Asserter and are therefore open to name suggestions.
Hey Felix, I really enjoy your blog and I've got a question about your Assert class. Well, actually I have a problem. The problem is, that it don't renders the layout/view when an Exception was thrown.
I don't really have a clue about what to do ... I'm using Cake 1.2 RC3. Maybe you have an idea about where to find the problem? Because I would really like to use it!
Ohhh ... and ... everything works fine (the layout/view) is rendered, when I comment out the "ob_start();" and "$out = ob_get_clean();" and Cake's core view class or if I add an echo/pr before the "ob_start();"
Is this class still valid in CakePHP? Can you post an example Controller (or Model :) ) using this? I'm a bit lost as to how to utilize this class, other than including it as a vendor, which if there is a better way, I'm open to hearing about it :)
This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.
Very cool Felix.
I must admit seeing "assert" in non-test code is a little strange for me, but the overall solution is very elegant.