debuggable

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

PHP 5.2.1 was a evil release - check your server

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

Hey folks,

I think this could be interesting for a lot of you running PHP 5.2.1 in a production environment. While I was at php|works I heard a lot of people say how bad of a release 5.2.1 was and that I should stay away from it. Well I didn't take it to seriously at this point, but when I checked what version of PHP we are running on my client project recently it turns out it was 5.2.1.

At this point we still had significant performance issues with our application (300db tables are fun!) so I suggested Dennis (the project manager) to see if we can upgrade to the latest PHP release. I just talked with him again and was amazed. The upgrade from PHP 5.2.1 to 5.2.4 seems to have given us a 40-60% performance boost in terms of our CPU usage having dropped from being constantly at 100% down to around those values.

So if you have an application in production which struggles with performance issues, check if its running 5.2.1 - you may have a very cheap optimization option available ; ).

HTH,
-- Felix Geisendörfer aka the_undefined

PS: While there have been several bug fixes and improvements in between 5.2.1 and 5.2.4, this bug seems to have caused the biggest issues.

 

Migrating from WordPress to CakePHP

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

Hey folks,

I'm currently working on migrating my blog from WordPress to a light-weight CakePHP replacement that I hope to enhance and customize to my liking easily in future. Its not like WordPress has treated me badly, if it wasn't around I'd probably never have started this blog and never gotten to where I am right now. While I'm giving out kudos, I'd also like to mention that you, the readers of this blog have been an amazingly good audience by sharing your knowledge and opinions on PHP programming with me, while also being very encouraging about the whole thing. I really feel bad when I don't get to blog, so time to do it again : ).

In this post I'm simply going to throw out some snippets to show how I'm currently approaching the whole process in order to give both my insight into what works and what doesn't, while also hoping to get some people to share their insight into migrating legacy apps to CakePHP.

So one of the first things I started was to do a new layout and come up with the visual screen elements that I want to integrate. The next thing I did (and still am doing) is to start a little migration script using the new Cake 1.2 console to get my data moved in the new db schema. I haven't finalized the new schema yet and kind of make it up as I go, run a migration and then implement the functionality to show the data currently available. So far I've been working on migrating the wp_posts and wp_comments table. The first thing that hit me was that WordPress is not good about conventions. For example the primary key on my wp_posts table is 'ID' while it is 'comment_ID' on the comments table. I soon realized that one of the biggest tasks would be to map old Wp table fields to ones that have different names in my new CakePHP blog. But before I go in too much detail, I'll paste the migration script I currently have and then explain it a little further:


uses('model'.DS.'model');
class WpMigrationShell extends Shell {
  var $uses = array(
    'WpPost'
    , 'WpComment'
  );
 
  var $times = array();
 
  function initialize() {
    $this->times['start'] = microtime(true);
    parent::initialize();
  }
 
  function main() {
    $this->out('Migrating WordPress posts ...');
    $this->WpPost->migrate();
    $this->out('Migrating WordPress comments ...');
    $this->WpComment->migrate();
   
    $this->out('Populating WordPress posts counter cache ...');
    $this->WpPost->populateCounterCache();


   
    $this->times['end'] = microtime(true);
    $duration = round($this->times['end'] - $this->times['start'], 2);
    $this->hr();
    $this->out('Migration took '.$duration.' seconds.');
  }
}

class WpMigrationModel extends Model{
  var $useDbConfig = 'wordpress';
  var $newModel = null;
  var $map = array();
 
  function migrate() {
    if (empty($this->newModel)) {
      $this->newModel = substr($this->name, 2);
    }
   
    loadModel($this->newModel);
    $Model = new $this->newModel();
    $Model->execute('TRUNCATE '.$Model->table);
   
    $methods = get_class_methods($this);
    $keys = array_keys($this->map);
    $idKey = $keys[0];
   
    $oldEntries = $this->findAll(null, $keys);
    foreach ($oldEntries as $oldEntry) {
      if (!$this->filter($oldEntry[$this->name])) {
        continue;
      }
      $id = $oldEntry[$this->name][$idKey];
      $Model->create();
      foreach ($this->map as $oldField => $newField) {
        $value = $oldEntry[$this->name][$oldField];
        $migrateFct = 'migrate'.Inflector::camelize($newField);
        if (in_array($migrateFct, $methods)) {
          $value = $this->{$migrateFct}($value);
        }
        $Model->set($newField, $value);
      }
      if (!$Model->save()) {
        die('Could not save '.$this->newModel.' #'.$id);
      }
    }
  }
 
