debuggable

 
Contact Us
 
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61

Agile web development (with CakePHP)

Posted on 14/8/06 by Felix Geisendörfer

For everybody who has read my previous post on automatisation, there are good news:

After having finished the "Pragmatic Programmer", I'm reading "Practices of an Agile Developer" and I enjoy it a lot. So I've decided that, over the course of the next couple days (or weeks), I'll dedicate quite a bit of my time to explore ways to improve the way I write web applications. This includes getting into unit/integration testing, automating workflows, and also adopting better coding techniques. A lot of it will eventually speed up processes here and there, but most of it will be directed torwards higher quality (which will hopefully lead to less bugs and reduce work on "finished" projects). A lot of times I'll try to translate some of the tips out of the books I mentioned above into the world of web development, on other times you'll just see my regular php related code postings ; ).

Right now, I'm trying to get a list of good technologies to help me on my way to agility. So far I've come across a couple very nice ones for some of the things I plan to do:

Automatisation: Apache Ant

In my last post I wrote about using bash scripts to automate some of my common tasks. Daniel Hofstetter pointed out that he is using Ant for his projects, and since I respect his opinnion a lot, I went ahead and checked it out:

The official website states that "Apache Ant is a Java-based build tool. In theory, it is kind of like Make, but without Make's wrinkles.". Usally tools like make (or Ant) are being used for java/c/c++ or other compiled languages to help with the process of creating the binary files by providing ways to define a toolchain. In the (Cake)PHP world, we don't "compile" things, so I wasn't quite sure if it would be useful. But as it turns out those folks working on ant are going way beyond that. Ant ships (or can be extended) with tools to complete tasks like: Zipping, Mailing, FTP, SVN/CVS, Image editing, Unit testing, MySql, and many more. Lots of them very useful when working on web projects. I already used it to automate the deployment (strip .svn files, upload to ftp, update sql) of my latest project and I'm in the process of refactoring it to be useful for any CakePHP app. I'll write more about it when I feel like I gained enough familarity with it.

In case you are interested in it already and want to check it out by yourself:

Testing: Simple Test for PHP

For unit testing I am probably going to use the popular Simple Test testing framework. I've looked at some other ones but I've heard a lot of good things about this one so I'll give it a try. However, I am probably not going to use CakePHP's TestSuite for it. That's mostly because it has no official documentation / support and it also seems to have some problems when running from within a sub folder. I want my unit (and integrational) testing to work from the command line, so I can include it into ant's deployment task (nothing ships unless it passes the test).

If anybody has other suggestions of which testing framework to use, i'd be glad to hear them ; ).

Testing: Selenium Remote Control

In terms of Integration/Acceptance testing, it seems like the Selenium framework is the only thing around that allows you to run your tests directly inside some of the major browsers (ie, firefox, opera, safari (?)). This allows for not only testing the normal html output, but also most of the fancy ajax features one might uses in his application. Selenium itself uses javascript to perform a good form of cross site scripting (xss) that allows you to simulate the behavior of a user. You can make it click links, submit forms, validate texts etc.. Selenium Remote Control is an addition to Selenium Core that allows you to use a programming language to write your integrational tests, rather then Selenium Core's default Selenese markup (which basically is made up by a bunch of html tables). That way you can output the results to just about anything, like a command line for my needs ; ).

Since php is not on the list of supported languages I'll not waste my time to write a hack for it, but rather learn one of the other languages it supports. Most likely that'll be ruby (but don't you worry, I'm not going to join the rails crowd at any point soon).

Testing: Selenium IDE

Selenium IDE is an extension for Mozilla Firefox that'll allow you to manually record things you do in your browser and play them back. Yes, you've heard right! It's the exact kind of neat little tool that you wanted to have years ago when you tried to get this form to work and typed in sample data by hand over and over again! Well it's 2006 and you really shouldn't be doing this any more ; ). One or two times are ok, but if you find yourself running the same kind of test for more then 3 times in a row, you might want to use this little tool instead. But anyway, that's just it's basic funcionality. What it will do as well, is to generate Selenese, Java, Ruby, Python, C# or Perl scripts for you, that you can use for Selenium Remote Control. Neat, uhm?!

Finally ...

... I ran out of tools to point out and have come to the end of this posting. One of the things I want to say before I quit firefox in order to bring out the trash, is that I don't know if all the tools I mentioned here, will really be the ones I end up using. I've played around with ant for the last couple of days, and I used to work with Selenium core, but other then that I might change my mind again. In case you have other programs you like for agile web development, or things one of the ones I mentioned isn't any good, just drop me a comment and I'll check it out.

