debuggable

 
Contact Us
 
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45

ActiveDOM talk slides from jQueryCamp 07

Posted on 30/10/07 by Felix Geisendörfer

If you're looking for the slides to my ActiveDOM talk, I've just uploaded them to SlideShare:

ActiveDOM - "jQuery('a.nd > html :tricks').for('web application').data()"

If you're looking for some of the other talks, checkout the jquerycamp07 tag.

-- Felix Geisendörfer aka the_undefined

 

Sorting challenge

Posted on 25/10/07 by Felix Geisendörfer

Quick challenge,

lets say you have an array like this:

$products = array(
   array('Product' => array('ordering' => 5))
   , array('Product' => array('ordering' => 3))
   , array('Product' => array('ordering' => 9))
   , array('Product' => array('ordering' => 1))
);

and you want to iterate through your products by Product.ordering ASC. Whats the fastest way to do this? I just came up with this nifty little attempt, but I'd love to see some other solutions. (Rules: Don't use anything eval()'ish, but anything you find in Cake 1.2 can be used).

$order = array_flip(Set::extract($products, '{n}.Product.ordering'));
ksort($order);
while ($product = $products[array_shift($order)]) {
   debug($product);
}

-- Felix Geisendörfer aka the_undefined

 

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:

$user = $Session->read('User');
if (empty($user)) {
   throw new AppException();
}

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.

$user = $Session->read('User');
Assert::notEmpty($user);

Or another example, this time rendering a 404 error page if the assertion fails.

$this->Task->set('id', $id);
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

 

Exceptional Cake

Posted on 21/10/07 by Felix Geisendörfer

Hey folks,

sorry for letting you guys wait so long, but here is my promised post on how to use Exceptions in CakePHP. Before you continue reading, be warned that you'll need PHP 5 as well as CakePHP 1.2 for this code to work properly.

First of all. Why did I decide to experiment with exceptions in CakePHP? Well, Object::cakeError() does an ok job at providing me with a way to render some sort of internal error while I'm debug mode. However, I think that is what its really meant for, and its not the way to go for rendering errors to the user directly. Besides you cannot really use it within a static function call, a class that is not a descendant of 'Object', nor do you have any way of "catching" an error thrown this way. All of these things can be addressed by using PHP5s support for custom Exception classes quite elegantly.

But lets look at the code before I explain even further. Put this in /app/error.php:


uses('error');
/**
 * undocumented class
 *
 * @package default
 * @access public
 */

class AppError extends ErrorHandler{
/**
 * New Exception handler, renders an error view, then quits the application.
 *
 * @param object $Exception AppException object to handle
 * @return void
 * @access public
 */

   static function handleException($Exception) {
      $Exception->render();
      exit;
   }
/**
 * Throws an AppExcpetion if there is no db connection present
 *
 * @return void
 * @access public
 */

   function missingConnection() {
      throw new AppException('db_connect');
   }
}
set_exception_handler(array('AppError', 'handleException'));

/**
 * undocumented class
 *
 * @package default
 * @access public
 */

class AppException extends Exception {
/**
 * Details about what caused this Exception
 *
 * @var array
 * @access public
 */

   var $info = null;
/**
 * undocumented function
 *
 * @param mixed $info A string desribing the type of this exception, or an array with information
 * @return void
 * @access public
 */

   function __construct($info = 'unknown') {
      if (!is_array($info)) {
         $info = array('type' => $info);
      }
      $this->info = $info;
   }
/**
 * Renders a view with information about what caused this Exception. $info['type'] is used to determine what
 * view inside of views/exceptions/ is used. The default is 'unknown.ctp'.
 *
 * @return void
 * @access public
 */

   function render() {
      $info = am($this->where(), $this->info);
     
      $Controller = new Controller();
      $Controller->viewPath = 'exceptions';
      $Controller->layout = 'exception';
     
      $Dispatcher = new Dispatcher();
      $Controller->base = $Dispatcher->baseUrl();
      $Controller->webroot = $Dispatcher->webroot;
     
      $Controller->set(compact('info'));
      $View = new View($Controller);

      $view = @$info['type'];
      if (!file_exists(VIEWS.'exceptions'.DS.$view.'.ctp')) {
         $view = 'unknown';
      }
     
      header("HTTP/1.0 500 Internal Server Error");
      return $View->render($view);
   }
/**
 * Returns an array describing where this Exception occured
 *
 * @return array
 * @access public
 */

   function where() {
      return array(
         'function' => $this->getClass().'::'.$this->getFunction()
         , 'file' => $this->getFile()
         , 'line' => $this->getLine()
         , 'url' => $this->getUrl()
      );
   }
/**
 * Returns the url where this Exception occured
 *
 * @return string
 * @access public
 */

   function getUrl($full = true) {
      return Router::url(array('full_base' => $full));
   }
/**
 * Returns the class where this Exception occured
 *
 * @return void
 * @access public
 */

   function getClass() {
      $trace = $this->getTrace();
      return $trace[0]['class'];
   }
/**
 * Returns the function where this Exception occured
 *
 * @return void
 * @access public
 */

   function getFunction() {
      $trace = $this->getTrace();
      return $trace[0]['function'];
   }
}

