<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title><![CDATA[Content with Style]]></title>
    <link>http://www.contentwithstyle.co.uk/feeds/rss</link>
    <description />
    <pubDate>Wed, 10 Mar 2010 10:57:37 +0000</pubDate>
    <generator>Zend_Feed</generator>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/ContentWithStyle" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="contentwithstyle" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.bloglines.com/sub/http://feeds.feedburner.com/ContentWithStyle" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.plusmo.com/add?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://plusmo.com/res/graphics/fbplusmo.gif">Subscribe with Plusmo</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.thefreedictionary.com/_/hp/AddRSS.aspx?http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://img.tfd.com/hp/addToTheFreeDictionary.gif">Subscribe with The Free Dictionary</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.bitty.com/manual/?contenttype=rssfeed&amp;contentvalue=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.bitty.com/img/bittychicklet_91x17.gif">Subscribe with Bitty Browser</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.newsalloy.com/?rss=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.newsalloy.com/subrss3.gif">Subscribe with NewsAlloy</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.live.com/?add=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://tkfiles.storage.msn.com/x1piYkpqHC_35nIp1gLE68-wvzLZO8iXl_JMledmJQXP-XTBOLfmQv4zhj4MhcWEJh_GtoBIiAl1Mjh-ndp9k47If7hTaFno0mxW9_i3p_5qQw">Subscribe with Live.com</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://mix.excite.eu/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://image.excite.co.uk/mix/addtomix.gif">Subscribe with Excite MIX</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.yourminis.com/subscribe.aspx?u=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.yourminis.com/images/addtoyourminisbadge.gif">Subscribe with Yourminis.com</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://download.attensa.com/app/get_attensa.html?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.attensa.com/blogs/attensa/WindowsLiveWriter/BadgeredintoBadges_10C02/attensa_feed_button5.gif">Subscribe with Attensa for Outlook</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.webwag.com/wwgthis.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.webwag.com/images/wwgthis.gif">Subscribe with Webwag</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://hub.netomat.net/account/account.autoSubscribe.jspa?urls=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.netomat.net/blogger/images/icon_netomat_feedbutton.gif">Subscribe with netomat Hub</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.podcastready.com/oneclick_bookmark.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.podcastready.com/images/podcastready_button.gif">Subscribe with Podcast Ready</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.flurry.com/pushRssFeed.do?r=fb&amp;url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.flurry.com/images/flurry_rss_logo2.gif">Subscribe with Flurry</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.wikio.com/subscribe?url=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.wikio.com/shared/img/add2wikio.gif">Subscribe with Wikio</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.dailyrotation.com/index.php?feed=http%3A%2F%2Ffeeds.feedburner.com%2FContentWithStyle" src="http://www.dailyrotation.com/rss-dr2.gif">Subscribe with Daily Rotation</feedburner:feedFlare><item>
      <title><![CDATA[Happy New Year 2010]]></title>
      <link>http://www.contentwithstyle.co.uk/content/happy-new-year-2010</link>
      <guid>http://www.contentwithstyle.co.uk/content/happy-new-year-2010</guid>
      <description><![CDATA[<p>A recap over what happened at CwS over the last 12 months and what we're looking forward to.</p>]]></description>
      <content:encoded><![CDATA[<p>It's four days into the new year, and I've had more sunshine in that short time than in the last 3 months together. I'm reasonably sure this applies to Pascal as well, given that at the moment he's making sure the southern hemisphere gets a little CwS love, too. Seems like a good opportunity to say thanks to you, for reading and contributing to our site over the last 12 months.</p>

<h2>What was</h2>
<p>The most exciting thing for CwS in 2008 must've been that Pascal and I got to spend about half the year working together, as real life desk neighbours at Photobox, when I managed to hire him for 2 projects in spring and autumn.</p>

<p>We got around to churn out some lovely best practice code (almost all Zend Framework based) and wrote about it. Gaining some traction with CwS, we started looking into other distribution channels, <a href="http://twitter.com/contentwstyle">Twitter</a> being the first, and our articles were gladly picked up by <a href="http://www.phpdeveloper.org">PHPDeveloper</a> as well as the <a href="http://devzone.zend.com">Zend DevZone</a>. I always meant to write about the increase of traffic and the benefit of hustling your site a little bit (we received some very helpful comments in the process, so it's not all just numbers and pretty graphs), but didn't get around to it. I guess you'll have to trust me on this one.</p>

<p>What definitely wasn't was a series about tips I wanted to publish for people newer to the world of web development, mostly because <a href="http://www.456bereastreet.com/archive/200812/quick_tips_for_web_developers_and_web_designers/">Roger Johansson had the same idea</a> round-about the same time, and he does it so much better.</p>

<p>For your collective and individual enjoyment, I've grouped our 15 most popular posts in (not necessarily from) 2009:</p>
<ol>
<li><a href="http://www.contentwithstyle.co.uk/content/a-css-framework">A CSS Framework</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps">Fixing the Back Button and Enabling Bookmarking for AJAX Apps</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/modular-css">Modular CSS</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/unit-testing-controllers-with-zend-framework">Unit testing controllers with Zend Framework</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/css-background-image-on-html-image-element">CSS Background image on html image element?</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/deploying-php-applications-with-vlad">Deploying PHP applications with Vlad and SVN</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/dynamic-tables-with-xslt">Dynamic tables with XSLT</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/clean-urls-for-a-better-search-engine-ranking">Clean URLs for a better search engine ranking</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/find-your-node-advanced-xpath-commands">Find your node: Advanced XPATH commands</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/a-caching-pattern-for-models">A caching pattern for models</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/xml-validation-in-php">XML validation in PHP</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/4-ssh-config-tips-for-faster-remote-working">4 ssh config tips for faster remote working</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/css-is-worthless">CSS is Worthless</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/my-take-on-jquery-charts">My take on jQuery charts</a></li>
<li><a href="http://www.contentwithstyle.co.uk/content/unit-testing-web-service-based-models-in-zend-framework">Unit testing web service based models in Zend Framework</a></li>
</ol>

<h2>What will be (maybe)</h2>
<p>Of course we're looking forward to another year of CwS that will discuss all aspects of web technology that we deal with, client and server side. Right now there are many exciting news on the horizon (if not already knocking on our door daily), I'm thinking Zend Framework 2.0, PHP 5.3, push concepts such as Comet (especially in the PHP world), and of course the exciting new world of HTML 5 with all its possibilities and pitfalls. The last word in deployment and build processes is surely not spoken without having a look at integrating dpkg or rpm mechanisms, and in more concrete news, I'm doing a PHP certification this year, so hopefully you'll hear some of my experience learned there, too.</p>
<p>Of course we're still open for contributors to write a post here and there, so feel free to contact us if you've got an idea or maybe simply a nice code example you'd like to share.</p>
<p>This leaves me with wishing you all a happy new year, thanks again for being such a smart audience. Here's to you and a great 2010,</p>
<p>from all of us at Content with Style</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=mFpVCGYUNYk:SC42B7c_rXY:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=mFpVCGYUNYk:SC42B7c_rXY:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=mFpVCGYUNYk:SC42B7c_rXY:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=mFpVCGYUNYk:SC42B7c_rXY:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=mFpVCGYUNYk:SC42B7c_rXY:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Mon, 04 Jan 2010 06:52:37 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Viewing BLOB content in phpMyAdmin]]></title>
      <link>http://www.contentwithstyle.co.uk/content/viewing-blob-content-in-phpmyadmin</link>
      <guid>http://www.contentwithstyle.co.uk/content/viewing-blob-content-in-phpmyadmin</guid>
      <description><![CDATA[<p>PhpMyAdmin seems to have changed their concept of how to view BLOB content multiple times, making it hard to find a solution for my recent install.</p>]]></description>
      <content:encoded><![CDATA[<p>In my phpMyAdmin installation, version 3.1.1, none of the old style config settings such as</p>
<pre>$cfgShowBlob = true</pre>
<p> or</p>
<pre>$cfg['ShowBlob']</pre>
<p>had any effect, and the solution was terribly elusive. "view blob", "show blob", "display blob", all that Google suggested were people asking the same question, at various ages of the internet.</p>
<p>Even more annoying, the most common answer was "Why would you want to do that?", and too many times I was reading that they shouldn't be read anyway, because it could be large amounts of, well, binary data, bla bla bla.</p>
<p>In my case this makes very much sense, though, because I was looking at serialized data objects. Don't ask me why it's not a TEXT field (another smartypants suggestion in a forum), that's just the setup I was dealing with.</p>
<p>After much digging and the wish for a search engine that will suggest better search terms to me, I finally decided to bother the source instead, and found "display_blob". Hurray! It comes in this form:</p>
<pre>$_SESSION['userconf']['display_blob'];</pre>
<p>All you need to do is set this session variable (possibly by sticking it into the config? I didn't try it out), or alternatively: Open the content frame and put</p> <pre>&amp;display_blob=true</pre>
<p>into the querystring, and it will keep it in the session for you, when you use the frameset as normal afterwards.</p>
<p>While writing this, I also <a href="http://www.yegq.org/archives/177">saw this page</a>, can anyone translate?</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=ax4aTvlpHDE:0DgwpOKFLoY:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=ax4aTvlpHDE:0DgwpOKFLoY:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=ax4aTvlpHDE:0DgwpOKFLoY:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=ax4aTvlpHDE:0DgwpOKFLoY:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=ax4aTvlpHDE:0DgwpOKFLoY:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Fri, 20 Nov 2009 08:25:39 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Fulltext searches with Xapian and PHP]]></title>
      <link>http://www.contentwithstyle.co.uk/content/searching-with-xapian-and-php</link>
      <guid>http://www.contentwithstyle.co.uk/content/searching-with-xapian-and-php</guid>
      <description><![CDATA[<p>Sometimes MySQL just isn't quick enough. Especially when it comes to fulltext searches. Everything needs to be indexed correctly, and if we're using different fields with different weights for a relevance percentage, things get very complicated quickly. Xapian to the rescue.</p>]]></description>
      <content:encoded><![CDATA[<p>Sometimes MySQL just isn't quick enough. Especially when it comes to fulltext searches. Everything needs to be indexed correctly, and if we're using different fields with different weights for a relevance percentage, things get very complicated quickly. <a href="http://xapian.org/">Xapian</a> to the rescue.</p>

<h2>What is Xapian?</h2>

<p>Xapian is a Search Engine Library, similar to Lucene and Sphinx. It's compiled from C++ code and therefore pretty low level. There are PHP, Perl and Python bindings available for it, which are straight forward to use. Packages are available for Ubuntu and Red Hat, it compiles on OSX and you can run it on Windows via CygWin.</p>

<h2>Demo Scripts</h2>

<p>
Rather than explaining why and how, I decided to create some demo scripts instead.
The XapianWrapper PHP class I've created is quite large, so feel free to <a href="http://www.contentwithstyle.co.uk/resources/xapian-example.zip">download the example zip file</a> as well.
</p>

<h3>db.sql</h3>

<pre><code>
CREATE DATABASE `demo`;

CREATE TABLE `demo`.`demo` (
`id` INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`unique_key` VARCHAR( 255 ) NOT NULL ,
`name` VARCHAR( 255 ) NULL DEFAULT NULL ,
`summary` TEXT NULL DEFAULT NULL ,
`date` DATETIME NULL DEFAULT NULL ,
UNIQUE (`unique_key`));

INSERT INTO `demo`.`demo` 
(`id`, `unique_key`, `name`, `summary`, `date`)
VALUES (NULL, 'foo', 'foo', 'foo bar test', '2008-11-05 00:00:00'), 
(NULL , 'bar', 'bar', 'test foo bar', '2009-11-05 00:00:00');        
</code></pre>

<h3>XapianWrapper.php</h3>

<pre><code>
&lt;?php
// includes
require_once 'xapian.php';

// main class
class XapianWrapper {
  const XAPIAN_FIELD_URL = 0;
  const XAPIAN_FIELD_NAME = 1;
  const XAPIAN_FIELD_DATE = 2;
  const XAPIAN_FIELD_UID = 3;
  const XAPIAN_FIELD_SUMMARY = 4;
  const XAPIAN_PREFIX_UID = "UID:";
  
  const SETTINGS_XAPIAN_DB = './xapian_db';

  const SETTINGS_MYSQL_HOST = 'localhost';
  const SETTINGS_MYSQL_USER = 'root';
  const SETTINGS_MYSQL_PASS = 'root';
  const SETTINGS_MYSQL_DB = 'demo';
  const SETTINGS_MYSQL_TABLE = 'demo';

  const DEFAULT_COUNT = 10;

  private $mysql_link;
  private $category_cache;
  
  private $xapian_read_db;
  private $xapian_write_db;
  private $xapian_stemmer;
  private $xapian_indexer;
  private $xapian_enquire;

  private function xapian_init_readonly() {
    try{
      $this-&gt;xapian_read_db = new XapianDatabase(self::SETTINGS_XAPIAN_DB);
      $this-&gt;xapian_stemmer = new XapianStem("english");
      $this-&gt;xapian_enquire = new XapianEnquire($this-&gt;xapian_read_db);
    } catch(Exception $e) {
      throw new Exception('Could initialize Xapian: ' . $e->getMessage());
    } 
  }
  
  private function xapian_init_writable() {
    try{
      $this-&gt;xapian_write_db = new XapianWritableDatabase(self::SETTINGS_XAPIAN_DB, Xapian::DB_CREATE_OR_OPEN);  
      $this-&gt;xapian_indexer = new XapianTermGenerator();
      $this-&gt;xapian_stemmer = new XapianStem("english");
      $this-&gt;xapian_indexer-&gt;set_stemmer($this-&gt;xapian_stemmer);
    } catch(Exception $e) {
      throw new Exception('Could initialize Xapian: ' . $e->getMessage());
    } 
  }
  
  private function mysql_init() {
    $this-&gt;mysql_link = mysql_connect(self::SETTINGS_MYSQL_HOST, self::SETTINGS_MYSQL_USER, self::SETTINGS_MYSQL_PASS);
    if (!$this-&gt;mysql_link) {
      throw new Exception('Could not connect: ' . mysql_error());
    }

    $db_selected = mysql_select_db(self::SETTINGS_MYSQL_DB, $this-&gt;mysql_link);
    if (!$db_selected) {
      throw new Exception('Can\'t use db : ' . mysql_error());
    }
  }
  
  /**
   * Index method
   *
   */
  public function index($params) {
    $this-&gt;xapian_init_writable();
    $this-&gt;mysql_init();
    
    $start = microtime(true);

    $response = new stdClass();
    $response-&gt;indexed = array();

    $offset = (isset($params['offset'])) ? intval($params['offset']) : 0;
    $count = (isset($params['count'])) ? intval($params['count']) : self::DEFAULT_COUNT;
    $sql = 'SELECT * FROM '.self::SETTINGS_MYSQL_TABLE.' LIMIT ' . $offset . ', ' . $count . ';';

    $result = mysql_query($sql);

    if (!$result) {
      throw new Exception('Invalid query: ' . mysql_error());
    }
    
    $this-&gt;xapian_write_db-&gt;begin_transaction();

    while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
       $response-&gt;indexed[] = $this-&gt;index_row($row);      
    }

    $this-&gt;xapian_write_db-&gt;commit_transaction();
    mysql_free_result($result);
    mysql_close($this-&gt;mysql_link);

    return $response;
  }
    

  private function index_row($row) {
    $doc = new XapianDocument();

    $this-&gt;xapian_indexer-&gt;set_document($doc);
    $this-&gt;xapian_indexer-&gt;index_text($row['name'],50);
    $this-&gt;xapian_indexer-&gt;index_text($row['summary'], 1);

    $GUID = self::XAPIAN_PREFIX_UID . $row['unique_key'];
    $doc-&gt;add_term($GUID);

    $doc-&gt;add_value(self::XAPIAN_FIELD_URL, $row['url']);
    $doc-&gt;add_value(self::XAPIAN_FIELD_DATE, date('Ymd', strtotime($row['date'])));
    $doc-&gt;add_value(self::XAPIAN_FIELD_UID, $row['unique_key']);
    $doc-&gt;add_value(self::XAPIAN_FIELD_NAME, $row['name']);
    $doc-&gt;add_value(self::XAPIAN_FIELD_SUMMARY, $row['summary']);
    
    $this-&gt;xapian_write_db-&gt;replace_document(strval($GUID), $doc);

    $row_response = array();
    $row_response['name'] = $row['name'];
    $row_response['guid'] = $row['unique_key'];
    $row_response['url'] = $row['url'];
    return $row_response; 
  }
  
  /**
   * Delete method
   *
   */
  public function delete($params) {
    $this-&gt;xapian_init_writable();

    $this-&gt;xapian_write_db-&gt;begin_transaction();

    $response = array();

    foreach($params['items'] as $param_guid) {      
      $GUID = self::XAPIAN_PREFIX_UID . $param_guid;
      $this-&gt;xapian_write_db-&gt;delete_document(strval($GUID));
      $response[] = $param_guid;
    }
    
    $this-&gt;xapian_write_db-&gt;commit_transaction();
    return $response;
  }

  /**
   * Search method
   *
   */
  public function search($params) {
    $this-&gt;xapian_init_readonly();

    $start = microtime(true);

    // queries array to later construct full query
    $arr_queries = array();

    // from date
    if(!empty($params['date_from'])) {
      $arr_queries[] = new XapianQuery(XapianQuery::OP_VALUE_GE, 6, date('Ymd', strtotime($params['date_from'])));
    }

    // to date
    if(!empty($params['date_to'])) {
      $arr_queries[] = new XapianQuery(XapianQuery::OP_VALUE_LE, 6, date('Ymd', strtotime($params['date_to'])));
    }

    // unique key
    if(!empty($params['unique_key'])) {
      $arr_queries[] = new XapianQuery(self::XAPIAN_PREFIX_UID . $params['unique_key']);
    }

    // normal search query parsed
    if(!empty($params['search'])) {
      $qp = new XapianQueryParser();
      $qp-&gt;set_stemmer($this-&gt;xapian_stemmer);
      $qp-&gt;set_database($this-&gt;xapian_read_db);
      $qp-&gt;set_stemming_strategy(XapianQueryParser::STEM_SOME);
      $arr_queries[] = $qp-&gt;parse_query($params['search']);
    }

    // Find the results for the query.
        // construct final query
    $query = array_pop($arr_queries);

    foreach($arr_queries as $sq) {
      $query = new XapianQuery(XapianQuery::OP_AND, $query, $sq);
    }    
    $this-&gt;xapian_enquire-&gt;set_query($query);
  
    // set the count to the specified params
    $offset = (isset($params['offset'])) ? intval($params['offset']) : 0;
    $count = (isset($params['count'])) ? intval($params['count']) : self::DEFAULT_COUNT;
    $matches = $this-&gt;xapian_enquire-&gt;get_mset($offset, $count);

    $response = new stdClass();
    $response-&gt;result_count = $matches-&gt;get_matches_estimated();
    $results = array();

    $i = $matches-&gt;begin();
    while (!$i-&gt;equals($matches-&gt;end())) {
      $m = array();

      $n = $i-&gt;get_rank() + 1;
      $doc = $i-&gt;get_document();

      $m['position'] = $n;
      $m['url'] = $doc-&gt;get_value(self::XAPIAN_FIELD_URL);
      $m['name'] = $doc-&gt;get_value(self::XAPIAN_FIELD_NAME);
      $m['summary'] = $doc-&gt;get_value(self::XAPIAN_FIELD_SUMMARY);
      $m['date'] = $doc-&gt;get_value(self::XAPIAN_FIELD_DATE);
      $m['unique_key'] = $doc-&gt;get_value(self::XAPIAN_FIELD_UID);
      $m['percent'] = $i-&gt;get_percent();

      $results[count($results)] = $m;
      $i-&gt;next();
    }

    $response-&gt;results = $results;
    $end = microtime(true);
    
    // runtime info
    $response-&gt;execute = new stdClass();
    $response-&gt;execute-&gt;call = 'search';
    $response-&gt;execute-&gt;offset = $offset;
    $response-&gt;execute-&gt;count = $count;
    $response-&gt;execute-&gt;start = $start;
    $response-&gt;execute-&gt;end = $end;
    $response-&gt;execute-&gt;time = $end - $start;

    // debug stuff
    $response-&gt;execute-&gt;debug = $query-&gt;get_description();

    return $response;
  }
}
</code></pre>

<h3>index.php</h3>

<pre><code>
&lt;?php

require_once 'XapianWrapper.php';

$x = new XapianWrapper();
$res = $x-&gt;index(array());
print_r($res);               
</code></pre>

<h3>search.php</h3>

<pre><code>
&lt;?php

require_once 'XapianWrapper.php';

$x = new XapianWrapper();
$params = array('search' =&gt; 'foo');
$res = $x-&gt;search($params);
print_r($res);           
</code></pre>

<h3>delete.php</h3>

<pre><code>
&lt;?php

require_once 'XapianWrapper.php';
$x = new XapianWrapper();
$params = array(
	'items' =&gt; array('foo'),
);
$res = $x-&gt;delete($params);
print_r($res);
</code></pre>

<h3>Using the example</h3>

<p>Once you've unzipped the <a href="http://www.contentwithstyle.co.uk/resources/xapian-example.zip">expamples</a>, you should now be able to create the DB from the db.sql file, and run the php examples via command line.</p>

<pre><code>
bash$ php index.php 
stdClass Object
(
    [indexed] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [name] =&gt; foo
                    [guid] =&gt; foo
                    [url] =&gt; 
                )

            [1] =&gt; Array
                (
                    [name] =&gt; bar
                    [guid] =&gt; bar
                    [url] =&gt; 
                )

        )

)
bash$ php search.php 
stdClass Object
(
    [result_count] =&gt; 2
    [results] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [position] =&gt; 1
                    [url] =&gt; 
                    [name] =&gt; foo
                    [summary] =&gt; foo bar test
                    [date] =&gt; 20081105
                    [unique_key] =&gt; foo
                    [percent] =&gt; 100
                )

            [1] =&gt; Array
                (
                    [position] =&gt; 2
                    [url] =&gt; 
                    [name] =&gt; bar
                    [summary] =&gt; test foo bar
                    [date] =&gt; 20091105
                    [unique_key] =&gt; bar
                    [percent] =&gt; 50
                )

        )

    [execute] =&gt; stdClass Object
        (
            [call] =&gt; search
            [offset] =&gt; 0
            [count] =&gt; 10
            [start] =&gt; 1256674866.79
            [end] =&gt; 1256674866.79
            [time] =&gt; 0.000944852828979
            [debug] =&gt; Xapian::Query(Zfoo:(pos=1))
        )

)
bash$ php delete.php 
Array
(
    [0] =&gt; foo
)
bash$ php search.php 
stdClass Object
(
    [result_count] =&gt; 1
    [results] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [position] =&gt; 1
                    [url] =&gt; 
                    [name] =&gt; bar
                    [summary] =&gt; test foo bar
                    [date] =&gt; 20091105
                    [unique_key] =&gt; bar
                    [percent] =&gt; 100
                )

        )

    [execute] =&gt; stdClass Object
        (
            [call] =&gt; search
            [offset] =&gt; 0
            [count] =&gt; 10
            [start] =&gt; 1256674876.02
            [end] =&gt; 1256674876.02
            [time] =&gt; 0.000872850418091
            [debug] =&gt; Xapian::Query(Zfoo:(pos=1))
        )

)
</code></pre>