Anyway, thanks for reading, I hope I have some more "productive" stuff to write about next time ; ).

--Felix Geisendörfer aka the_undefined

 

A word on Firefox

Posted on 18/7/06 by Felix Geisendörfer

I recently discovered that my (and propably many others) favourite browser, Mozilla Firefox can become your worst enemy when developing web sites.

Don't get me wrong, I think it's the most helpful tool for me when trying to get accessibility straight and do things right in general. I mean there are a lot of extensions that make my life a lot easier, but sometimes all of that is just working against you.

The case when this happens, is when you have your firefox setup really nicely and use it to test your web site. You'll only occasionally check if everything works and looks the same in the other browsers (ie, opera, ...), but then return to your comfortable little fox. If you are like me you've got a couple extensions like:

  • Mouse Gestures, which will allow you to open & close tabs quicker then you can say 'firefox' out aloud
  • AdBlock (with Filterset G.), which keeps all those annoying flash pop ups and other commercials away from you
  • Web Developer Toolbar, which allows you to quickly enable/disable graphics, css, js, cookies and to analyze any site you might come across
  • Some PageRank checker, which together with the Alexa rating allows you to guess on the traffic of the web site you view
  • IE Tab, which quickly lets you switch to Internet Explorer if something doesn't work in FF
  • Stumble Upon, which is useful for bookmarking things real quick that you might want to come back to later on without leaving a mess in your FF bookmars

For a complete list of my extensions have look here or at this screenshot.

But that's not all, don't forget about the things that are directly build into firefox, most notably tabed browsing and the js debugger that won't create a little pop up if there was an error (like IE does).

Now you might say: What is bad about this? All those extensions sound really useful!

The answer is simple: They change the way you percieve the web (and the sites you build).

With all those extensions, browsing the web is incredibly more comfortable then it is for your average IE user (or even non-extensions ff/opera one). You simply don't get why people might complain that using your web site isn't easy, because when you use it, you open 3-5 tabs, not seeing the ads you might run yourself, not getting annoyed if some js messages pop up and so on. It's like you are driving a state of the art BMW and wondering why all those [put a shity car in here] drivers complain about the streets being in bad shape. You are living in a different world.

But it might even comes worse, some extensions can cause you to have issues: I recently realized that my Sessions Saver extensions is responsible for problems when uploading files. Messages like "A script is causing the site to run slowly" or a random instance of the message that pops up when you hit F5 on a page requested by a POST can be traced bag to bad code in there. You seriously will got nuts when you try to fix this in your code ; ). But that just as a side note, back to the main story:

What can you do about being a spoiled firefox user? Well the best thing for me has been to create a new, totally uncustomized firefox profile (run "firefox.exe -profilemanager" for this) and to do a complete "joe average" user session on the page you create using it. Don't use all the shortcuts you know, don't use tabbed browsing, use the back & forward key to navigate, and try to act like normal people do. You will find a lot of things that could need improvment in terms of usability that you weren't aware of before.

Another thing people forget about is to educate. If your site isn't only visted by geeks like this weblog is, why don't share some advices with your users? Use browser detection to check for IE users and display reasons to switch to firefox (specific to your site) to them. Do it in a discreet and not annoying manner and you might make people's day. Same goes for firefox users, display a hint showing a useful shortcut like "Hold Strg and Click on a link to open it in a new Tab" or "Press Strg + W to close one" to them and maybe make those tips links with more detailed informations about how they can utilize this to get a better experience on your web site (and the web in general). Yes, some people will already know this stuff, and might think "err, excuse me, I'm no idiot.", but the truth is, most people won't. They will appriciate it if you show them ways to save time by learning how to make a better use of their web browser.

But again, I think that you as web developer should sometimes go back to the basics and start to improve things from there. Because it will be a long time until you have 70%++ of (skilled) Firefox users visiting your site like here on ThinkingPHP ; ).

--Felix Geisendörfer aka the_undefined

 

Issues with output buffering in CakePHP

Posted on 16/7/06 by Felix Geisendörfer

Output buffering is a useful way to accomblish things in php like gzipping all html output and such. A while ago I wrote about it on this blog (see "A miracle called gzip") as well.

The reason I write about it again is, that I have discovered a little issue with it in CakePHP. Because if you use:

ob_start ('ob_gzhandler')