  function filter() {
    return true;
  }
 
  function migrateText($text) {
    return utf8_decode($text);
  }
}

class WpPost extends WpMigrationModel{
  var $useTable = 'posts';
  var $map = array(
    'ID' => 'id'
    , 'post_title' => 'title'
    , 'post_content' => 'text'
    , 'post_status' => 'published'
  );
 
  function populateCounterCache() {
    $Post = new Post();
    $Post->recursive = -1;
    $Post->Comment->recursive = -1;
   
    $posts = $Post->findAll(array('id'));
    foreach ($posts as $post) {
      $Post->set($post);
      $comment_count = $Post->Comment->findCount(array('post_id' => $Post->id));
      if ($comment_count) {
        $Post->set(compact('comment_count'));
        $Post->save();
      }
    }
  }

  function filter($item) {
    if (!in_array($item['post_status'], array('publish', 'draft', 'private'))) {
      return false;
    }
    return true;
  }

  function migratePublished($value) {
    if ($value == 'publish') {
      return true;
    }
   
    return false;
  }
}

class WpComment extends WpMigrationModel{
  var $useTable = 'comments';
  var $map = array(
    'comment_ID' => 'id'
    , 'comment_post_ID' => 'post_id'
    , 'comment_author' => 'author_name'
    , 'comment_author_email' => 'author_email'
    , 'comment_author_url' => 'author_url'
    , 'comment_content' => 'text'
  );
 
  function filter($item) {
    static $Post;
    if (empty($Post)) {
      $Post = new Post();
    }
   
    $Post->set('id', $item['comment_post_ID']);
    return $Post->exists();
  }
 
  function migrateAuthorName($name){
    return utf8_decode($name);
  }
}

Alright there is quite some stuff going on here so let me begin with the basics. I've decided to give all WordPress tables their own model which I prefix with 'Wp' to not overlap with the models I'm using in the new CakePHP application. Those 'Wp' models all extend a common base model which I call the WpMigrationModel. This is because I found that a lot of functionality is shared across them, especially things like mapping old 'Wp' fields to new fields for my CakePHP models as well as filtering out items I'm not interested anymore. All of this is happening in the WpMigrationModel::migrate. Another neat thing I built in is that while the algorithm loops through the fields of my new cake models, it also looks for a function ::migrate. If its found then the function is applied as a "filter" to the old value in WordPress. As you can see, I'm using this to convert my latin1 encoded (wtf) fields to the new utf8 encoding. I also migrate a wp varchar field called 'post_status' to a more sane / simplified tinyint field called 'published'. Once the posts / comments are migrated I finally loop through all posts again in order to populate a counter cache field called 'comment_count'. Oh and before I forget, during the migration of the post comments, I check if those post_id's are still around. This is neccessary b/c no FK restrictions were used by WordPress which lead to some lonely data islands cluttering the db.

Anyway, this of course is just the beginning, but its already most of the data I'm really interested in migrating - I don't mind loosing a couple meta / whatever fields. In a next post I'll show how to replicate some legacy WordPress logic in my new cake blog. For now I've got to stop as my plane to San Francisco from the Atlanta airport is boarding ; ).

-- Felix Geisendörfer aka the_undefined

PS: Typos may be corrected when I get wifi again ; ).

 

Setting up Xdebug on Mac OS X (or Win32 / Linux)

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

If you want to take your debugging to the next level by getting a nice stack / function trace on errors that occur, then you should check out Xdebug by Derick Rethans (who I had the pleasure to meet at php|works). I've only been touching the surface of this powerful extensions so far, but its already been a pleasure to work with and I'll try to write more about it in future. Maybe I can even integrate it into the CakePHP test suite for code coverage analysis at some point.