<p>Right, I leave it up to you to amend the examples to suit your individual needs. As always any feedback or improvements are welcome. Happy indexing and searching everyone.</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=6YBHsDNIPzs:IQwY6UqYs7g:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=6YBHsDNIPzs:IQwY6UqYs7g:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=6YBHsDNIPzs:IQwY6UqYs7g:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=6YBHsDNIPzs:IQwY6UqYs7g:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=6YBHsDNIPzs:IQwY6UqYs7g:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Tue, 03 Nov 2009 16:35:15 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Quick helper script for ZF view translations  ]]></title>
      <link>http://www.contentwithstyle.co.uk/content/quick-helper-script-for-zf-view-translations--</link>
      <guid>http://www.contentwithstyle.co.uk/content/quick-helper-script-for-zf-view-translations--</guid>
      <description><![CDATA[<p>Manual copy and paste jobs are a pain. Faced with the prospect of internationalizing 30+ big view files, I thought there had to be a better way. When I started to use the reg exp search facility in Textmate, the penny dropped. A helper script is what was needed.</p>]]></description>
      <content:encoded><![CDATA[<p>Manual copy and paste jobs are a pain. Faced with the prospect of internationalizing 30+ big view files, I thought there had to be a better way. When I started to use the reg exp search facility in Textmate, the penny dropped. A helper script is what was needed.</p>

<h2>PHP cli script 'internationalize.php'</h2>

<pre><code>
&lt;?php
// check for CLI
if (php_sapi_name() != "cli") {
  print "This script is written to run under the command line ('cli') version of\n";
  print "the PHP interpreter, but you're using the '".php_sapi_name()."' version\n";
  exit(1);
}

// check for valid syntax
if(empty($argv[1])) {
  die('syntax: php internationalize.php &lt;file&gt; &lt;optional token prefix&gt;');
}


function get_key($str, $prefix) {
  $key = str_replace(' ', '_', strtolower($str));
  $key = preg_replace('/[\\:\\.\\,\\!\\?]{0,}/', '', $key);
  $prefix = $GLOBALS['prefix'];
  return $prefix.$key;
}

function key_replace($matches) {
  if(trim($matches[2]) != "") {
    $key = get_key($matches[2]);
    return '&lt;'.$matches[1].'&gt;&lt;?php echo $this-&gt;translate-&gt;_("'.$key.'"); ?&gt;&lt;/'.$matches[3].'&gt;';
  } else {
    return $matches[0];
  }
}


// get a prefix for the tokens
$prefix = (!empty($argv[2])) ? $argv[2] . '_' : '';

// identify and load file
$file = realpath($argv[1]);
$contents = file_get_contents($file);

// create backup file
copy($file, $file.'.bak');

// reg exp to find all text in between tags
$pattern = '/&lt;([\\w \\=\\%\\"\\(\\)\\\'\\:]+)&gt;([\\w\\s\\:\\.\\,\\!\\?_-]+)&lt;\\/([\\w]+)&gt;/';

// replace with translation calls
$translated = preg_replace_callback($pattern, 'key_replace', $contents);
file_put_contents($file, $translated);


// start generate ini file
$rows = array();
$matches = array();
preg_match_all($pattern, $contents, $matches);

for($i=0; $i&lt;count($matches[1]); $i++) {
  if(trim($matches[2][$i]) != "") {
    $key = get_key($matches[2][$i]);
    $rows[] = $key.'="'.addslashes($matches[2][$i]).'"';
  }
}

$ini_string = implode("\n", $rows);
file_put_contents($file.'.translation.ini', $ini_string);
</code></pre>

<h2>Example view file 'test.phtml'</h2>

<pre><code>
&lt;p&gt;Fooo&lt;/p&gt;

&lt;blockquote style="test"&gt;Ich teste gerade&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Test list&lt;/li&gt;
  &lt;li&gt;Test list  2&lt;/li&gt;
&lt;/ul&gt;                                                                        
</code></pre>

<h2>Translate it!</h2>

<p>Now we're set. We just need to call:</p>

<pre><code>
php internationalize.php test.phtml
</code></pre>

<p>We end up with a this in test.phtml:</p>

<pre><code>
&lt;p&gt;&lt;?php echo $this-&gt;translate-&gt;_("fooo"); ?&gt;&lt;/p&gt;

&lt;blockquote style="test"&gt;&lt;?php echo $this-&gt;translate-&gt;_("ich_teste_gerade"); ?&gt;&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;?php echo $this-&gt;translate-&gt;_("test_list"); ?&gt;&lt;/li&gt;
  &lt;li&gt;&lt;?php echo $this-&gt;translate-&gt;_("test_list__2"); ?&gt;&lt;/li&gt;
&lt;/ul&gt;
</code></pre>

<p>and this in test.phtml.translation.ini:</p>

<pre><code>
fooo="Fooo"
ich_teste_gerade="Ich teste gerade"
test_list="Test list"
test_list__2="Test list  2"
</code></pre>

<h2>In progress</h2>

<p>This is not meant as THE solution. It's merely a quick script I put together in a couple of minutes. There are a couple of things that it can't do, like translating attributes and all that.
</p>

<p>I'm no reg exp guru, and if you can improve please feel free to comment. Otherwise, have fun.</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=HkcKERjnKz0:0eCMpOlrnos:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=HkcKERjnKz0:0eCMpOlrnos:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=HkcKERjnKz0:0eCMpOlrnos:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=HkcKERjnKz0:0eCMpOlrnos:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=HkcKERjnKz0:0eCMpOlrnos:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Thu, 29 Oct 2009 10:34:01 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Quick lint check on changed php files with svn st and xargs]]></title>
      <link>http://www.contentwithstyle.co.uk/content/quick-lint-check-on-changed-php-files-with-svn-st-and-xargs</link>
      <guid>http://www.contentwithstyle.co.uk/content/quick-lint-check-on-changed-php-files-with-svn-st-and-xargs</guid>
      <description><![CDATA[<p>If you haven't done it in a hook already, here's the quick way to check all your changed files for syntax errors.</p>]]></description>
      <content:encoded><![CDATA[<p>If you haven't done it in a <a href="http://blueparabola.com/blog/subversion-commit-hooks-php">hook</a> already, here's the quick way to check all your changed files for syntax errors:</p>

<pre><code>
svn st | grep .php | grep M | cut -c 8- | xargs -n1 php -l
</code></pre>

<p>
I use xargs quite a lot lately, but some good clues came from David Singletons post "<a href="http://dsingleton.co.uk/blog/using-xargs-bash-example">Using xargs like you mean it</a>".
</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=KWt6nlA1n-E:wEP0OmCuL2U:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=KWt6nlA1n-E:wEP0OmCuL2U:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=KWt6nlA1n-E:wEP0OmCuL2U:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=KWt6nlA1n-E:wEP0OmCuL2U:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=KWt6nlA1n-E:wEP0OmCuL2U:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Tue, 20 Oct 2009 09:12:05 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Zend Framework DB and Mysql pre 5.17]]></title>
      <link>http://www.contentwithstyle.co.uk/content/zend-framework-db-and-mysql-pre-5.17</link>
      <guid>http://www.contentwithstyle.co.uk/content/zend-framework-db-and-mysql-pre-5.17</guid>
      <description><![CDATA[<p>I was getting weird errors when running multiple queries with Zend Framework, that I just couldn't replicate in my dev environment.</p>]]></description>
      <content:encoded><![CDATA[<p>I was getting weird errors when running multiple queries with Zend Framework, that I just couldn't replicate on my local environment.</p>

<pre><code>
Exception information:

Message: SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
Stack trace:

#0 /project/ZendFramework-1.7.1/Zend/Db/Statement.php(109): Zend_Db_Statement_Pdo-&gt;_prepare('UPDATE `foo...')
#1 /project/ZendFramework-1.7.1/Zend/Db/Adapter/Pdo/Abstract.php(170): Zend_Db_Statement-&gt;__construct(Object(Zend_Db_Adapter_Pdo_Mysql), 'UPDATE `foo...')
#2 /project/ZendFramework-1.7.1/Zend/Db/Adapter/Abstract.php(429): Zend_Db_Adapter_Pdo_Abstract-&gt;prepare('UPDATE `foo...')
#3 /project/ZendFramework-1.7.1/Zend/Db/Adapter/Pdo/Abstract.php(220): Zend_Db_Adapter_Abstract-&gt;query('UPDATE `foo...', Array)
#4 /project/ZendFramework-1.7.1/Zend/Db/Adapter/Abstract.php(551): Zend_Db_Adapter_Pdo_Abstract-&gt;query('UPDATE `foo...', Array)

...
</code></pre>

<p>Turns out the server runs MySQL pre 5.17:</p>

<pre><code>
$ yum list installed | grep mysql
mysql.i386                              5.0.45-7.el5                   installed
mysql-server.i386                       5.0.45-7.el5                   installed
php-mysql.i386                          5.1.6-23.2.el5_3               installed
</code></pre>

<p>In order to fix this we need to turn on query buffering:</p>

<pre><code>
$pdoParams = array(
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY =&gt; true
);

$params = array(
    'host'           =&gt; '127.0.0.1',
    'username'       =&gt; 'webuser',
    'password'       =&gt; 'xxxxxxxx',
    'dbname'         =&gt; 'test',
    'driver_options' =&gt; $pdoParams
);

$db = Zend_Db::factory('Pdo_Mysql', $params);
</code></pre>

<p>
Thanks to <a href="http://mysqltalk.wordpress.com/">Joe Devon from MySQL Talk</a> for <a href="http://mysqltalk.wordpress.com/2008/11/09/enabling-mysql-query-cache-with-zend-framework-through-pdomysql_attr_use_buffered_query/">pointing out the relevant part of the Zend Framework documentation</a> that points out why it happens and how to fix it.</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=0F-aaTwhtbc:vXedx_dsMDM:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=0F-aaTwhtbc:vXedx_dsMDM:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=0F-aaTwhtbc:vXedx_dsMDM:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=0F-aaTwhtbc:vXedx_dsMDM:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=0F-aaTwhtbc:vXedx_dsMDM:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Thu, 08 Oct 2009 09:38:34 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Looking after your health is important]]></title>
      <link>http://www.contentwithstyle.co.uk/content/looking-after-your-health-is-important</link>
      <guid>http://www.contentwithstyle.co.uk/content/looking-after-your-health-is-important</guid>
      <description><![CDATA[<p>About three months ago I suffered a contractors nightmare: An injury that meant I couldn't work for quite some time.</p>]]></description>
      <content:encoded><![CDATA[<p>About three months ago I suffered a contractors nightmare: An injury that meant I couldn't work for quite some time.</p>

<p>In my instance it was a very nasty episode of lower back pain, which made me go through a month of not being able to tie my shoes. Of course it could have been worse. I did have a break planned anyway (ironically it was to get fit and do the <a href="http://en.wikipedia.org/wiki/Dunwich_Dynamo">Dunwich Dynamo</a>) so I didn't have to bail out of a current contracting situation, and I was lucky that my osteopath didn't confirm my fears and sent me to hospital to have any surgery done. However, I didn't feel OK with taking on any contracting job for two and a half months, and I did spend quite some money on sessions to get my spine clicked into position. Also, following the routine I was recommended means I have to do back and core strength exercises every day.</p>

<p>This is why I want to give you contractors out there a couple of words on your way:</p>

<h2>Stay healthy</h2>

<p>I know it sounds lame, but you might want to consider doing the exercises before the pain comes. This might keep you fit and healthy to cope with 8 to 10 hours sitting each day, plus it might also help to balance out the stress that sometimes comes with contracting work, deadlines and delivery dates.</p>

<h2>Look at your working environment</h2>

<p>If you can help it, make sure you get a good chair, the computer set up and all the rest. I am sure you know the drill.</p>

<h2>Insurance</h2>

<p>In my case it didn't really matter that much, but what if I had kids, a mortgage and an ongoing contract? Clearly I should be insured against loss of income? There's a <a href="http://www.contractoruk.com/money/phi.html">pretty neat guide on Contractors UK about what to look for when you purchase insurance</a>. I recommend reading it and contemplating the necessary steps.</p>

<br />
<br />

<p>Finally, if you're suffering from back pain like I do, have a look at <a href="http://www.youtube.com/watch?v=LodlEH6TluM&amp;feature=PlayList&amp;p=FEC7C9FF242C8C6C&amp;playnext=1&amp;playnext_from=PL&amp;index=22">youtube</a>. The videos of yoga exercises have given me some good clues of what to incorporate into my exercise routine.</p>

<p>Also, If you have any tips to share, be it about injury prevention, workplace set up or experiences with insurance claims, please do!</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=k91afxvFDFo:pGHkX0OVmzM:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=k91afxvFDFo:pGHkX0OVmzM:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=k91afxvFDFo:pGHkX0OVmzM:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=k91afxvFDFo:pGHkX0OVmzM:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=k91afxvFDFo:pGHkX0OVmzM:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Sun, 30 Aug 2009 18:15:33 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Remove nodes in SimpleXMLElement]]></title>
      <link>http://www.contentwithstyle.co.uk/content/remove-node-in-simplexml</link>
      <guid>http://www.contentwithstyle.co.uk/content/remove-node-in-simplexml</guid>
      <description><![CDATA[<p>SimpleXML provides no way to remove XML nodes. DOM to the rescue!</p>]]></description>
      <content:encoded><![CDATA[<blockquote><p>SimpleXML provides no way to remove [...] XML nodes.</p></blockquote>

<p>You might think otherwise and <a href="http://www.kavoir.com/2008/12/how-to-delete-remove-nodes-in-simplexml.html">hack it with <code>unset()</code></a>, as it was done in one of the web applications I inherited at work, but today I found out that this works under some conditions, but not with every setup. I'd love to tell you what exactly the differences are that make it break, but I didn't spend the time tracking it down.</p>

<p>I can tell you what <code>unset()</code> did the the specific setup (PHP 5.1.2 on Windows Vista): Nothing. It happily executed it without error or warning, but also without changing anything.</p>

<p>Luckily S. Gehrig had a good and in my eyes not-hacky idea in <a href="http://stackoverflow.com/questions/262351/remove-a-child-with-a-specific-attribute-in-simplexml-for-php#262556">a thread on stackoverflow.com</a>: He suggests to use the DOM, specifically <code>dom_import_simplexml()</code>, to then simply use <code>removeChild()</code>.</p>

<p>In my case I had a predictable XML structure to parse, and only needed to kick out the first of a set of nodes, so my solution (using the same xml setup as example) is as follows:</p>

<pre><code>
$data='&lt;data&gt;
    &lt;seg id="A1"/&gt;
    &lt;seg id="A5"/&gt;
    &lt;seg id="A12"/&gt;
    &lt;seg id="A29"/&gt;
    &lt;seg id="A30"/&gt;
&lt;/data&gt;';
$doc=new SimpleXMLElement($data);
$dom=dom_import_simplexml($doc-&gt;seg[0]);
$dom-&gt;parentNode-&gt;removeChild($dom);
echo $doc-&gt;asXml();
/*outputs: 
 * &lt;?xml version="1.0"?&gt;
 * &lt;data&gt;&lt;seg id="A5"/&gt;&lt;seg id="A12"/&gt;&lt;seg id="A29"/&gt;&lt;seg id="A30"/&gt;&lt;/data&gt;
 */
</code></pre>
<p>For more complex XML structures (and probably closer to most real-life scenarios), I agree with S. in suggesting xpath as a way to detect your unwanted node.</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=sGOzC-qRj3U:g51zYHjMf-8:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=sGOzC-qRj3U:g51zYHjMf-8:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=sGOzC-qRj3U:g51zYHjMf-8:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=sGOzC-qRj3U:g51zYHjMf-8:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=sGOzC-qRj3U:g51zYHjMf-8:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Wed, 15 Jul 2009 17:56:07 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Custom Zend Form Image Upload Element]]></title>
      <link>http://www.contentwithstyle.co.uk/content/custom-zend-form-image-upload-element</link>
      <guid>http://www.contentwithstyle.co.uk/content/custom-zend-form-image-upload-element</guid>
      <description><![CDATA[<p>It really is time to post something, isn't it? Here is a quick way to have an image preview inside of a form based on Zend Form.</p>]]></description>
      <content:encoded><![CDATA[<p>It really is time to post something, isn't it? Here is a quick way to have an image preview inside of a form based on Zend Form.</p>

<h2>Assumptions</h2>

<p>I am using the auto include mechanism for this one, utilizing the PEAR naming convention, and all my files will sit underneath a Shared folder within the library folder.</p>

<h2>A custom form Element</h2>

<p>This is sitting under Shared/Form/Element/Image.php</p>

<pre><code>
&lt;?php
class Shared_Form_Element_Image extends Zend_Form_Element 
{
  public $helper = "imageUpload";
  public $options;
  
  public function __construct($image_name, $attributes, $data_item) {
    $this->options = $data_item;
    parent::__construct($image_name, $attributes);
  }
}
</code></pre>

<p>As you can see, the form Element itself doesn't contain much code, but it does overwrite the property 'helper'. Which bings us to the next file:</p>

<h2>A custom view helper</h2>

<p>In our example, this sits under Shared/Views/Helpers/ImageUpload.php</p>

<pre><code>
&lt;?php
class Shared_View_Helper_ImageUpload extends Zend_View_Helper_FormFile {

  public function imageUpload($name, $value, $attribs, $options) {
    $str = parent::formFile($name, $attribs = null);

    if(!empty($options[$name])) {
      $str .= $this->getImagePreview($name, $options[$name]);
    } else {
      $str .= $this->getEmptyPreview();
    }
    
    return $str;
  }

  private function getImagePreview($name, $path) {
    $img = ($this->view->doctype()->isXhtml())
       ? '&amp;lt;img src="/'.$path.'" alt="'.$name.'" />'
       : '&amp;lt;img src="/'.$path.'" alt="'.$name.'">';
    
    return '&amp;lt;p class="preview">'.$img.'&amp;lt;/p>';
  }
  
  private function getEmptyPreview() {
    return '&amp;lt;p class="preview">No image uploaded.&amp;lt;/p>';
  }
}
</code></pre>

<p>Pretty straight forward, I am just subclassing the FormFile helper that Zend brings us. However, you might want to create a different helper all together, if you need to use much more customized markup than I do.</p>

<h2>Invocation</h2>

<p>The form itself will no contain the image path as element, therefore we need to inject it into the form data inside the Controller, then invoke the Form with that data:</p>

<pre><code>
$data['image'] = $image_path;
$form = new My_From($data);
</code></pre>

<p>Inside our Form, all we need to do now is to add a new element using our custom ImageUpload element:</p>

<pre><code>
$preview = new Shared_Form_Element_Image('image', null, $options);
$preview->addValidator('Count', false, 1);
$preview->addValidator('Extension', false, 'jpg,png,gif');
$preview->setLabel('My Image');
$fields[] = $preview;
</code></pre>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=L0zL5bgngn4:DMwuqY3jyIE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=L0zL5bgngn4:DMwuqY3jyIE:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=L0zL5bgngn4:DMwuqY3jyIE:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=L0zL5bgngn4:DMwuqY3jyIE:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=L0zL5bgngn4:DMwuqY3jyIE:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Mon, 29 Jun 2009 22:59:44 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Getting phpunit to work with MAMP]]></title>
      <link>http://www.contentwithstyle.co.uk/content/getting-phpunit-to-work-with-mamp</link>
      <guid>http://www.contentwithstyle.co.uk/content/getting-phpunit-to-work-with-mamp</guid>
      <description><![CDATA[<p>In order to run my unit tests, I needed to get phpunit running with MAMP. Thanks to the <a href="http://mark-kirby.co.uk/2009/installing-phpunit-with-mamp/">helpful post of Mark Kirby</a> on this, I figured it out.</p>]]></description>
      <content:encoded><![CDATA[<p>In order to run my unit tests, I needed to get phpunit running with MAMP. Thanks to the <a href="http://mark-kirby.co.uk/2009/installing-phpunit-with-mamp/">helpful post of Mark Kirby</a> on this, I figured it out.</p>

<ol>
<li>cd into your PEAR installation directory</li>
<li>Make backup of phpunit file in bin folder</li>
<li>Edit the original file, replace #!/usr/bin/php with #!/Applications/MAMP/bin/php5/bin/php</li> 
<li>3. Also remove this:
<pre><code>
if (strpos('/usr/bin/php', '@php_bin') === 0) {
    set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
}
</code></pre></li>
<li>And finally, something that Mark didn't mention, add PEAR include path into /Applications/MAMP/conf/php5/php.ini</li>
</ol><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=Xq1eem1HMZk:whFB--4rytM:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=Xq1eem1HMZk:whFB--4rytM:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=Xq1eem1HMZk:whFB--4rytM:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=Xq1eem1HMZk:whFB--4rytM:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=Xq1eem1HMZk:whFB--4rytM:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Wed, 13 May 2009 11:41:46 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Deploying PHP applications with Vlad and SVN]]></title>
      <link>http://www.contentwithstyle.co.uk/content/deploying-php-applications-with-vlad</link>
      <guid>http://www.contentwithstyle.co.uk/content/deploying-php-applications-with-vlad</guid>
      <description><![CDATA[A detailed post explaining the first steps to get Vlad the Deployer working for your PHP application.]]></description>
      <content:encoded><![CDATA[<p>In my current day job I have to deploy a number of web apps every few weeks. 
The person I took over from did the whole thing in a manual and terribly cumbersome way:</p>
<ul>
	<li>exporting the project locally,</li>
	<li>zipping it up,</li>
	<li>moving it to the server,</li>
	<li>unpacking and symlinking it, to finally</li>
	<li>run a small script that would set the correct production config, chmod log and cache folders etc.</li>
</ul>
<p>In the following article, I'm going to describe how I went from there to deploying my Zend Framework-driven PHP apps with Vlad. To save yourself some time, feel free to <a href="#ruby-setup">skip the back story</a>, the <a href="#pre-configure" title="skip installing ruby, gem and rake">pre-requisite of installing ruby, gem and rake</a>, or <a href="#deployment-recipes">go straight to the deployment recipes</a>.</p>

<p id="backstory">I did the above method for a while, and realized quickly how much time is wasted by this process. Remembering my efforts of <a href="http://www.contentwithstyle.co.uk/content/php-deployment-with-capistrano">deploying PHP apps with Capistrano</a>, I felt
it was time to set up an automated deployment routine. While re-reading the article, thoughts returned of how unwieldy and badly documented Capistrano came across,
and I started looking for something more lean, that wouldn't scare of my non-ruby colleagues who might have to do my job when I'm away.</p>

<p>Very quickly I was lead to <a href="http://rubyhitsquad.com/Vlad_the_Deployer.html">Vlad the Deployer</a>, replacing one deployment app where the person in charge of the name had one tipsy field day with, well, another.</p>

<p>Taking it for its first spin, I felt that while there's a lot of confidence behind Vlad, the word "simple" crops up lots of times, but again, 
the documentation is a bit of a thinking man's manual; As soon as you go beyond the initial example it skips a few steps here and there, 
which can leave someone who is unfamiliar with the concept struggling. Maybe it's a case of "it's advanced stuff, just live with it". </p>

<p>Luckily I have my Capistrano experience, so I got started fairly quickly using Vlad for my own purposes. What I liked immediately about Vlad was that it declares as one of the project goals to "use the right tool for the job (ssh, rsync, etc)."</p>

<p id="vlad-goals">My initial goal was to use Vlad</p>
<ul>
	<li>to have a deployment one-liner,</li>
	<li>to roll back if needed, and</li>
	<li>to be able to share logs and other assets between releases</li>
</ul>

<p id="example-app">For the example below I created a demo Zend Framework application with <a href="http://devzone.zend.com/article/4559-Zend_Tool-and-ZF-1.8">Zend_Tool</a>, 
but what I'm describing should work for any PHP app out of the box, actually for any app, if you add any potential server tasks yourself.
This example is using Subversion, but there is other examples out there that'll tell you how to use it with Git and other SCM software.
</p>

<h2 id="ruby-setup">Setup</h2>

<p>To be able to deploy with Vlad you need to have the following 4 things set up:</p>

<h3>1. unix/linux development platform with SVN</h3>
<p>not much to add, Vlad works on Mac OS X and various linux flavours, but not under Windows. For this setup I'm assuming you're working with SVN.</p>

<h3>2. server with SVN client</h3>
<p>Your webserver should be able to work with SVN, and it should be able to reach your SVN repository.</p>

<h3>3. install vlad dependencies</h3>
<p>on OS X 10.5 you already have a rails installation coming with the developer tools; just do</p>
<pre><code>% gem update rails</code></pre>

<p>Alternatively and on earlier OS X, you can use MacPorts, to install Ruby with</p>
<pre><code>% port install ruby</code></pre>

<p>You might also be able to use fink, and on debian/ubuntu you can install via aptitude</p>
<pre><code>
% sudo apt-get install ruby
% sudo apt-get install rubygems</code></pre>

<p>For other platforms and compiling from source see here:<br/>
<a href="http://www.ruby-lang.org/en/downloads/">http://www.ruby-lang.org/en/downloads</a></p>

<p>Next you need to install Rake. For all systems, to update gem and install Rake, do the following. You don't need to <code>gem update</code> if you started from scratch:</p>
<pre><code>
% gem update --system
% gem update</code></pre>

<p>
<a href="http://www.virtualmin.com/index.php?option=com_flyspray&Itemid=82&do=details&task_id=4901&Itemid=82&project=1&pagenum=4">Gem may fail initially</a> - if so this is due to a bug in the gem command and requires you to edit the gem script (found with the command <code>which gem</code> on the command line, it's probably in <code>/usr/bin/gem</code>) by adding to the require statements (under <code>require 'rubygems'</code>):</p>
<pre><code>
require 'rubygems/gem_runner'
</code></pre>

<p>And finally, to install Rake itself:</p>
<pre><code>% gem install rake</code></pre>

<p>There are 3 more dependencies,</p> 
<ul>
<li>Hoe</li>
<li>Rubyforge</li>
<li>open4</li></ul>

<p>but they're installed automatically with the next step.</p>

<h3>4. install vlad as a gem</h3>
<p>To install vlad as a gem, all you need to do is</p>
<pre><code>% sudo gem install vlad</code></pre>

<p>while allowing for dependencies to be installed, which is set by default. Congratulations, you're ready to configure Vlad.</p>

<h2 id="pre-configure">Pre-Configure</h2>

<p>As Vlad is working closely with your system tools, you should set up your SSH for it first. You need to make sure the remote user is set up correctly, and you'll want some way of not having to enter your password a million times. Luckily <a href="http://www.contentwithstyle.co.uk/content/4-ssh-config-tips-for-faster-remote-working" title="4 ssh config tips for faster remote working">I wrote about this recently</a>. For the server I'd like to deploy to I added this. You might not need the ControlMaster/ControlPath, the important line is to set the remote user:</p>
<pre><code>
Host myserver.pretendco.com
    ControlMaster auto
    ControlPath ~/.ssh/master-%r@%h:%p
    User remoteuser
</code></pre>

<p>Now let's have a look at the server itself. There is a possibility that the web directory isn't writable by your remote user and you need to have root permissions. As you should use the root user for all this, and sudo croaks on remote commands, it's best to set your future releases directory to be owned by your remote user.</p>

<p>on the remote machine</p>
<pre><code>
% sudo chown remoteuser:remotegroup /path/to/releases/directory
% chmod 775 /path/to/releases/directory
</code></pre>

<h2>Configure and Deploy</h2>

<p>Next you need to create 2 files for Vlad: A Rakefile in your root directory, and a deploy.rb config file in application/configs. Your project should look a little something like this (yes, this is slightly abbreviated):</p>

<pre> |-application
 |---configs
 |-----config.ini
 |-----deploy.rb  &lt;-- just created
 |---controllers
 |---models
 |---views
 |-library
 |-public
 |-Rakefile     &lt;-- just created
</pre>

<p id="deployment-recipes">At its core, the Rakefile is very straight forward; I've lifted the initial loading step more or less from the Vlad documentation, with the exception of setting the path for deploy.rb manually. Should your application have a root folder <code>/config</code>, you're following default convention and won't need the instructions in the curly braces. Further down I'm resetting the <code>:update_symlinks</code> task to link to shared logs and media folders, as well as linking the Zend library into my library. By default Vlad would set 3 shared folders: /log, /system and /pids, which I am all omitting here.</p>
<pre><code>
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'

begin
  require 'vlad'
  Vlad.load {:config => 'application/configs/deploy.rb'} # set path for deploy.rb
rescue LoadError
  # do nothing
end

namespace :vlad do
  # Clear existing symlink task so that we can redefine.
  Rake.clear_tasks('vlad:update_symlinks')

  # we've only got log to share
  remote_task :update_symlinks, :roles => :app do
    run "rm -Rf #{latest_release}/log && ln -s #{shared_path}/log #{latest_release}/log && chmod -f 666 #{latest_release}/log/*"
    run "rm -Rf #{latest_release}/public/media && ln -s #{shared_path}/cache #{latest_release}/public/media"
    run "rm -Rf #{latest_release}/library/Zend && ln -s #{shared_path}/Zend #{latest_release}/library/Zend"
  end
end
</code></pre>

<p>Next up is deploy.rb, the config file for the to-be-deployed app. The only 3 mandatory lines in all cases are <code>:domain</code>, <code>:deploy_to</code>, and <code>:repository</code>.</p>
<pre><code>
set :application, "Example application"
set :domain, "pretendco.com"
set :deploy_to, "/project/example_releases"
set :repository, 'http://svn.pretendco.com/example/trunk'
set :svn_cmd, 'svn --username svnuser --password svnpwd'
</code></pre>
<p>The default SVN command is <code>svn</code>, but in my case the SVN user is different to local and remote user, and for some reason the SVN password prompt from within the deployment step kept failing on me, so I added it right here. Not the safest thing in the world, but I'm using a limited read-only SVN user for these purposes, so that's good enough for me. Any suggestions of improvement are welcome.</p>
<p>For your convenience I have zipped up <a href="http://www.contentwithstyle.co.uk/resources/vlad-config.zip">both files</a>, or if you like, the <a href="http://www.contentwithstyle.co.uk/resources/vlad-example.zip">whole demo project</a>.</p>
<p>The first time you run vlad, you do</p>
<pre><code>
% rake vlad:setup vlad:update
</code></pre>
<p>You can skip the additional <code>vlad:migrate vlad:start</code> mentioned in the <a href="http://hitsquad.rubyforge.org/vlad/doco/getting_started_txt.html">vlad documentation</a>, as we're neither migrating DBs (this time), nor does Apache need to be restarted.</p>
<p><code>vlad:setup</code> has now created a folder structure on your server that looks like this (abbreviated again):</p>
<pre> |-project
 |---example_releases
 |-----current           &lt;-- link to your web application
 |-----releases
 |-------yyyymmddhhiiss  &lt;-- your web application
 |---------application
 |---------library
 |-----------Zend        &lt;-- link to Zend Framework
 |---------log           &lt;-- link to shared logs
 |---------public
 |-----------media       &lt;-- link to shared media
 |-----revisions.log
 |-----scm               &lt;-- folder used to checkout release
 |-----shared
 |-------log
 |-------media
 |-------Zend
</pre>
<p><code>vlad:update</code> has exported your application from the HEAD of your SVN repository, pushed it into a release folder, and pointed <code>/current</code> to this release. This means once you've verified this initial deployment step you can set your Apache webroot to point to <code>/project/example_releases/current</code>. You can also see that the folders <code>/log</code>, <code>/public/media</code> and <code>/library/Zend</code> have been symlinked, so you'll want to move all your dynamically created files into the shared folder now.</p>
<p>As a side note, I've had two major criticisms about this, out of which I have fixed one in this example: The first one was that initially I didn't include Zend Framework in my release, but rather added it to the global include_path on the server. "Your applications should be self-contained!", Pascal was shouting from his desk, including some expletives. The other thing he flagged up was that the version of my application config checked into SVN was set up for the live environment, so that I didn't have to change a thing when deploying. I have fixed this in the meantime as well, with a set of other convenient changes, but you'll have to be patient for me to write my next post about it and stick with this solution for now.</p>
<p id="finish-line">The good news are that you're ready to go now!
To release a new version:</p>
<ul>
	<li>check in your changes into SVN</li>
	<li>make sure that the repository URL in deploy.rb points to the same as yours</li>
	<li>run <code>% rake vlad:update</code> to deploy your app</li>
	<li>put your feet up, and enjoy the rest of your day.</li>
</ul>
<p>If your release is broken, roll back with</p>
<pre><code>% rake vlad:rollback</code></pre>
<p>This will remove the last release and symlink to the previous version.</p>
<p>As a final word of warning: Using any means of deployment that rely on your code versioning software mean that you have to keep it clean. Only check in code you've tested locally, don't use the branch you deploy from as a backup mechanism. If you and any other people working with your code follow this advice, Vlad will save you lots of time, and turn the pain of updating a site into the joy of releasing something new.</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=f18WWraVr3c:Xas6Yt84qx0:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=f18WWraVr3c:Xas6Yt84qx0:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=f18WWraVr3c:Xas6Yt84qx0:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=f18WWraVr3c:Xas6Yt84qx0:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=f18WWraVr3c:Xas6Yt84qx0:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Fri, 08 May 2009 14:12:32 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[DOMDocument loadXML throws errors: A bug?]]></title>
      <link>http://www.contentwithstyle.co.uk/content/domdocument-loadxml-throws-errors-a-bug-not-a-feature</link>
      <guid>http://www.contentwithstyle.co.uk/content/domdocument-loadxml-throws-errors-a-bug-not-a-feature</guid>
      <description><![CDATA[<p>So I was wondering why loadXML gives me parsing errors, despite a big try and catch around it ... <a href="http://php.filearena.net/manual/kr/function.dom-domdocument-loadxml.php#69295">it's not a bug they say</a>.</p>]]></description>
      <content:encoded><![CDATA[<p>So I was wondering why loadXML gives me parsing errors, despite a big try and catch around it ... <a href="http://php.filearena.net/manual/kr/function.dom-domdocument-loadxml.php#69295">it's not a bug they say</a>.</p>

<p>
Gavin Sinai put up a code snippet in the PHP.net reference, which shows how to set the error handler to get rid of it: 
</p>

<pre><code>
&lt;?php
function HandleXmlError($errno, $errstr, $errfile, $errline)
{
   if ($errno==E_WARNING &amp;&amp; (substr_count($errstr,"DOMDocument::loadXML()")&gt;0))
   {
       throw new DOMException($errstr);
   }
   else
       return false;
}

function XmlLoader($strXml)
{
   set_error_handler('HandleXmlError');
   $dom = new DOMDocument();
   $dom-&gt;loadXml($strXml);   
   restore_error_handler();
   return $dom;
 }

?&gt;
</code></pre><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=JrKF5Mx9FqY:h_8qvnBkNNQ:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=JrKF5Mx9FqY:h_8qvnBkNNQ:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=JrKF5Mx9FqY:h_8qvnBkNNQ:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=JrKF5Mx9FqY:h_8qvnBkNNQ:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=JrKF5Mx9FqY:h_8qvnBkNNQ:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Wed, 22 Apr 2009 16:37:46 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[Unit testing web service based models in Zend Framework]]></title>
      <link>http://www.contentwithstyle.co.uk/content/unit-testing-web-service-based-models-in-zend-framework</link>
      <guid>http://www.contentwithstyle.co.uk/content/unit-testing-web-service-based-models-in-zend-framework</guid>
      <description><![CDATA[<p>Web applications nowadays use an increasingly distributed set of resources. How do we test our MVC applications that use web services in their models?</p>]]></description>
      <content:encoded><![CDATA[<p>Web applications nowadays use an increasingly distributed set of resources. How do we test our MVC applications that use web services in their models?</p>

<h2>What is the problem?</h2>

<p>Unit tests for database driven models are relatively straight forward. Usually we connect to a separate database, containing a known set of data. Since we know the data we can run assertions against it.</p>

<p>However, unit testing models that talk to web services presents us with a different set of problem. Usually our application runs requests against something that is not in our control. Unless the service provider gives us some kind of test mode, we're operating with dynamic live data which shouldn't be used for testing, since it's harder to test in the first place, but also could be harmful to be manipulated.</p>

<p>Let's have a look at two strategies to test out models.</p>

<h2>Mock objects for Services</h2>

<p>If we are using an object to represent the API calls to the service from within our model, i.e. <a href="http://framework.zend.com/manual/en/zend.service.delicious.html">Zend_Service_Delicious</a>, we can swap this object for a mock object. A simple accessor allows us to overwrite private properties of the object:</p> 

<pre><code>
class DeliciousModel {
  private $delicious;

  public function __construct() {
    $this-&gt;delicious = new Zend_Service_Delicious('username', 'password');
  }

  public function getPosts() {
    return $this-&gt;delicious-&gt;getAllPosts();
  }

  public function setProperty($name, $value) {
    if(UNITTEST_CONTEXT) {
      $this-&gt;$name = $value;
    }
  }
}
</code></pre>

<p>Now it's easy swap out private properties from within a unit test:</p>

<pre><code>
public function testDeliciousModel {
  $mockPosts = array('foo', 'bar');    

  $deliciousMock = $this-&gt;getMock('Zend_Service_Delicious', array('getAllPosts'));
  $deliciousMock-&gt;expects($this-&gt;once())
          -&gt;method('getAllPosts')
          -&gt;will($this-&gt;returnValue($mockPosts));
  
  $model = new DeliciousModel();
  $model-&gt;setProperty('delicious', $deliciousMock);

  $posts = $model-&gt;getPosts();
  $this-&gt;assertEquals($posts, $mockPosts);
}
</code></pre>

<p>
Of course this would mean that we're not launching any requests to our services at all, but instead check that models are doing the right method calls to the service API object, and get the right data back.
</p>

<h2>Validate service requests</h2>

<p>
Generally speaking, models that are using web services will transform method calls into HTTP requests against web service end points. Those might come in a variety of flavours, but all together we can say that, if we can intercept the request, we can make sure the model serializes method calls correctly into known request data.
</p>

<h3>Test adapter for Zend_HTTP calls</h3>

<p>If our model uses Zend_Http to launch the API calls, we might provide a hook in the model so that we can swap the HTTP adapter for the <a href="http://framework.zend.com/manual/en/zend.http.client.adapters.html#zend.http.client.adapters.test">test adapter</a> that ships with Zend Framework. We then have access to the entire request that the model launched, which we can use for assertions:</p>

<pre><code>
public function testJsonCall() {
  $adapter = new Zend_Http_Client_Adapter_Test();

  $response = "";
  $response .= "HTTP/1.1 200 OK" . "\r\n";
  $response .= "Content-type: application/json" . "\r\n";
  $response .= "\r\n";
  $response .= '{}';

  $adapter-&gt;setResponse($response);

  $model = new JsonModel();
  $model-&gt;setAdapter($this-&gt;adapter);

  $request_raw = $model-&gt;http_client-&gt;getLastRequest();
  // do your assertions here
}
</code></pre>

<h3>Swap the endpoint</h3>

<p>
Instead of changing the adapter to intercept the call, we could swap the endpoint of the web service against a mock service. This endpoint then can respond with known data which we can use for our assertions.
</p>

<p>Depending on what kind of web services we have to mock, we might use the servers provided by Zend, e.g. <a href="http://framework.zend.com/manual/en/zend.rest.server.html">Zend_Rest_Server</a> or <a href="http://framework.zend.com/manual/en/zend.soap.html#zend.soap.server">Zend_Soap_Server</a>.
</p>

<h2>A better understanding</h2>

<p>Looking at the two strategies above, it still might seem to be a hassle to do, what is essentially a replication of the web service functionality with test data, whether that happens as mock object, on the request/response level, or as test endpoint.</p>

<p>But instead, this exercise is a very <em>valuable documentation of ones understanding of the service functionality</em> at a particular point in time. Given that service APIs are often in a constant state of change, things might break in the future, but the tests we've written can help us understand whether it's down to our own code or the web service instead. If things fail at the web service level, we can now refer to our tests to compare what we expect the service to do with what it actually does.</p>

<h2>Related Links</h2>

<ul>
<li><a href="http://www.pointbeing.net/weblog/2009/04/unit-testing-code-which-consumes-soap-services.html">Unit Testing Code which Consumes SOAP Services</a></li>
<li><a href="http://www.soapui.org/gettingstarted/mocking.html">Mocking with SoapUI</a></li>
<li><a href="http://www.codeguru.com/columns/vb/article.php/c15209">Unit Testing with Stubs or Mocks</a></li>
</ul><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=twFDK-gPWpA:4k0ZpHiD7Qg:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=twFDK-gPWpA:4k0ZpHiD7Qg:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=twFDK-gPWpA:4k0ZpHiD7Qg:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=twFDK-gPWpA:4k0ZpHiD7Qg:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=twFDK-gPWpA:4k0ZpHiD7Qg:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Mon, 20 Apr 2009 08:37:30 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[4 ssh config tips for faster remote working]]></title>
      <link>http://www.contentwithstyle.co.uk/content/4-ssh-config-tips-for-faster-remote-working</link>
      <guid>http://www.contentwithstyle.co.uk/content/4-ssh-config-tips-for-faster-remote-working</guid>
      <description><![CDATA[<p>If your ssh experience was so far limited to typing your username and password lots of times, this post is for you.</p>]]></description>
      <content:encoded><![CDATA[<p>With the vast majority of web developers deploying their own code, and ssh playing a central role in the toolbox, I felt that tweaking this part of my work saves me some time, and if not, definitely some frustration over mistyping passwords and host names.</p>
<p>So, for myself as much as everyone out there, here is my collected useful knowledge that has helped me ease the pain of remote work.</p>
		<h3>1. Execute commands remotely as one-liners</h3>
		<p>This is what some deployment tools such as <a href="http://rubyhitsquad.com/Vlad_the_Deployer.html">vlad</a> use, but for a quick look at something, it cuts out valuable seconds you could look at web comics instead.</p>

		<p>Let's try a simple example:</p>
		<pre><code>
matthias:~ matthias$ ssh remoteuser@myserver.pretendco.com ls -l
remoteuser@myserver.pretendco.com's password: 
total 804
lrwxrwxrwx   1 remoteuser remoteuser      31 Nov 10  2007 access-logs -> /links/to/my/access-logs
drwxr-xr-x   2 remoteuser remoteuser    2048 Apr  6  2008 cgi-bin
-rw-r--r--   1 remoteuser remoteuser  808417 Jul 17  2007 error_log_dump
drwxr-x---   3 remoteuser mail     2048 Oct  1  2007 etc
drwxrwx---   7 remoteuser remoteuser    2048 Apr  4 02:33 mail
drwxr-x---   3 remoteuser remoteuser    2048 May 23  2008 public_ftp
drwxr-x---  30 remoteuser nobody   2048 Dec  4 06:33 public_html
drwxr-xr-x   7 remoteuser remoteuser    2048 Sep 24  2008 tmp
lrwxrwxrwx   1 remoteuser remoteuser      11 Apr  1  2006 www -> public_html
matthias:~ matthias$
		</code></pre>
		
		<p>You can even <a href="http://lookherefirst.wordpress.com/2007/11/29/executing-command-remotely-via-ssh/">pipe through to a remote command</a>. Here's an example taken from Look Here First, where the content of a local file is concatenated to a remote file.</p>
		<pre><code>
matthias:~ matthias$ cat localfile.txt | ssh remoteuser@myserver.pretendco.com "cat - >> remotefile.txt"
		</code></pre>

		<h3>2. Add your public key to your remote server</h3>
		<p>Next let's see how we can get around typing our password all the time. One way of doing this is to <a href="http://sial.org/howto/openssh/publickey-auth/" title="OpenSSH Public Key Authentication">add your public key to your remote server</a>.</p>
		<pre><code>
# first, upload public key from client to server
client$ scp ~/.ssh/id_rsa.pub remoteuser@myserver.pretendco.com:~

# next, setup the public key on server
server$ mkdir ~/.ssh
server$ chmod 700 ~/.ssh
server$ cat ~/id_rsa.pub >> ~/.ssh/authorized_keys
server$ chmod 600 ~/.ssh/authorized_keys
server$ rm ~/id_rsa.pub
		</code></pre>
		<p>Of course, we can combine with the previous example and run it like this:</p>
		<pre><code>
matthias:~ matthias$ cat ~/.ssh/id_dsa.pub | ssh remoteuser@myserver.pretendco.com "cat - >> ~/.ssh/authorized_keys2"
		</code></pre>
		<p>Windows users fear not, <a href="http://www.aota.net/Telnet/puttykeyauth.php4" title="Key Authentication with PuTTY">you can do this too</a>, with PuTTY and PuTTYgen.</p>

		<h3>3. Let the ControlMaster handle your sessions</h3>
		<p>Sadly this might not always possible, maybe your key management on your server is different, or you don't have permissions to the file.</p>
		<p>Luckily you can get around this by <a href="http://blog.zenlinux.com/?p=270">letting your first ssh connection act as ControlMaster</a>. By adding the following to ~/.ssh/config, you only need to enter your password once, and all parallel sessions won't ask for a password</p>
		<pre><code>
Host *
    ControlMaster auto
    ControlPath ~/.ssh/master-%r@%h:%p
		</code></pre>

		<h3>4. Set your user and hostname for your host</h3>
		<p>Finally, more often than not your username won't be the same on your local machine and on whatever server you'd like to connect to, or your server might not use the default ssh port. Or maybe you're just tired of typing that superlong domain name, and look for a less challenging typing task. ~/.ssh/config to the rescue.</p>
		<p>As final example:</p>
		<pre><code>
Host myserver
    User remoteuser
    Port 22022
    HostName myserver.pretendco.com
		</code></pre>
		<p>Most likely you won't need the Port instruction in there, because your server runs ssh on the default port 22, so just drop the line.</p>
<p>All this allows us something as compact as:</p>
<pre><code>
matthias:~ matthias$ ssh myserver mycommand
</code></pre>
<p>Have fun, and let's hope this got you into your weekend early!</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=YDUgFeNtqL8:nLRZ4j_s-YE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=YDUgFeNtqL8:nLRZ4j_s-YE:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=YDUgFeNtqL8:nLRZ4j_s-YE:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=YDUgFeNtqL8:nLRZ4j_s-YE:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=YDUgFeNtqL8:nLRZ4j_s-YE:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Fri, 17 Apr 2009 11:00:02 +0000</pubDate>
    </item>
    <item>
      <title><![CDATA[A caching pattern for models]]></title>
      <link>http://www.contentwithstyle.co.uk/content/a-caching-pattern-for-models</link>
      <guid>http://www.contentwithstyle.co.uk/content/a-caching-pattern-for-models</guid>
      <description><![CDATA[<p>This is a caching pattern for models using Zend_Cache and the __call magic method.</p>]]></description>
      <content:encoded><![CDATA[<p>This is a caching pattern for models using Zend_Cache and the __call magic method.</p>

<h2>The Basic Idea</h2>

<p>
The initial thought behind this is that a model should be able to return calls either uncached or cached, without initializing some cache object every time. It should be easy to switch between the two calls, and the cache should be coming with the model object already.
</p>

<h3>The old way</h3>

<p>
Before introducing the cache pattern, I would create an object that then would use Zend_Cache_Frontend_Class, then initialize a new instance of my model in there, then redefine a method that acts as somewhat like a proxy, but uses the cache.
And if that wasn't enough, I'd have to initialize them both, like in this example:
</p>

<pre><code>
$model = MyModel();
$model_cached = MyModelCached();

$values_direct = $model-&gt;doStuff();
$values_cached = $model_cached-&gt;doStuff();
</code></pre>

<h3>The new way</h3>

<p>
So Matthias asked me: Would it not be nicer if the cache was already available in the model, and you could do something like this:
</p>

<pre><code>
$model = MyModel();
$values_direct = $model-&gt;doStuff();
$values_cached = $model-&gt;cache-&gt;doStuff();
</code></pre>

<p>
A great idea. As you can see this proposed way saves a bit of code, it's intuitive and easy to change, if you ever need to switch between cached and non-cached calls.
</p>

<h2>The Code</h2>

<p>First we need to create an object that initializes the cache. We'll initialize a cache with Zend_Cache and then utilize the __call function to check whether a call exists in the cached object or not. This enables us to only do valid calls.</p>

<pre><code>
class BaseModelCache {
  private $object;
  private $cache;
  
  public function __construct($object) {
    $backendName = 'File';
    $frontendName = 'Class';

    $frontendOptions = array(
      'lifetime' =&gt; 1800,
    );

    $backendOptions = array(
      'cache_dir' =&gt; '/my/cache/dir/',
    );

    $this-&gt;object = $object;
    $frontendOptions['cached_entity'] = $object;

    try {
      Zend_Loader::loadClass('Zend_Cache');
      $this-&gt;cache = Zend_Cache::factory($frontendName, $backendName, $frontendOptions, $backendOptions);     
    } catch(Exception $e) {
      throw($e);
    }
  }
  
  public function __call($method, $args) {
    $class = get_class($this-&gt;object);
    $class_methods = get_class_methods($class);

    if(in_array($method , $class_methods)) {
        $caller = Array($this-&gt;cache, $method);
        return call_user_func_array($caller, $args);
    }

    throw new Exception( " Method " . $method . " does not exist in this class " . get_class($class ) . "." );
  }
}
</code></pre>

<p>The second step is to create a base model class, that we'll use for all our models. It will initialize the cache object using the BaseModelCache and make it available as public property:</p>

<pre><code>
abstract class BaseModel {
  public $cache;

  public function __construct() {
    $this-&gt;cache = new BaseModelCache($this);
  }
}
</code></pre>

<p>Et Voilà! Now we can create models by extending the BaseModel. The cache will be available as described above.</p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=Ot3iXCrI8AU:Vs7jDRSc-yI:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=Ot3iXCrI8AU:Vs7jDRSc-yI:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=Ot3iXCrI8AU:Vs7jDRSc-yI:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/ContentWithStyle?a=Ot3iXCrI8AU:Vs7jDRSc-yI:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/ContentWithStyle?i=Ot3iXCrI8AU:Vs7jDRSc-yI:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
      <pubDate>Mon, 06 Apr 2009 10:50:07 +0000</pubDate>
    </item>
  </channel>
</rss>