You'll also need this in your /app/config/bootstrap.php file:

require_once(APP.'error.php');

Now you can do cool stuff like this:

function view($id = null) {
   $this->Task->set('id', $id);
   if (!$this->Task->exists()) {
      throw new AppException(array('type' => '404', 'id' => $id));
   }
   // ...
}

Or like this:

static function svnVersion() {
   static $version = null;
   if (!is_null($version)) {
      return $version;
   }
   
   $version = trim(shell_exec("svn info ".ROOT." | grep 'Changed Rev' | cut -c 19-"));
   if (empty($version)) {
      throw new AppException('no_working_copy');
   } elseif (!is_int($version) || !($version > 0)) {
      throw new AppException('svn_version');
   }
   return $version;
}

Or just as simple as:

function utcTime() {
   $time = strtotime(gmdate('Y-m-d H:i:s'));
   if (!is_numeric($time)) {
      throw new AppException();
   }
   return $time;
}

In either case you'll need a new 'exception.ctp' layout. This layout should be very simple, and ideally work even if no Models could have been loaded or other parts of your system have failed. If you have a dynamic navigation, this means either falling back to a default one, or not displaying anything but a back button.

After you created that you also need a default exception view called 'unknown.ctp'. Mine looks simply like this:

<h1><?php echo $this->pageTitle = 'Oops, an internal error occured'; ?></h1>
<p>Sorry, but something must have gone horribly wrong in the internal workings of this application.</p>

For exceptions that are associated with a HTTP response status like '404', I recommend a view like this:

<h1><?php echo $this->pageTitle = '404 - Page not found'; ?></h1>
<p>We are sorry, but we could not locate the page you requested on our server.</p>

Alright this is nice ... but you can do even more! Having the unified AppError::handleException function allows you to do fun things like logging your exceptions, or even sending out notification emails to the system administrator. Oh and its also very convenient if you want to catch only certain kinds of Exceptions:

try{
   $version = Common::svnVersion();
} catch (AppException $Exception) {
   if ($Exception->info['type'] != 'no_working_copy') {
      AppError::handleException($Exception);
   }
   $version = 'HEAD';
}

One of the things I'm currently trying to do with Exceptions is to build my current application so that it fails fast. By that I mean that I rather have the user see a big fat error message instead of trying to recover from failing functions. Jeff Atwood has an interesting article on this subject which I mostly agree with. However with web applications I feel like we can justify seeing our users see our application crashing hard much more often then with desktop software. That is because its simply much easier to fix the problem for everybody - no software update needed. If you go this path however, please make sure you have rock-solid error reporting form or an e-mail address independent from the server where the app runs on, and mention those in your exception layout.

Anyway, I'm going to periodically make changes to this AppException class and eventually add support to allow people to customize its behavior (like add logging) without having to change the code itself. For now however this should give you some inspiration on how you could leverage some of that yummy PHP5 goodies that a lot of us cake folks sometimes forget about (just b/c cake is PHP4 compatible it doesn't mean our apps have to be!).

Hope some of you find this useful,
-- Felix Geisendörfer aka the_undefined

 

"uhhh.......WAT?" - Introduction to the jQuery community

Posted on 28/9/07 by Felix Geisendörfer

Hey folks,

the idea for this post came to me when I read a post called uhhh.......WAT? on the jQuery mailing list today. For me its a perfect illustration of one of the things that makes me like jQuery the most - the community around it. Don't get me wrong, I also love the CakePHP community - its a great and helpful crowd. But boy, when I see how friendly the people are over at jQuery I sometimes can't believe it. Take a couple of minutes and and think about how you would have re-acted to this post. Its obviously written by somebody who has either not invested more then half an hour to pick up a new technology or has been watching the latest youtube BS while doing so. He is obviously offending the jQuery project / community and provides little to no insight into what his actual problem is. Personally I've reached the point were I trained myself to simply hold on to the urge to start yelling at those people. Sometimes I find myself typing a somewhat sarcastic comment on some blog or mailing list but I can almost always keep myself from hitting submit at the end.

Of course I know a lot of open source communities are known for pretty aggressive behavior in their mailing lists (php / linux kernel come to my mind) and I'm also not saying that I wouldn't miss a refreshing grumpy post from Chris every once in a while. But again, looking over to what those folks working on and with jQuery have achieved with their community is nothing short of amazing.

So if I look at the speed the development the jQuery community is showing then I'm tempted to say a lot of this goes back to situations like this. I'm not sure if the guy who started this rant is going to contribute much to the jQuery project, he might. But the fact that the creator of the library (john) and some of the best folks in the community / dev team (karl, glen, etc.) jump into discussions like this with a positive attitude sends out a signal throughout the community. The message I hear is "this is more then just a piece of software, this is something we take pride in and an opportunity for friendship and collaboration for everybody involved". And this is a good message.

So let me round up this little rant by saying that I love jQuery and CakePHP both (see my religious views on Facebook ^^), but that I hope I will become as positive as the people running this neat JavaScript library and that others may follow me.

What do you think? How would your perfect OS look like and interact with each other?

-- Felix Geisendörfer aka the_undefined

 
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45