Anyway, one of the biggest obstacles when getting started with Xdebug was that I had a hard time finding good instructions on how to set it up on Mac OS X (I finally got rid of windows, yeah !). The only good resource I found was stored in Googles cache (wasn't live anymore) and had instructions on how to manually build xdebug from source. However, I didn't have a lot of time back then and compiling would have involved setting up all kinds of additional tools. This was the point when I vaguely remembered that the Komodo IDE was using xdebug. So I did some more research and sure enough, the good folks over at ActivateState actually provide their Xdebug binaries as stand-alone downloads for all major platforms.

On my macbook I'm using MAMP, but the basic concept of setting this up should be cross plattform:

  1. Download the latest Xdebug binaries for your OS from: http://aspn.activestate.com/ASPN/Downloads/Komodo/RemoteDebugging
  2. Copy the xdebug.so / xdebug.dll for your PHP version to your extensions directory (for MAMP this was: /Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20050922/xdebug.so, in Windows its probably C:\php\modules\xdebug.dll)
  3. Find your php.ini file (for my MAMP install this was in /Applications/MAMP/conf/php5/php.ini, under Windows it should be C:\Windows\php.ini)
  4. Add the following lines to your php.ini configuration (MAMP):
[xdebug]
zend_extension=/Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20050922/xdebug.so

or for Windows:

zend_extension_ts="c:/php/modules/xdebug.dll"

To test if things worked or not run a phpinfo() script and see if xdebug shows up in your list of extensions (you'll probably have to restart Apache first). Once that is accomplished you can start to indulge yourself in discovering all the various configuration options and functions there are.

One of my favorite settings allows to make all file paths that show up in error messages turn into links that will open my text editor for the given file at the right line. For Textmate this is simply a matter of adding this line to your php.ini:

xdebug.file_link_format = "txmt://open?url=file://%f&line=%l"

Check the manual of your editor of choice to see what "protocol" it is using for accomplishing the same thing. Also make sure to check out if your editor happens to have built-in Remote Debugging support for Xdebug. A list of those editors can be found in the Xdebug documention (Textmate is not in there yet : /).

Alright, I hope some of you find this useful. If you run Windows or Linux there are also more detailed instructions on the Xdebug website itself, I just found the lack of OS X info out there worth making this little blog post. Kudos go out to Derick for writing this awesome extension and ActiveState for providing Mac OS X (and other plattform) binaries for all kinds of PHP versions.

-- Felix Geisendörfer aka the_undefined

 

How To Transform HTML To Textile Markup - The CakePHP TextileHelper Revisited

Posted on 23/8/07 by Tim Koschützki

 

Composing Methods: Substitute Algorithmn

Posted on 15/8/07 by Tim Koschützki

When you want to replace an algorithmn with one that is clearer, replace the body of the method with the new algorithmn.

Motivation

This often does not appear as a real refactoring. However, "Substitute Algorithmn" should not be underestimated. It's really something we should do all the time.
If you find a clearer way to accomplish something, you should replace the complicated way with the easier one.

Remember this important statement: "Do the simplest possible thing." Designing systems is so hard already - why should you go out of your way to make the task even harder?
Break complex things up into simpler things, and let each solve a piece of the puzzle. Gaius Julius Cesar had the right idea with his "Divide and Conquer" approach.

Programming is such a dynamic action that you often find yourself having to replace an algorithmn alltogether. It will be much easier to do if the current algorithmn is an easy one already.
Also, you may start using a library and thus your code may be subject to duplication. Substitute the corresponding algorithmns and get rid of the duplication.

Make sure you decompose your algorithmns as much as you can and use many small methods for it.

Mechanics

  • Prepare your alternative algorihtmn.
  • Run your new algorithmn against your unit tests. If the results are the same, you are finished.
  • If the results are not the same, run each test case with the old a new algorihtmn and solve those that fail.

Example

Before:

function foundPerson($people = array()) {
  $numPeople = count($people);
  for($i=0;$i<$numPeople;$i++) {
    if($people[$i] == "Tim") {
      return "Tim";
    }
    if($people[$i] == "Felix") {
      return "Felix";
    }
    if($people[$i] == "John") {
      return "John";
    }
  }
  return "";
}

After:

function foundPerson($people = array()) {
  $numPeople = count($people);
  $candidates = array("Tim", "Felix", "John");
 
  for($i=0;$i<$numPeople;$i++) {
    if(in_array($people[$i], $candidates)) {
      return $people[$i];
    }
  }
  return "";
}

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