or similar commands in your beforeFilter, you might suddenly notice that setting DEBUG > 1 in app/config/core.php will not give you the usal sql dump any longer. I'm not quite sure why this happens, but I think it's because CakePHP uses ob_buffering as well so there might be conflicts. The easiest solution I found is this one (this time as a complete code example):

if (DEBUG<2)
{            
    @ob_start ('ob_gzhandler');
    header('Content-type: text/html; charset: UTF-8');
    header('Cache-Control: must-revalidate');
    $offset = -1;
    $ExpStr = "Expires: " .
    gmdate('D, d M Y H:i:s',
    time() + $offset) . ' GMT';
    header($ExpStr);
}

I had a hard time debugging this issue today, so I hope this might saves you the trouble I had ; ).

--Felix Geisendörfer aka the_undefined

 

Check if an action was called from within a Controller

Posted on 6/7/06 by Felix Geisendörfer

Gopher just asked me weather it was possible in CakePHP to see if an action was invoked by the dispatcher or called by $this->action();. So since I think this is one of these things that could be useful for others as well, here is my solution:

All you need to do is to check whether $this->action and the name of the function that is currently beeing executed match. This can be done like this:

if ($this->action == __FUNCTION__)

So for example you could use it like this:

class PostsController extends AppController
{
    function index()
    {
        $post3 = $this->view(3);
    }

    function view($id)
    {
        $post = $this->Post->find(array('id' => $id));
       
        if ($this->action != __FUNCTION__)
            return $post;
        else
            $this->render();
    }
}

Ok, this is a pretty useless example I have to admit, but I'm sure there are more interesting ways to utilize this ; ).

--Felix Geisendörfer aka the_undefined

 

Spotting performance leaks in your application

Posted on 18/6/06 by Felix Geisendörfer

Today I was implementing a new and critical functionality into SpliceIt! that allows plugins to register global hooks like beforeFilter, afterFilter, etc. in order to make their own changes to the AppController (SpliceItAppController), even when they are not beeing requested. That way you can simply drop in a menu plugin, and your layout will automatically have the variable $menu set, even so the user didn't request any action from the menu plugin itself.

Anyway, when I implemented the first version I noticed a good performance loss right away, which I didn't like very much. So I had to figure out which part of my code was responsible for giving my processor such a hard time ; ). In order to do this I wrote a useful little singleton class that allows you to start, pause and resume multiple timers throughout your app in order to see what parts are eating up what performance.

Usage is very simple, after you've included the class with require/vendor/whatever you can use it like this:

Performance::startTimer('Sleep Walking');
sleep(1);
Performance::pauseTimer('Sleep Walking');
sleep(1);
Performance::resumeTimer('Sleep Walking');
sleep(1);

Performance::debugTimersPercantage();

// Outputs an array like this:
// Array
//  (
//       [sleep walking] => 1.99932789803 (52.04%)
//       [_total] => 3.84202480316 (100%)
//  )

Want to play around with it yourself? Here is the code:

class Performance
{
    var $_timers = array();
    var $time_start;
   
    /**
    * Returns a singleton instance of the Performance class
    *
    * @return unknown
    */

    function &getInstance()
    {
        static $instance = array();
        if (!$instance) {
            $instance[0] = &new Performance;
        }
            return $instance[0];
    }    
   
    /**
    * In the constructor we check if we already know the time our Application started (by checking for
    * the variable $TIME_START like cakephp set's it. Or we use the current time as our starttime.
    *
    * @return Performance
    */

    function Performance()
    {
        global $TIME_START;
   
        if (!empty($TIME_START))
            $this->time_start = $TIME_START;
        else
            $this->time_start = $this->getMicrotime();
    }
   
    /**
    * Starts a new timer $key. If such a timer has already been started it's going to be reset to 0.
    *
    * @param string $key
    */

    function startTimer($key)
    {
        $_this =& Performance::getInstance();
   
        $key = strtolower($key);     
        $_this->_timers[$key] = $_this->getMicrotime();
    }
   
    /**
    * Pauses the timer $key. You can resume it using Performance::resumeTimer()
    *
    * @param string $key
    */

    function pauseTimer($key)
    {
        $_this =& Performance::getInstance();
       
        $key = strtolower($key);
        $_this->_timers[$key] = array($_this->getTimer($key));
    } 
   
    /**
    * Resumes the timer $key if it had been paused before. If not nothing happens, and if such a timer
    * doesn't exists it get's created via Performance::startTimer() automatically.
    *
    * @param string $key
    */

