<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tommy Lacroix &#187; PHP</title>
	<atom:link href="http://www.tommylacroix.com/category/web-development/programming/php/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.tommylacroix.com</link>
	<description>Professional Blog</description>
	<lastBuildDate>Wed, 16 Feb 2011 16:35:50 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.5</generator>
		<item>
		<title>PHP based MP4/F4V meta data reader</title>
		<link>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/</link>
		<comments>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/#comments</comments>
		<pubDate>Thu, 11 Jun 2009 14:45:40 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Flash]]></category>
		<category><![CDATA[PHP]]></category>
<category>duration</category><category>f4v</category><category>flash</category><category>flv</category><category>height</category><category>meta</category><category>meta data</category><category>mp4</category><category>mp4info</category><category>php</category><category>tool</category><category>width</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=124</guid>
		<description><![CDATA[You might have read my post about my FLV meta data and cue point reader/writer. You might also know that the F4V format (which really is simple MP4+H264+AAC) succeeded to the FLV format, and is supported by Flash 9.0.115 and up. The MP4Info class is a simple extensible PHP class reading the MP4 container&#8217;s frames [...]]]></description>
			<content:encoded><![CDATA[<p><img style="float:left;margin:0 10px 10px 0;" title="F4V File Format" src="http://www.tommylacroix.com/wp-content/uploads/2009/06/f4v.gif" alt="F4V File Format" width="128" height="128" /><br />
You might have read my post about my <a title="PHP based FLV meta and cue points reader/writer" href="http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/" target="_self">FLV meta data and cue point reader/writer</a>. You might also know that the <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flash_Video#File_formats" target="_blank">F4V format</a> (which really is simple MP4+H264+AAC) succeeded to the FLV format, and is supported by Flash 9.0.115 and up.</p>
<p>The <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">MP4Info class</a> is a simple extensible PHP class reading the MP4 container&#8217;s frames (called boxes) to get various information, namely the video duration, the video/audio codecs, the width and the height, as well as the embedded <a title="Adobe's Extensible Meta-data Platform (XMP), at Wikipedia" href="http://en.wikipedia.org/wiki/Extensible_Metadata_Platform" target="_blank">XMP meta data</a>.</p>
<p>The F4V format is better than the FLV format in many aspects:</p>
<ul>
<li>Better compression ratio</li>
<li>Better quality at similar bit rates</li>
<li>Open source compressors available, through ffmpeg and libh264 (Flash 8 On2 VP6 codec is proprietary)</li>
<li>Decoding H.264 is a lot less CPU intensive on the host computer than decoding On2 VP6</li>
</ul>
<div>With <a title="Adobe Flash Player Version Penetration" href="http://www.adobe.com/products/player_census/flashplayer/version_penetration.html" target="_blank">Flash 9 being at a rate of penetration of 98.9% in mature markets</a><sup>1</sup> and will all the advantages, MP4 Flash video is becoming more and more common.</div>
<div><span id="more-124"></span></div>
<div>It really only has been tested with Sorenson Squeeze encoded F4V files for now, and therefore should be considered beta.</p>
<p>You&#8217;ll find the project <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">at Google Code, under php-mp4info</a>.</p>
<p><small><sup>1</sup> As per Adobe&#8217;s Adobe Flash Player Version Penetration statistics, on June 11, 2009<br />
<sup>2</sup> F4V logo at the top found at <a title="Flash Develop And Design" href="http://www.flashdevelop.net/postshow_975.html" target="_blank">Flash Develop And Design</a></small></div>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>PHP Design Pattern: Building a Tree</title>
		<link>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/</link>
		<comments>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/#comments</comments>
		<pubDate>Wed, 10 Sep 2008 17:12:27 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[pattern]]></category>
		<category><![CDATA[tree]]></category>
		<category><![CDATA[tree building]]></category>
<category>pattern</category><category>php</category><category>tree</category><category>tree building</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=89</guid>
		<description><![CDATA[Categories and sub categories. Directories and sub directories. Building a tree of nodes that have parents is a common problem these days. There are two solutions to this problem, but unfortunately, the inefficient one is often used by programmers, due to the lack of time, or to inexperience. Imagine, for the sake of clarity, a simple [...]]]></description>
			<content:encoded><![CDATA[<p><img style="float:right; border:1px solid black; margin-left: 5px; margin-bottom: 5px; padding:5px; background-color:white;" src="/wp-content/uploads/2008/09/imagebinarytree.jpg" alt="Tree, in computing" width="289" height="253" />Categories and sub categories. Directories and sub directories. Building a <a title="Tree, at Wikipedia" href="http://en.wikipedia.org/wiki/Tree_(data_structure)" target="_blank">tree </a>of nodes that have parents is a common problem these days.</p>
<p>There are two solutions to this problem, but unfortunately, the inefficient one is often used by programmers, due to the lack of time, or to inexperience.</p>
<p><span id="more-89"></span></p>
<p>Imagine, for the sake of clarity, a simple dataset that goes as follow:</p>
<pre class="code">Array
(
    [0] =&gt; Array
        (
            [name] =&gt; Node 0
            [parent] =&gt; null
        )

    [1] =&gt; Array
        (
            [name] =&gt; Node 1
            [parent] =&gt; 4
        )

    [2] =&gt; Array
        (
            [name] =&gt; Node 2
            [parent] =&gt; 8
        )
    ...</pre>
<p>The common approach is to use a recursive function that finds all the root nodes, then all their sub nodes, then all the sub nodes of their sub nodes, and so on.</p>
<pre class="code">function mapTree($dataset, $parent=null) {
	$tree = array();
	foreach ($dataset as $id=&gt;$node) {
		if ($node['parent'] !== $parent) continue;
		$node['children'] = mapTree($dataset, $id);
		$tree[$id] = $node;
	}

	return $tree;
}</pre>
<p>The problem with this method is that, in terms of calculation, the problem complexity is exponential: you need to search all the dataset for children nodes for each node. Thus, building a tree of 1,000 nodes is 100 times more complicated than building a tree of 100 nodes, because you will have to search 10 times more nodes, 10 times more often.</p>
<p>A more efficient way to build the tree is to use dereferencing. This allows to map the whole tree in a single pass, without recursion. The problem complexity then becomes linear rather than exponential, and 10 times more nodes equals to 10 times more time (although the time required by PHP to find a specific index of an array isn&#8217;t taken into account here).</p>
<pre class="code">function mapTree($dataset) {
	$tree = array();
	foreach ($dataset as $id=&gt;&amp;$node) {
		if ($node['parent'] === null) { // root node
			$tree[$id] = &amp;$node;
		} else { // sub node
			if (!isset($dataset[$node['parent']]['children'])) $dataset[$node['parent']]['childs'] = array();
			$dataset[$node['parent']]['children'][$id] = &amp;$node;
		}
	}

	return $tree;
}</pre>
<p>The full PHP code of examples can be found below:</p>
<p><a title="Tree building pattern example" href="http://www.tommylacroix.com/wp-content/uploads/2008/09/tree.zip"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></a> <a title="Tree building pattern example" href="http://www.tommylacroix.com/wp-content/uploads/2008/09/tree.zip">tree.zip</a> <em>(1.2k)</em></p>
<p><small>Tree image from <a title="Binary Tree Image" href="http://en.wikipedia.org/wiki/Image:Binary_tree.svg">Wikipedia.org</a></small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>HTTP caching basics</title>
		<link>http://www.tommylacroix.com/2008/07/17/http-caching-basics/</link>
		<comments>http://www.tommylacroix.com/2008/07/17/http-caching-basics/#comments</comments>
		<pubDate>Thu, 17 Jul 2008 18:56:56 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Usability]]></category>
		<category><![CDATA[browser]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[http caching]]></category>
		<category><![CDATA[http headers]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[proxy]]></category>
<category>browser</category><category>caching</category><category>http</category><category>http headers</category><category>performance</category><category>php</category><category>proxy</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=70</guid>
		<description><![CDATA[The HTTP protocol is quite simple. But many of us under-use it, programmatically speaking. There are many very simple performance mechanisms that are often forgotten. Many developers go for disabling HTTP caching completely, as they often don&#8217;t understand how to use it, and because it can cause weird bugs when used incorrectly. But so much [...]]]></description>
			<content:encoded><![CDATA[<p>The <a title="Hyper-Text Transfer Protocol, at Wikipedia" href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" target="_blank">HTTP protocol</a> is quite simple. But many of us under-use it, programmatically speaking. There are many very simple performance mechanisms that are often forgotten. Many developers go for disabling HTTP caching completely, as they often don&#8217;t understand how to use it, and because it can cause weird bugs when used incorrectly.</p>
<p>But so much things are cacheable: pages, images, CSS, JavaScript, even many REST web services! Yes, even in this social web era where content changes faster than you can write, there&#8217;s still plenty of slow changing information, such as home pages, or lists of countries, regions and cities.</p>
<p>Efficiently using caching translates into:</p>
<ul>
<li>Better response and loading time</li>
<li>Decreased load on the server</li>
<li>Better user experience</li>
</ul>
<p>This article aims to present a simple explanation of the HTTP protocol and proper use of HTTP caching.</p>
<p><span id="more-70"></span></p>
<h3>The HTTP Protocol basics</h3>
<p>The HTTP protocol is a communication scheme between two or three actors: the server, the browser, and the often forgotten proxy.</p>
<p>First, to see the implicated headers, let&#8217;s have a look to the typical request &#8230;</p>
<pre class="code">GET / HTTP/1.1
Host: www.tommylacroix.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
<strong>If-Modified-Since: Thu, 17 Jul 2008 16:11:24 GMT
If-None-Match: "a9432-fc1b28423-cb122da"</strong>
<strong>User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</strong></pre>
<p>&#8230; and response &#8230;</p>
<pre class="code">HTTP/1.1 200 OK
Date: Thu, 17 Jul 2008 16:11:24 GMT
<strong>Expires: Wed, 11 Jan 1984 05:00:00 GMT
Etag: "a9432-fc1b28423-cb122da"
Last-Modified: Thu, 17 Jul 2008 16:11:24 GMT
Cache-Control: private, must-revalidate, max-age=0
Vary: User-Agent
</strong>Content-Type: text/html; charset=UTF-8

&lt;html&gt;...&lt;/html&gt;</pre>
<h3>Basic Caching</h3>
<p>The response headers returned by the server give the browser and the proxy information to make caching decisions.</p>
<p>First, the <a title="The Cache-Control header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9" target="_blank"><code>Cache-Control</code> header</a> tells if the content is cacheable or not. If they&#8217;re not sent, it&#8217;s cacheable by default. Consider the following headers:</p>
<pre class="code">Cache-Control: public, max-age=86400</pre>
<p>It basically says that the page is cacheable in a <code>public</code> scope, and that the content shouldn&#8217;t be kept in the cache without revalidation for more than 1 day (86400 seconds). The scope can be:</p>
<ul>
<li><code>public</code>: Cacheable by browsers and proxies, even if authenticated</li>
<li><code>Private</code>: Cacheable by browsers only (and proxies, but only for requests from the same clients)</li>
<li><code>no-cache</code>: Cacheable, but revalidation is required every time.</li>
<li><code>no-store</code>: Not cacheable at all.</li>
</ul>
<p>In addition, there are a few other keywords that you can add to your <code>Cache-Control</code> header. Keep in mind that you must separate multiple keywords by commas.</p>
<ul>
<li><code>must-revalidate</code>: Some proxies can be configured to ignore the <code>Expires</code> and <code>maxage</code>. This keyword forces them to always act like the resource was expired.</li>
<li><code>proxy-revalidate</code>: Same as <code>must-revalidate</code>, but only for proxies.</li>
<li><code>s-maxage</code>: Same as <code>maxage</code>, but only for proxies.</li>
</ul>
<p>Finally, for HTTP/1.0 compatibility, you should send a <code>Pragma</code> header when using the <code>Cache-Control no-cache</code> directive.</p>
<pre class="code">Pragma: no-cache</pre>
<p><strong>Browsers and proxies cache resources based on their URL</strong>. You can take advantage of this in many ways at the design stage of your web site or web application. For example, you should put non private REST web service parameters (such as language, or country) in the URL:</p>
<pre class="code">http://www.somesite.com/webservice/regionslist/country/usa/language/en</pre>
<p>Browsers and proxies also store three important information about the page: the <code>Expires</code>, <code>Last-Modified</code> and <code>Etag</code> headers.</p>
<ul>
<li>The <a title="The Expires header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21" target="_blank"><code>Expires</code> header</a> tells the browser and proxy, along with the <code>max-age</code> component of the <code>Cache-Control</code> header, until when this version of the content should be valid.</li>
<li>The <a title="The Last-Modified header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29" target="_blank"><code>Last-Modified</code> header</a> tells the browser and proxy the date and time of the last modification to this page. It isn&#8217;t always provided.</li>
<li>The <a title="The Etag header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19" target="_blank"><code>Etag</code> header</a>, which stands for Entity Tag, gives the browser and proxy a unique identifier that describes the content it returned. If a page&#8217;s content changes, its <code>Etag</code> changes as well.</li>
</ul>
<p>When you requested this page, your browser (and the proxy in the middle if there was one) did check if the page is cacheable, and since it was, stored a copy of the page associated with the URL, and the three metrics above. For subsequent requests, the server might use this cached copy.</p>
<p>Might? Yes, because the cached page won&#8217;t be good until the end of times. At some point, the browser (or the proxy) will check with the server if the page it has in cache is still valid. This is called revalidation, and the <code>Expires</code> HTTP response header along with the <code>max-age</code> component of the <code>Cache-Control</code> header control it.<a href="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-browser.gif"><img class="aligncenter size-full wp-image-71" title="HTTP caching, with the browser\'s perspective" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-browser.gif" alt="" width="476" height="267" /></a></p>
<p>When the browser or proxy revalidates a page, it sends information about its version: the <code>Etag</code>, and the <code>Last-Modified</code> the server sent when he cached the page. These are sent as <a title="The If-Modified-Since header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25" target="_blank"><code>If-Modified-Since</code></a>, and <a title="The If-None-Match header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26" target="_blank"><code>If-None-Match</code></a>, respectively:</p>
<pre class="code">GET /some-cachable-page HTTP/1.1
Host: www.tommylacroix.com
<strong>If-Modified-Since: Thu, 17 Jul 2008 16:11:24 GMT
If-None-Match: "a9432-fc1b28423-cb122da"
</strong>User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</pre>
<p>The server then compares this information with the <code>Etag</code> and the <code>Last-Modified</code> of the up-to-date page. If the browser&#8217;s cached copy appears to be valid, the server replies with the <code>Expires</code> and <code>Etag</code> (if available), headers, and no content:</p>
<pre class="code">HTTP/1.1 304 Not modified
Date: Thu, 17 Jul 2008 16:11:24 GMT
Expires: Wed, 11 Jan 2009 05:00:00 GMT
Etag: "a9432-fc1b28423-cb122da"</pre>
<p>If the browser&#8217;s cached copy appears to be out-dated, the server replies with the whole page, as usual.</p>
<p><a href="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-server.gif"><img class="aligncenter size-full wp-image-72" title="HTTP caching, with the server\'s perspective" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-server.gif" alt="" width="417" height="308" /></a></p>
<h3>Specific Cases</h3>
<p>Now, here comes the tricky cases. For the sake of clarity, I&#8217;ll use the following plot for the scenarios below:</p>
<blockquote><p>« Bob, Alice and Gregg work in the same office. Their office is equiped with a caching web proxy. Bob and Gregg share the same computer with the same user (ok, not credible, so lets say it&#8217;s a under-financed non-profit organization), and Alice has her own (she&#8217;s the boss).  »</p></blockquote>
<h4>Scenario 1: Secure sessions</h4>
<blockquote><p>« Bob goes on his SuperSocial profile page, at http://www.supersocial.com/profile/. His browser and the office proxy will check if the page is cacheable, and it is. They both store a copy of the page associated with the URL.</p>
<p>But what happens when Gregg or Alice log in his/her SuperSocial&#8217;s profile page right after? The browser will give him/her Bob&#8217;s page! »</p></blockquote>
<p><strong>The <code>Cache-Control</code>, <code>Expires</code>, and the <code>Etag</code> headers. </strong>Setting the <code>Expires</code> header in the past and the <code>max-age</code> to zero will cause the browser to revalidate the content each time. If the cached content is valid, only a header with no content will be sent. This is slightly slower than full blown caching, but no as much as no caching at all.</p>
<p>We could also set the <code>Cache-Control</code> scope to private, as each copy of the page will obviously only be valid one user.</p>
<p>We should also use <code>Etag</code>, as the content returned for the same URL isn&#8217;t the same for Bob and for Gregg, so the <code>Etag</code> will change, and the browser will reload the page for Gregg.</p>
<h4>Scenario 2: Content optimization</h4>
<blockquote><p>« The three unproductive workers browse a news site that has a wicked design. So wicked that the guys behind it had to make browser specific optimizations. Therefore, when you&#8217;re with Internet Explorer, the page is IE optimized, when you&#8217;re with Safari, some in-line CSS styles are different, and when you&#8217;re with Firefox, you get the regular page because this beauty is standard-compliant. Standard-what? Lets not digress&#8230;</p>
<p>When Bob and Gregg browse the site with IE from the same computer, it&#8217;s fine. But when Alice accesses it a short while after with Safari, she gets an ugly page as it&#8217;s IE optimized. The reason is, the server sent the IE version to Bob, and the proxy cached it. When Alice requests it, the proxy sends the IE page, thinking that it&#8217;s all fine. »</p></blockquote>
<p><strong>The <code>Vary</code> header.</strong> When the content of a page changes based on miscellaneous headers contained in the request, the server must tell the browser and the proxy using the <a title="The Vary header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44" target="_blank"><code>Vary</code> header</a>. The <code>Vary</code> header basically says: If you cache this content, beware that if this header changes, the content might change as well, so use the URL with these fields to make sure you serve a valid version later.</p>
<p>Here&#8217;s an example. The request&#8230;</p>
<pre class="code">GET / HTTP/1.1
Host: www.tommylacroix.com
User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</pre>
<p>&#8230; and response &#8230;</p>
<pre class="code">HTTP/1.1 200 OK
Date: Thu, 17 Jul 2008 16:11:24 GMT
Expires: Wed, 11 Jan 2009 05:00:00 GMT
Last-Modified: Thu, 17 Jul 2008 16:11:24 GMT
Cache-Control: public, max-age=86400
Vary: User-Agent
Content-Type: text/html; charset=UTF-8</pre>
<h3>Code Snippet</h3>
<p>Here&#8217;s a code snippet I wrote quite some time ago. It&#8217;s not pretty, but it works so I think it&#8217;s a good basic practical example.</p>
<pre class="code">/**
* setCacheHTTPHeaders
*
* @author	Tommy Lacroix
* @param	string	$privacy		Scope: public, private or no-cache
* @param	int	$lastModified		Unix timestamp of last page modification (optional)
* @param	int	$maxage			Maximum caching time before revalidation (optional)
* @param	string	$etag			Entity tag, page-content specific (optional)
* @return	bool				TRUE if content need to be sent, FALSE if no content need to be sent
*/
function setCachePolicy($privacy = 'public', $lastModified = false, $maxage = false, $etag = false) {
	// Sanitize privacy
	switch ($privacy) {
		case 'privacy':
		case 'public':
		case 'no-cache':
			break;
		default:
			$privacy = "public";
			break;
	}

	// Calculate expiry and max-age
	if (is_string($maxage)) { // Expiry is a string, interpret
		unset($m);
		if (preg_match('/^([0-9]+)([smhdwy])$/', $maxage, $m)) {
			$maxage = $m[1];
			switch ($m[2]) {
				case 's':	break;
				case 'm':	$maxage *= 60; 		break;
				case 'h':	$maxage *= 3600; 	break;
				case 'd':	$maxage *= 86400; 	break;
				case 'w':	$maxage *= 604800; 	break;
				case 'y':	$maxage *= 31536000; 	break;
			}
		}
	}
	if ($privacy != 'no-cache') {
		header('Expires: '.gmdate("r", time()+$maxage));
	} else {
		header('Expires: '.gmdate("r", time()-31536000));
		$maxage = 0;
	}

	// Send ETag headers
	if ($etag !== false) {
		header('ETag: "'.$etag.'"');
	}

	// Determine wheter we need to send content or not
	$outputContent = true;

	// Check ETag
	if ((isset($_SERVER['HTTP_IF_NONE_MATCH'])) &amp;&amp; ($etag !== false)) {
		if ($_SERVER['HTTP_IF_NONE_MATCH'] == '"'.$etag.'"') {
			header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
			$outputContent = false;
		}
	}

	if ((isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) &amp;&amp; ($lastModified !== false)) {
		$ifModifiedSince = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
		if ($ifModifiedSince &gt;= $lastModified) {
			header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
			$outputContent = false;
		}
	}

	// Remove content if output has been disabled
	if (!$outputContent) {
		// Return false: You don't need to send content
		return false;
	} else {
		// Send other headers
		header('Cache-Control: '.$privacy.', must-revalidate, post-check=0, pre-check=0, max-age='.$maxage);
		if ($privacy == 'no-cache') header('Pragma: no-cache');
		if ($lastModified !== false) header('Last-Modified: '.gmdate('r',$lastModified));

		// Return true: you need to send content
		return true;
	}
}</pre>
<h3>Conclusion</h3>
<p>The caching HTTP headers are simple to implement, and provide a huge performance bonus. If done properly, this convert into a better user experience, as there&#8217;s less waiting, and more browsing.</p>
<p>Shall you like me to add other scenarios, feel free to drop me a line and I&#8217;ll see what I can do.</p>
<h3>Further Reading</h3>
<ul>
<li><a title="Caching in HTTP, at W3" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html" target="_blank">Caching in HTTP, at W3</a></li>
<li><a title="HTTP Caching in Mozilla, at Mozilla" href="http://www.mozilla.org/projects/netlib/http/http-caching-faq.html" target="_blank">HTTP Caching in Mozilla, at Mozilla</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/17/http-caching-basics/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Super-charged Amazon SQS Sample in PHP</title>
		<link>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/</link>
		<comments>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/#comments</comments>
		<pubDate>Fri, 11 Jul 2008 20:00:58 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[example]]></category>
		<category><![CDATA[sample]]></category>
		<category><![CDATA[simple queue service]]></category>
		<category><![CDATA[sqs]]></category>
<category>amazon</category><category>example</category><category>php</category><category>sample</category><category>simple queue service</category><category>sqs</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=69</guid>
		<description><![CDATA[If you played with the pretty cool Amazon Web Services, you probably started off fetching a sample off the Developer&#8217;s Connection pages. At least, I did. One cool sample for Simple Queue Service is the one by Justin@AWS. But (you saw it coming, I suppose), being anti-PEAR (for many reason, such as dependencies, weight, version [...]]]></description>
			<content:encoded><![CDATA[<p>If you played with the pretty cool <a title="Amazon's Web Services" href="http://www.amazon.com/gp/browse.html?node=3435361" target="_blank">Amazon Web Services</a>, you probably started off fetching a sample off the <a title="Amazon Web Services Developer's Connection" href="http://developer.amazonwebservices.com/connect/index.jspa">Developer&#8217;s Connection pages</a>. At least, I did.</p>
<p>One cool sample for <a title="Simple Queue Service, at Amazon's Web Services" href="http://www.amazon.com/gp/browse.html?node=3435361" target="_blank">Simple Queue Service</a> is the one by <a title="Amazon SQS Sample in PHP by Justin@AWS" href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1180" target="_blank">Justin@AWS</a>. But (you saw it coming, I suppose), being anti-<a title="PEAR - PHP Extension and Application Repository, at PHP.net" href="http://pear.php.net/" target="_blank">PEAR</a> (for many reason, such as dependencies, weight, version conflicts, non-catchiness of the name, fructose intolerance, etc.), I just couldn&#8217;t leave the sample like that. Yes, this is me again reinventing the wheel.</p>
<p>The original library depended on two PEAR components:</p>
<ul>
<li><a title="Crypt_HMAC PEAR package, at PHP.net" href="http://pear.php.net/package/Crypt_HMAC" target="_self"><code>Crypt_HMAC</code></a>, for authentication</li>
<li><a title="HTTP_Request PEAR package, at PHP.net" href="http://pear.php.net/package/HTTP_Request" target="_blank"><code>HTTP_Request</code></a>, for HTTP calls</li>
</ul>
<h3><span id="more-69"></span>The Crypt_HMAC replacement</h3>
<p>I replaced the <code>Crypt_HMAC</code> dependency by three potential routines: <a title="Hash PECL, at PHP.net" href="http://ca.php.net/manual/en/book.hash.php" target="_blank"><code>hash PECL</code></a>, <a title="mhash, at PHP.net" href="http://ca.php.net/manual/en/book.hash.php" target="_blank"><code>mhash</code></a>, and <a title="Lance's HMAC function, at PHP.net" href="http://ca.php.net/manual/en/function.mhash.php">Lance&#8217;s function</a> (which you will never hear in a Numb3r&#8217;s episode, as it has nothing to do with a mathematician, but rather with a smart dude who posted a fully PHP HMAC code snippet in the PHP comments of the <code>mhash()</code> function).</p>
<p>The code goes as follow:</p>
<pre class="code">/**
 * HMAC function using hash PECL (http://ca.php.net/manual/en/book.hash.php)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_hash($stringToSign) {
	return base64_encode(pack('H*',hash_hmac('sha1', $stringToSign, $this-&gt;secretKey)));
}	

/**
 * HMAC function using mhash (http://ca.php.net/manual/en/book.hash.php)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_mhash($stringToSign) {
	return base64_encode(mhash(MHASH_SHA1, $stringToSign, $this-&gt;secretKey));
}

/**
 * HMAC function using Lance's function (http://ca.php.net/manual/en/function.mhash.php, see comments)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_lance($stringToSign) {
    $b = 64;
	if (strlen($this-&gt;secretKey) &gt; $b) {
		$key = pack("H*",sha1($this-&gt;secretKey));
	} else {
		$key = $this-&gt;secretKey;
	}
	$key  = str_pad($key, $b, chr(0x00));
	$ipad = str_pad('', $b, chr(0x36));
	$opad = str_pad('', $b, chr(0x5c));
	$k_ipad = $key ^ $ipad ;
	$k_opad = $key ^ $opad;

	return base64_encode(pack('H*', sha1($k_opad  . pack("H*",sha1($k_ipad . $stringToSign)))));
}</pre>
<h3>HTTP_Request replacement</h3>
<p>I replaced the <code>HTTP_Request</code> dependency by two potential routines: cURL and URL wrappers.</p>
<p>The code goes as follow:</p>
<pre class="code">/**
 * Fetch with curl
 *
 * @author Tommy Lacroix
 * @param string 	$url
 * @param string 	$qs
 * @param bool		$post
 * @return array(output,httpCode)
 * @internal
 */
private function fetch_curl($url, $qs, $post) {
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($curl, CURLOPT_HEADER, false);
	curl_setopt($curl, CURLOPT_POST, $post);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
	curl_setopt($curl, CURLOPT_URL, $url . (!$post ? '?'.$qs : ''));
	if ($post) {
		curl_setopt($curl, CURLOPT_POSTFIELDS, $qs);
	}

	// Execute
	$output = array();
	$output[0] = curl_exec($curl);
	$output[1] = curl_getinfo($curl,CURLINFO_HTTP_CODE);

	// Close handle
	curl_close($curl);

	return $output;
}

/**
 * Fetch with urlwrappers
 *
 * @author Tommy Lacroix
 * @param string 	$url
 * @param string 	$qs
 * @param bool		$post
 * @return array(output,httpCode)
 * @internal
 */
private function fetch_urlwrappers($url, $qs, $post) {
	$output = array();

	if ($post) {
		$opts = array(
		  'http'=&gt;array(
		    'method'=&gt;"POST",
		    'header'=&gt;"Content-type: application/x-www-form-urlencoded\r\n" .
		              "Content-length: " . strlen($qs),
		    'content'=&gt;$qs
		  )
		);
		$context = stream_context_create($opts);
	} else {
		$opts = array(
		  'http'=&gt;array(
		    'method'=&gt;"GET"
		  )
		);
		$context = stream_context_create($opts);
		$url .= '?'.$qs;
	}

	$output[0] = '';
	$f = @fopen($url,'r',null,$context);
	if (!$f) {
		$output[0] = '';
		$output[1] = 404;
		return $output;
	}
	while (!feof($f)) {
		$output[0] .= fread($f,1024);
	}
	$meta_data = stream_get_meta_data($f);
	if (preg_match('/^HTTP\/1\.[01] ([0-9]{3})/',$meta_data['wrapper_data'][0],$m)) {
		$output[1] = $m[1];
	} else {
		$output[1] = false;
	}
	fclose($f);

	$output[2] = 'wrappers';

	return $output;
}</pre>
<h3>Detection and conclusion</h3>
<p>Finally, the constructor includes a small detection mechanism that selects an HMAC and a fetching function:</p>
<pre class="code">/**
 * Constructor
 *
 * @author Justin@AWS
 * @author Tommy Lacroix
 * @param string $accessKey
 * @param string $secretKey
 * @param string $endpoint	http://queue.amazonaws.com
 * @param string $queueName	optional
 * @return SQSClient
 */
public function SQSClient($accessKey, $secretKey, $endpoint = 'http://queue.amazonaws.com', $queueName = '')
{
	.
	.
	.

	// Select fetch function
	// Call it with $this-&gt;{$this-&gt;fetchFunction}(...)
	if (function_exists('curl_init')) $this-&gt;fetchFunction = 'fetch_curl';
		else $this-&gt;fetchFunction = 'fetch_urlwrappers';

	// Select hash function
	// Call it with $this-&gt;{$this-&gt;hmacFunction}(...)
	if (function_exists('hash_hmac')) {
		$this-&gt;hmacFunction = 'hmac_hash';
	} else if (function_exists('mhash')) {
		$this-&gt;hmacFunction = 'hmac_mhash';
	} else if (class_exists('Crypt_HMAC')) {
		$this-&gt;hmacFunction = 'hmac_pear';
	} else {
		$this-&gt;hmacFunction = 'hmac_lance';
	}

	.
	.
	.
}</pre>
<p>The complete modified sqsClient package can be downloaded below. The original Justin@AWS package is <a title="Amazon SQS Sample in PHP by Justin@AWS" href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1180" target="_blank">available here</a>.</p>
<p><a title="sqsClientPHP-2.1" href="http://www.tommylacroix.com/demos/sqsClientPHP-2.1/sqsClientPHP-2.1.zip"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></a> <a title="sqsClientPHP-2.1" href="http://www.tommylacroix.com/demos/sqsClientPHP-2.1/sqsClientPHP-2.1.zip" >sqsClientPHP-2.1.zip</a> <em>(11k)</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP based FLV meta and cue points reader/writer</title>
		<link>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/</link>
		<comments>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/#comments</comments>
		<pubDate>Fri, 04 Jul 2008 21:30:26 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Flash]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[cue point]]></category>
		<category><![CDATA[flv]]></category>
		<category><![CDATA[meta data]]></category>
<category>cue point</category><category>flash</category><category>flv</category><category>meta data</category><category>php</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=63</guid>
		<description><![CDATA[Update (Dec 20, 2008): The AMF0Parser library has been updated to better handle broken AMF0 packets. The FLVInfo2 library PHPDoc has been updated. Update (Aug 15, 2008): The AMF0Parser library has been updated to support the Date type in the metas, added among others by the &#8220;inlet media FLVTool2&#8243; tool. Update (Jun 11, 2009): The [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignnone size-medium wp-image-68" style="float:left; margin: 0 8px 0 8px;" title="Flash Video" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/64px-flashvideo.png" alt="" width="64" height="64" /><strong>Update (Dec 20, 2008): The AMF0Parser library has been updated to better handle broken AMF0 packets. The FLVInfo2 library PHPDoc has been updated.</strong></p>
<p><strong>Update (Aug 15, 2008): The AMF0Parser library has been updated to support the Date type in the metas, added among others by the &#8220;inlet media FLVTool2&#8243; tool.</strong></p>
<p><strong>Update (Jun 11, 2009): The project has been moved to </strong><a title="php-flvinfo at Google Code" href="http://code.google.com/p/php-flvinfo/" target="_blank"><strong>Google Code</strong></a><strong>. MP4Info, an F4V file (or more genericaly MP4 file) class is also available: check out <a title="PHP based MP4/F4V meta data reader" href="http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/" target="_self">blog post</a></strong><strong> and project at <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">Google Code</a></strong><strong>.</strong></p>
<p>If you&#8217;ve done Flash banners or micro-sites that embed video with cue points for synchronization before, you know that it&#8217;s a pain in the arse. There doesn&#8217;t seem to be any tool around to modify the damn points once the file is encoded, so you&#8217;ve got to re-encode the file over and over to have your things synced correctly.</p>
<p>If you&#8217;ve tried to reverse engineer the <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flv" target="_blank">FLV format</a> before, you know that it&#8217;s a pain in the arse as well. The AMF0 format is anything but intuitive, and the documentation has been lacking for a long long time &#8212; although there&#8217;s apparently an SDK now. Luckily, there was <a title="OSFlash" href="http://osflash.org/" target="_blank">OSFlash</a>, and <a title="SabreAMF PHP Library, at Google Code" href="http://sabreamf.googlecode.com/" target="_blank">SabreAMF</a> and <a title="AMFPHP library" href="http://osflash.org/projects/amfphp" target="_blank">AMFPHP</a> that could be used as a documentation source.</p>
<p>If you recognize yourself, this might be your lucky day. After many hours of trial and error, I&#8217;ve finally been able to reverse engineer it, and to build a library that extract essential FLV information, reads the Meta, and allows you to write them back &#8212; including the cue points.</p>
<p><span id="more-63"></span></p>
<p>First, you&#8217;ll need to FLVInfo2 and the AMF0Parser libraries, below. The FLVInfo2 library has three main functions:</p>
<ul>
<li><code>getInfo</code>, which returns information gotten from analyzing the FLV file and it&#8217;s meta. Frame rate, bit rate, audio/video codec, width, height, etc.
<pre class="code">(
    [signature] =&gt; 1
    [hasVideo] =&gt; 1
    [hasAudio] =&gt; 1
    [minimalFlashVersion] =&gt; 8
     =&gt; stdClass Object
        (
            [codec] =&gt; 4
            [width] =&gt; 320
            [height] =&gt; 240
            [keyframeRatio] =&gt; 0.0180537208278
            [keyframeEvery] =&gt; 55.3902439024
            [fps] =&gt; 15
            [bitrate] =&gt; 448
            [codecStr] =&gt; On2 VP6
        )

    [audio] =&gt; stdClass Object
        (
            [codec] =&gt; 2
            [frequency] =&gt; 22
            [depth] =&gt; 16
            [channels] =&gt; 2
            [bitrate] =&gt; 48
            [codecStr] =&gt; MP3
        )
)</pre>
</li>
<li><code>getMeta</code>, which returns the meta data, and the cue points
<pre class="code">Array
(
    [metas] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [0] =&gt; onMetaData
                    [1] =&gt; Array
                        (
                            [duration] =&gt; 151.46
                            [width] =&gt; 320
                            [height] =&gt; 240
                            [videodatarate] =&gt; 500
                            [canSeekToEnd] =&gt; 1
                            [videocodecid] =&gt; 4
                            [audiodatarate] =&gt; 48
                            [audiocodecid] =&gt; 2
                            [framerate] =&gt; 15
                            [creationdate] =&gt; Tue Jun 17 13:06:15 2008
                            [Encoded_By] =&gt; orangetango Video Encoder
                            [Encoded_With] =&gt; orangetango Video Encoder
                            [metadatacreator] =&gt; orangetango FLV meta data writer
                        )
                )
        )
    [cuepoints] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [0] =&gt; onCuePoint
                    [1] =&gt; Array
                        (
                            [name] =&gt; name1
                            [time] =&gt; 4.41
                            [type] =&gt; event
                        )
                )
            [1] =&gt; Array
                (
                    [0] =&gt; onCuePoint
                    [1] =&gt; Array
                        (
                            [name] =&gt; name1_end
                            [time] =&gt; 6.7
                            [type] =&gt; event
                        )
                )
        )
)</pre>
</li>
<li><code>rewriteMeta</code>, which inputs an FLV file, meta data and cue points, and outputs a new FLV file.</li>
</ul>
<p>As a practical example, you find the <code>rewriteTagsForFile</code> function in the <code>example.php</code> file, that rewrites the meta data and gets the cue points from a FLVCoreCuePoints XML file.</p>
<p><a title="FLVInfo2 class, reads and writes FLV meta data from a Flash Video File." href="http://www.tommylacroix.com/wp-content/uploads/2008/07/flvinfo2.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="AMF0 Parser, reads and writes AMF0 encoded data" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/flvinfo2.phps" target="_blank"><span style="text-decoration: line-through;">flvinfo2.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(20k , updated Dec 20, 2008)</span></em></p>
<p><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/amf0parser.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="AMF0 Parser, reads and writes AMF0 encoded data" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/amf0parser.phps" target="_blank"><span style="text-decoration: line-through;">AMF0Parser.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(16k, updated Dec 20, 2008)</span></em></p>
<p><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><span style="text-decoration: line-through;">example.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(12k)</span></em><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><br />
</a></p>
<p><small>Flash Video logo found on <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flash_Video">Wikipedia</a>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>CodeIgniter 1.6.x OpenID module</title>
		<link>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/</link>
		<comments>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/#comments</comments>
		<pubDate>Wed, 25 Jun 2008 12:42:22 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Social networks]]></category>
		<category><![CDATA[authentication]]></category>
		<category><![CDATA[ciopenid]]></category>
		<category><![CDATA[codeigniter]]></category>
		<category><![CDATA[openid]]></category>
<category>authentication</category><category>ciopenid</category><category>codeigniter</category><category>openid</category><category>php</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=61</guid>
		<description><![CDATA[I got no IPod. I got no IPhone. But I&#8217;ve got an OpenID. I&#8217;m like a low class geek. But whatever. I&#8217;ve read a bit lately about OpenID and wanted to give it a try on one of my personal projects (CodeIgniter based, you bet). I&#8217;ve stumbled upon Rémi Prévost &#8212; who&#8217;s blog a follow [...]]]></description>
			<content:encoded><![CDATA[<p>I got no IPod. I got no IPhone. But I&#8217;ve got an OpenID. I&#8217;m like a low class geek. But whatever. I&#8217;ve read a bit lately about OpenID and wanted to give it a try on one of my personal projects (CodeIgniter based, you bet).</p>
<p>I&#8217;ve stumbled upon <a title="Rémi Prévost CIOpenID module" href="http://remiprevost.com/2007/10/codeigniter-openid-1a" target="_blank">Rémi Prévost &#8212; who&#8217;s blog a follow more or less consistently due to the lack of time &#8212; CIOpenID module</a>. It&#8217;s basically a CI embed of <a title="Janrain's PHP_OpenID" href="http://openidenabled.com/php-openid/" target="_blank">Janrain&#8217;s PHP_OpenID</a>.</p>
<p>His code is great and works well. But one thing I don&#8217;t like about it is that it requires a different bootstrap file (a modified version of <code>index.php</code>) and a somewhat hacked version of your typical <code>.htaccess</code> file. The reason is, CodeIgniter annihilates the <code>$_GET</code> variable during initialization, because GET queries aren&#8217;t secure (ok, this is overly simplified, but you get the idea).</p>
<p>Being who I am, and somewhat liking to reinvent the wheel during my free time, I used a different approach. Rather than using a different bootstrap file, I rather chose to use the <code>pre_system</code> hook.</p>
<p><span id="more-61"></span></p>
<p>The installation process is quite simple:</p>
<ol>
<li>Put my <code>ciopenid.php</code> library in your <code>/system/application/libraries</code> directory.</li>
<li>Create <code>/system/application/libraries/openid</code>, and unzip PHP_OpenID 2.x.x&#8217;s <code>Auth</code> directory in it.</li>
<li>Open your <code>/system/application/config/config.php</code> file, and make sure that hooks are enabled:
<pre class="code">$config['enable_hooks'] = TRUE;</pre>
</li>
<li>Open your <code>/system/application/config/hooks.php</code> file, and add the following lines:
<pre class="code">$hook['pre_system'][] = array(
   'class'    =&gt; 'ciopenid',
   'function' =&gt; 'pre_system_hook',
   'filename' =&gt; 'ciopenid.php',
   'filepath' =&gt; 'libraries',
   'params'   =&gt; null
   );</pre>
</li>
</ol>
<p>To make an authentication query to an OpenID URL in <code>$openid</code>:</p>
<pre class="code">$this-&gt;load-&gt;library('ciopenid');
try {
   $redirect_url = $this-&gt;ciopenid-&gt;authenticate($openid, site_url($this-&gt;uri-&gt;uri_string()));
   header("Location: ".$redirect_url);
} catch (Exception $e) {
   die('Error: '.$e-&gt;getMessage());
}</pre>
<p>And to get the OpenID query response:</p>
<pre class="code">$this-&gt;load-&gt;library('ciopenid');
if (isset($this-&gt;ciopenid-&gt;response)) {
   switch ($this-&gt;ciopenid-&gt;response-&gt;status) {
      case Auth_OpenID_SUCCESS:
         $openid = $this-&gt;ciopenid-&gt;response-&gt;endpoint-&gt;claimed_id;
         print 'Success: '.$openid;
         break;
      case Auth_OpenID_CANCEL:
         print 'User cancelled';
         break;
      default:
         print 'Internal error';
         break;
   }
}</pre>
<p>That&#8217;s it! Ooops, here&#8217;s the file&#8230;</p>
<p><a href="http://www.tommylacroix.com/wp-content/uploads/2008/06/ciopenid.zip"><img style="border: 0pt none;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source (zipped)" width="16" height="16" /></a> <a title="CIOpenID library, 1.0" href="http://www.tommylacroix.com/wp-content/uploads/2008/06/ciopenid.zip">ciopenid.zip (3k)<br />
</a></p>
<p>Enjoy!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Rapid development PHP framework CodeIgniter</title>
		<link>http://www.tommylacroix.com/2008/02/23/codeigniter-overview/</link>
		<comments>http://www.tommylacroix.com/2008/02/23/codeigniter-overview/#comments</comments>
		<pubDate>Sat, 23 Feb 2008 14:12:33 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[codeigniter]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[zend framework]]></category>
<category>codeigniter</category><category>framework</category><category>php</category><category>programming</category><category>tool</category><category>zend framework</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/2008/02/23/codeigniter-overview/</guid>
		<description><![CDATA[What better, on a saturday morning, than to write (or read will you say) about a PHP framework with a nice cup of java &#8211; the warm caffeinated beverage that is. Saturday morning thought: the Java developers I know are like Jehova witnesses: they divert any programming conversation to &#8220;Java is the future, wake up [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.codeigniter.com/" target="_blank" style="margin: 0pt 5px 5px 0pt; float: left"><img src="http://www.tommylacroix.com/wp-content/uploads/2008/02/codeigniter.jpg" alt="CodeIgniter logo" border="0" /></a>What better, on a saturday morning, than to write (or read will you say) about a PHP framework with a nice cup of java &#8211; the warm caffeinated beverage that is.</p>
<p>Saturday morning thought: the Java developers I know are like Jehova witnesses: they divert any programming conversation to &#8220;Java is the future, wake up from your slumber&#8221;. Java&#8217;s been the future for 10 years, yet it failed to make it into the present. But lets take another sip of coffee and digress no more&#8230;</p>
<p>While I personally (this is a personal blog after all) like <a href="http://framework.zend.com/" title="Zend Framework" target="_blank">Zend Framework</a> for its lightness, speed and cleanliness, I rarely consider it for rapid development: I dislike the MVC, and tend to use Zend more like I use PEAR libraries. The documentation could be better too.</p>
<p>My love goes to <a href="http://www.codeigniter.com" title="CodeIgniter, PHP framework" target="_blank">CodeIgniter</a>, a PHP framework developed by the guys at <a href="http://ellislab.com/" title="Ellislab" target="_blank">Ellislab</a>. Setting up a CodeIgniter environment is easy as 1-2-3: Unzip (or <em>untar </em>if you&#8217;re a real geek), configure database, and take a sip of coffee (did I tell you I liked coffee?).</p>
<p>To the contrary of Zend, <a href="http://codeigniter.com/user_guide/" title="CodeIgniter User Guide" target="_blank">the documentation is a wonder</a>: tutorial oriented while not cluttered by stating the obvious. The only thing that I could use sometimes is an API-style documentation, which is inexistent and leaves me off looking at their code.</p>
<p>The essential libraries are there, ready to be loaded and used: configuration, database, benchmarking, crypto, input, input validation, output, language, unit testing, and much more. Plus there&#8217;s a few <a href="http://codeigniter.com/wiki/Special:Titles" title="CodeIgniter Contribs" target="_blank">very good contribs</a> out there on their wiki and elsewhere on the web.</p>
<p>Enough propaganda, let&#8217;s be honest, CodeIgniter is not perfect:</p>
<ul>
<li><strong>Configuration and language files are PHP declared arrays</strong>, which makes them kind of hard to maintain sometimes. Plus I&#8217;m worried (although I did no benchmark yet) about the cost of multiple file inclusion.</li>
<li><strong>The model object</strong> provides a lot of flexibility (actually, you do it the way you want), but flexibility is a double bladed knife. A real, functional yet flexible out of the box active record class would make rapid development rapider.</li>
<li><strong>The cookie-based session class sucks</strong>: All your data go in cookies + Cookies are size limited = Problems. But there&#8217;s proven and true replacements in the contribs.</li>
<li><strong>There is no &#8220;real world&#8221; caching class out of the box</strong>. Sure, there&#8217;s a caching class that caches, URL based, your output, and it works well if a URL always look the same no matter who visits it. But in that Web 2.0 world full of logins and &#8220;Hello Tommy&#8221;, it&#8217;s not the case. Yet, you can extend the output class by adding caching hooks.</li>
<li><strong>The framework is still fully PHP4 compatible</strong>, which tend to make its Object Oriented code ugly. Let&#8217;s hope they&#8217;ll drop that sooner than later.</li>
</ul>
<p>Yet, CodeIgniter stays my first choice. There&#8217;s more pros than cons, and we built a lot of libraries over time that ease the downsides.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/02/23/codeigniter-overview/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

