debuggable

 
Contact Us
 

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 ; ).

 
&nsbp;

You can skip to the end and add a comment.

[...] Felix Geisendorfer is in the process of moving his blog from Wordpress to a customized “light-weight CakePHP replacement” he’s developing to be extended later on. His post shares some of the tips he found so far. 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. [...]

Joaquin Windmüller said on Sep 25, 2007:

Hey, I've been thinking on doing this for a while.

The thing is that wordpress is a great software (and now with svn updates much more) and the little time I have free have kept me from trying anything. I don't know if you intend to have a plugin interface like the one wordpress has, it would be nice.

Jonathan Snook's blog runs on cakephp too, so you're not the only one.

I'm really interested to see what you come up with, and also to help if you need any.

Good Luck.

femeref said on Sep 25, 2007:

Eventhough it's in Dutch and not everything is working yet (hence the strikethroughs here and there), my blog runs on cakePHP too.

Sergey Storchay said on Sep 25, 2007:

He-he =)
I'm allready there. And extremely happy about it.

Now I'm working on trackbacks and XML-RPC =)

Felix Geisendörfer said on Sep 27, 2007:

Joaquin: What I'll do is to set up the new blog on my dev server pretty soon and invite everybody who is interested in checking it out and trying it out. The data on there will be migrated from the "real" blog every hour or so using a cron job so the comments made on the new one will not be permanent but good enough for testing purposes.

-- Felix

Matt Curry said on Sep 27, 2007:

Another approach, which I did for a client recently, is to integrate WordPress into CakePHP. I wish I could publish the code, but it wasn't all that hard.

Basically I dropped an installation of WordPress into Cake's webroot and did a normal install. Then I altered wp-config.php to pull in database settings from Cake's config/database.php. Then I made a customized wordpress template that pulls in a cake layout, essentially rendering the blog as the content_for_layout.

All the WordPress functionality (posts, trackbacks, pings, comments, pluggins...) work as normal. The only negative is having a separate admin area.

Felix Geisendörfer said on Sep 27, 2007:

Matt: No I want to see no WordPress around anymore. I had a good time with it and this blog wouldn't exist if it wasn't for WordPress, but its time to move on ; ).

sf devblog said on Oct 02, 2007:

Why not code igniter ? ;)

Felix Geisendörfer said on Oct 02, 2007:

@sf devblog: Try CakePHP if you want to find out ; ).

klevo said on Apr 24, 2008:
RaiulBaztepo  said on Mar 28, 2009:

Hello!
Very Interesting post! Thank you for such interesting resource!

PS: Sorry for my bad english, I'v just started to learn this language ;)

See you!

Your, Raiul Baztepo

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.