    function resumeTimer($key)
    {
        $now = Performance::getMicrotime();
        $_this =& Performance::getInstance();
       
        $key = strtolower($key);
       
        if (!$_this->isKeySet($key))
            return $_this->startTimer($key);
       
        $timerStart = $_this->_timers[$key];     
       
        if (is_array($timerStart))
            $_this->_timers[$key] = $now-array_pop($timerStart);
    }
   
    /**
    * Removes the timer $key from the list of timers
    *
    * @param unknown_type $key
    */

    function removeTimer($key)
    {
        $_this =& Performance::getInstance();
       
        $key = strtolower($key);     
        if (array_key_exists($key, $_this->_timers) === true)
            unset($_this->_timers[$key]);
    } 
   
    /**
    * Get's the current amount of time ellapsed for timer $timer.
    *
    * @param string $key
    * @return float
    */

    function getTimer($key, $now = null)
    {
        if (empty($now))
            $now = Performance::getMicrotime();
   
        $_this =& Performance::getInstance();
   
        $key = strtolower($key);    
        $timerStart = $_this->_timers[$key];
   
   
        if (is_array($timerStart))
            return array_pop($timerStart);
        else
            return $now - $timerStart;
    }
   
    /**
    * Get's a list of all registered timers and their current amount of ellapsed time.
    *
    * @return array
    */

    function getTimers()
    {
        $now = Performance::getMicrotime();
   
        $_this =& Performance::getInstance();   
   
        $timers = array();
   
        foreach ($_this->_timers as $key => $timer)
        {
            $timers[$key] = $_this->getTimer($key, $now);
        }
   
        $timers['_total'] = $now-$_this->time_start;
   
        return $timers;
    }
   
    /**
    * Returns the percantage that the timer $key has taken up in time compared
    * to the total execution time of the script (see the constructor to make sure
    * this works).
    *
    * @param string $key
    * @param float $now
    * @param float $timer
    * @return float
    */

    function getTimerPercantage($key, $now = null, $timer = null)
    {
        if (empty($now))
            $now = Performance::getMicrotime();
   
        $_this =& Performance::getInstance();
        if (empty($timer))
            $timer = $_this->getTimer($key, $now);
       
        return $timer.' ('.round((($timer)/($now-$_this->time_start)*100), 2).'%)';
    }
   
    /**
    * Get's a list of all timers together with the time percantage they have used up.
    * The total may add up to over 100% if some of the timers have been running at the
    * same time.
    *
    * @return array
    */

    function getTimersPercantage()
    {
        $now = Performance::getMicrotime();
        $_this =& Performance::getInstance();
       
        $timers = $_this->getTimers();
        $percantageTimers = array();
       
        foreach ($timers as $key => $timer)
        {
            $percantageTimers[$key] = $_this->getTimerPercantage($key, $now, $timer);
        }      
       
        return $percantageTimers;       
    }
   
    /**
    * Checks if the timer $key exists or not.
    *
    * @param unknown_type $key
    * @return unknown
    */

    function isKeySet($key) {
        $_this =& Performance::getInstance();
       
        $key = strtolower($key);
        return array_key_exists($key, $_this->_timers);
    } 
   
    /**
    * A convenience function for debug(Performance::getTimer($key));
    *
    * @param string $key
    */

    function debugTimer($key)
    {
        debug(Performance::getTimer($key));
    }
   
    /**
    * A convenience function for debug(Performance::getTimers($key));
    *
    * @param string $key
    */
 
    function debugTimers()
    {
        debug(Performance::getTimers());
    }
   
    /**
    * A convenience function for debug(Performance::getTimerPercantage($key));
    *
    * @param string $key
    */
   
    function debugTimerPercantage($key)
    {
        debug(Performance::getTimerPercantage($key));
    }
   
    /**
    * A convenience function for debug(Performance::getTimersPercantage($key));
    *
    * @param string $key
    */
   
    function debugTimersPercantage()
    {
        debug(Performance::getTimersPercantage());
    } 
   
    /**
    * Returns the microtime in seconds as a float. I know php5 / cakephp already have this function,
    * but I wanted a maximum of reusability for this class.
    *
    * @return float
    */

    function getMicrotime()
    {
        return array_sum(explode(chr(32), microtime()));
    } 
}

The license for it is MIT. Another thing I'd like to point out, is the fact that some of the code was inspired/derived from CakePHP's ClassRegistry class which you can find inside cake/libs/class_registry.php.

If you like the class or find a bug, any feedback is welcome ; ).

--Felix Geisendörfer aka the_undefined

 
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61