<?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/" 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/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>Jonathon Hill</title>
	
	<link>http://jonathonhill.net</link>
	<description>Notes, projects, and opinions on web application development and other related topics.</description>
	<lastBuildDate>Wed, 12 Oct 2011 23:56:09 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/JonathonHill" /><feedburner:info uri="jonathonhill" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>IE innerHTML CSS bug</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/mRhbDuhRLDw/</link>
		<comments>http://jonathonhill.net/2011-10-12/ie-innerhtml-style-bug/#comments</comments>
		<pubDate>Wed, 12 Oct 2011 23:51:22 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[bug]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[IE]]></category>
		<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=314</guid>
		<description><![CDATA[Today I encountered a bug that affects every version of Internet Explorer from IE6 all the way through IE9. Here&#8217;s what happens. The Bug Set a Javascript string variable containing CSS styles (&#60;style&#62; or &#60;link&#62;) followed by some HTML content, such as a &#60;p&#62; tag (in my application this happened via JSONP). Add a &#60;div&#62; [...]]]></description>
			<content:encoded><![CDATA[<p>Today I encountered a bug that affects every version of Internet Explorer from IE6 all the way through IE9. Here&#8217;s what happens.</p>
<h2>The Bug</h2>
<ol>
<li>Set a Javascript string variable containing CSS styles (<code>&lt;style&gt;</code> or <code>&lt;link&gt;</code>) followed by some HTML content, such as a <code>&lt;p&gt;</code> tag (in my application this happened via JSONP).</li>
<li>Add a <code>&lt;div&gt;</code> node to the DOM</li>
<li>Insert the content from #1 into the <code>.innerHTML</code> property of the <code>&lt;div&gt;</code></li>
<li>The content gets inserted, devoid of CSS styles.</li>
</ol>
<pre><code>&lt;!-- these styles are ignored when inserted via innerHTML --&gt;
&lt;style&gt;
  p { color: blue; }
&lt;/style&gt;
&lt;p&gt;Hello, how are you!&lt;/p&gt;</code></pre>
<h2>The Solution</h2>
<p>The <a title="IE not including CSS" href="http://www.houseoffusion.com/groups/ajax/thread.cfm/threadid:1304#4254" target="_blank">simple solution</a> was to put the other markup <em>before</em> the <code>&lt;style&gt;</code> or <code>&lt;link&gt;</code> tag. Bizarre.</p>
<pre><code>&lt;p&gt;Hello, how are you!&lt;/p&gt;
&lt;!-- these styles are applied when inserted via innerHTML --&gt;
&lt;style&gt;
 p { color: blue; }
&lt;/style&gt;</code></pre>
<p>Because IE needs something that affects the layout before it will apply CSS styles in an innerHTML property, these will not work as style triggers:</p>
<ul>
<li><code>&lt;!-- a comment --&gt;</code></li>
<li><code>&lt;p&gt;&lt;/p&gt;</code> (or any other empty block-level HTML tag)</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-10-12/ie-innerhtml-style-bug/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-10-12/ie-innerhtml-style-bug/</feedburner:origLink></item>
		<item>
		<title>Writing MySQL schema migrations: best practices</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/qlCluHUiHvY/</link>
		<comments>http://jonathonhill.net/2011-10-08/mysql-schema-migrations-best-practices/#comments</comments>
		<pubDate>Sun, 09 Oct 2011 05:50:25 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[best practices]]></category>
		<category><![CDATA[migration]]></category>
		<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=311</guid>
		<description><![CDATA[Building MySQL web applications with a team of developers will inevitably present the challenge of database schema changes. Any good web developer understands the importance of keeping all code under version control, but how many follow the same principle for the database? What follows are some best practices that I have learned over the past [...]]]></description>
			<content:encoded><![CDATA[<p>Building MySQL web applications with a team of developers will inevitably present the challenge of database schema changes. Any good web developer understands the importance of keeping all code under version control, but how many follow the same principle for the database?</p>
<p>What follows are some best practices that I have learned over the past several years from the school of hard knocks.<span id="more-311"></span></p>
<h2>Use revision control</h2>
<ul>
<li><strong>Version control all schema documentation</strong> and setup files, right alongside your code. This could be in the form of a <a href="http://www.mysql.com/products/workbench/" target="_blank">MySQL Workbench</a> file (.mwb), SQL, plaintext, PHP, or HTML files.</li>
<li><strong>Version control your database schema.</strong> I personally prefer to use individual SQL files for this purpose, one file per revision (each revision file could contain multiple SQL statements). Include INSERTs for tables that need to be pre-populated for your application to work properly, such as role or permission options, country or state data, and categories.</li>
<li><strong>Correlate code revisions and database schema revisions.</strong> For each codebase revision, there should be a way to re-create the corresponding database schema. An effective way to do this is to name individual SQL revision files after the codebase revision in which they were created (e.g. <code>r389.sql</code>).</li>
<li><strong>Store the database version in the database.</strong> You should be able to tell what database schema version you are running, and the codebase version that goes with it.</li>
</ul>
<h2>Rules of engagement</h2>
<ul>
<li><strong>Write readable SQL.</strong> Proper indentation, newlines, and comments are as important in your SQL as they are in other types of code. See my <a href="http://jonathonhill.net/coding-for-readability/mysql-style-guide/">MySQL Style Guide</a> for a suggested way to do it.</li>
<li><strong>Terminate each SQL statement with the proper delimiter.</strong> Usually, this will be a semicolon. This is recommended even if there is only a single SQL statement in the migration to prevent query syntax errors when running multiple migrations.</li>
<li><strong>Migrate forwards <em>and</em> backwards.</strong> When writing a database migration, include a way to roll back to the previous revision <em>after</em> the migration is already applied. Your version control system provides this for your code, but you&#8217;ll be limited if you can&#8217;t roll back your database too. You can do this by creating pairs of migration files for each revision, one to make a change, and one to undo it.</li>
<li><strong><span style="text-decoration: underline;">Always</span> test your migrations</strong>, no excuses. Don&#8217;t change your database schema with a GUI tool and <em>then</em> write a migration script as an afterthought. Write your migration, and run it on your own database before committing it to the codebase.</li>
<li><strong>Never change a migration file,</strong> unless you are certain that no one else has run the migration yet. If you test, you shouldn&#8217;t ever need to anyway.</li>
</ul>
<h2>Write bulletproof migrations</h2>
<ul>
<li><strong>Consider index speed.</strong> For large tables, re-indexing can take a while. Consider dropping an index and re-adding it after altering indexed columns or running an <code>UPDATE</code> query.</li>
<li><strong>Omit column order clauses</strong> such as <code>ADD COLUMN `column_b` VARCHAR(50) <span style="text-decoration: underline;">AFTER `column_a`</span></code>. Unless a very specific column order is required, this is unnecessary and can introduce errors if a database happens to lack <code>`column_a`</code> during migration.</li>
<li><strong>Preface <code>CREATE TABLE</code> statements with <code>DROP TABLE IF EXISTS</code>.</strong> Likewise, use <strong><code>DROP TABLE IF EXISTS</code></strong> when removing a table.</li>
<li><strong>Avoid key conflicts</strong> by running a <code>DELETE</code> or <code>TRUNCATE</code> before <code>INSERT</code>, or use <code>REPLACE</code> instead.</li>
<li><strong>Set character set and collation settings at the database level</strong>, and remove references to specific character sets or collations at the table or column level. MySQL Workbench is notorious for adding these when changing or creating <code>VARCHAR()</code> or <code>TEXT</code> columns, which can cause display or foreign key problems if a mismatch occurs.</li>
<li><strong>Beware of losing or corrupting data.</strong> Data loss may be insignificant during development, but consider the impact your migration will have on a production database.
<ul>
<li>Altering a column is generally preferable to dropping and re-adding a column.</li>
<li>When changing between incompatible datatypes (such as changing from <code>VARCHAR()</code> to <code>DATE</code>), you should use this sequence:
<ol>
<li>Rename the old column or table,</li>
<li>Add the new column or table,</li>
<li>Run an <code>UPDATE</code> query to reformat and copy the data from the old location to the new one, and <em>then</em></li>
<li>Drop the old column or table.</li>
</ol>
</li>
</ul>
</li>
<li><strong>Be aware of index name length limits.</strong> MySQL silently truncates really long index names, and if you try to add a similar (long) index name, it could inexplicably clash with the other index.</li>
</ul>
<h2>Play nice with InnoDB</h2>
<p>MySQL&#8217;s InnoDB storage engine, now the default in MySQL 5.5, is very strict and often obscure in its error messages. These rules will save you lots of grief when applying migrations that affect InnoDB tables, especially when it involves foreign key constraints.</p>
<ul>
<li><strong>Avoid suppressing InnoDB foreign key checks</strong> with <code>FOREIGN_KEY_CHECKS=0</code>, unless you know what you are doing and have a very good reason.</li>
<li><strong>Match foreign keys <span style="text-decoration: underline;">exactly</span>.</strong> Type, length, attributes (signed/unsigned, nullable, etc), character set, collation, and storage engine must all match identically.</li>
<li><strong>Drop foreign key constraints before altering primary or foreign keys.</strong> You&#8217;ll need to use separate <code>ALTER TABLE</code> statements for this, one to remove the constraint and change the column, and one to re-add the constraint.</li>
<li><strong>Drop the primary key index before changing a primary key column.</strong> Much like the previous point, but you&#8217;ll need to run <code>DROP PRIMARY KEY</code>, make your change, and then <code>ADD PRIMARY KEY (`column`)</code> afterwards.</li>
<li><strong>Remove references to specific databases</strong>. Your foreign keys should not depend on the presence of another specific database, and should work regardless of the database name. MySQL Workbench is notorious for adding schema prefixes to table names when creating foreign key constraints.</li>
<li><strong>Be aware of foreign key constraint name length limits.</strong> See the point &#8220;Be aware of index name length limits&#8221; above.</li>
</ul>
<h2>Smooth the migration process</h2>
<ul>
<li><strong><strong><strong>Apply all the statements for a single revision inside a transaction</strong></strong></strong>, so that whenever it fails, your database is not left in an unstable state. This way, you can fix the error and re-run the entire migration.<strong><strong><br />
</strong></strong></li>
<li><strong><strong>Automate schema migrations.</strong></strong> Don&#8217;t make your co-workers apply schema revisions by hand; script it.<strong><br />
</strong></li>
<li><strong>Take a backup first.*</strong> On production databases, this is just raw common sense. Take precautions against Murphy&#8217;s law.</li>
<li><strong>Test on a backup copy.*</strong> Some bugs can lurk in production databases that might not be present in your development database due to data differences. Know that your migration will work on the production database, before you apply it.</li>
</ul>
<p>* Specific to production databases.</p>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-10-08/mysql-schema-migrations-best-practices/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-10-08/mysql-schema-migrations-best-practices/</feedburner:origLink></item>
		<item>
		<title>Detecting mobile browsers via Javascript</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/w8wlk0K7_VQ/</link>
		<comments>http://jonathonhill.net/2011-10-06/detecting-mobile-browsers-via-javascript/#comments</comments>
		<pubDate>Thu, 06 Oct 2011 16:58:47 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[browser]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[mobile]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=308</guid>
		<description><![CDATA[Sometimes you want to do some sort of special processing in Javascript on mobile browsers, or vice versa. Here&#8217;s a quickie Javascript way to do that: function is_mobile() { var agents = ['android', 'webos', 'iphone', 'ipad', 'blackberry']; for(i in agents) { if(navigator.userAgent.match('/'+agents[i]+'/i')) { return true; } } return false; } This version is fast and [...]]]></description>
			<content:encoded><![CDATA[<p>Sometimes you want to do some sort of special processing in Javascript on mobile browsers, or vice versa. Here&#8217;s a quickie Javascript way to do that:</p>
<pre><code>function is_mobile() {
	var agents = ['android', 'webos', 'iphone', 'ipad', 'blackberry'];
	for(i in agents) {
		if(navigator.userAgent.match('/'+agents[i]+'/i')) {
			return true;
		}
	}
	return false;
}</code></pre>
<p>This version is fast and easy to extend to detect additional browsers by changing the <code>agents</code> array. Here&#8217;s a much more thorough version, modified from <a href="http://detectmobilebrowsers.com/" target="_blank">detectmobilebrowsers.com</a>:</p>
<pre><code>function is_mobile() {
	return /android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4));
}</code></pre>
<p>Since this is a single gigantic regex, I suspect that it would run more slowly than the previous version, and is also more challenging to modify.</p>
<p>Instead of browser user-agent sniffing, you might want to consider using <a href="http://stackoverflow.com/questions/3514784/best-way-to-detect-handheld-device-in-jquery/7677700" target="_blank">browser feature detection</a> instead to tailor your application to the capabilities of the browser, rather than relying on the userAgent string (which can be customized in most browsers).</p>
<p>Enjoy!</p>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-10-06/detecting-mobile-browsers-via-javascript/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-10-06/detecting-mobile-browsers-via-javascript/</feedburner:origLink></item>
		<item>
		<title>Enabling a Dell Wireless 1370 network adapter (BCM4328) on Ubuntu Linux</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/Kzuc4PlaRA4/</link>
		<comments>http://jonathonhill.net/2011-10-04/enabling-a-dell-wireless-1370-network-adapter-bcm4328-on-ubuntu-linux/#comments</comments>
		<pubDate>Wed, 05 Oct 2011 02:40:07 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[BCM4328]]></category>
		<category><![CDATA[Linux Mint]]></category>
		<category><![CDATA[ndiswrapper]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=304</guid>
		<description><![CDATA[After three recent virus infections on Windows XP and Windows 7 (including at least one rootkit infection), I turned to Ubuntu Linux as a safer operating system. Two of the PCs were blessed with Atheros-based wireless network adapters, which are well-supported on Linux. The other laptop, a Dell Inspiron 2200, is blessed with one of [...]]]></description>
			<content:encoded><![CDATA[<p>After three recent virus infections on Windows XP and Windows 7 (including at least one rootkit infection), I turned to <a href="http://www.ubuntu.com" target="_blank">Ubuntu Linux</a> as a safer operating system. Two of the PCs were blessed with Atheros-based wireless network adapters, which are well-supported on Linux. The other laptop, a Dell Inspiron 2200, is blessed with one of those infamous Broadcom chipsets.</p>
<p>Supposedly, the <a href="http://sourceforge.net/apps/mediawiki/ndiswrapper/index.php?title=Dell_Wireless_1370" target="_blank">BCM4328</a> (rev 02) wireless chipset is <a title="b43 and b43legacy Linux wireless drivers" href="http://linuxwireless.org/en/users/Drivers/b43" target="_blank">supported on Linux</a>, but as of Ubuntu Desktop 11.04 and Linux Mint 11, it doesn&#8217;t work reliably. So I turned to the old tried-and-true <a href="http://en.wikipedia.org/wiki/NDISwrapper" target="_blank">ndiswrapper</a> to run the BCM4328 <a href="http://ftp.us.dell.com/network/R115321.EXE" target="_blank">Windows driver</a> under Linux.<span id="more-304"></span></p>
<h2>ndiswrapper conflicts</h2>
<p>The process was nowhere near as straightforward as I expected. Ubuntu users since 6.04 have experienced with <a title="Module b44 interfering with ndiswrapper upon startup" href="https://bugs.launchpad.net/ubuntu/+source/linux/+bug/214917" target="_blank">ndiswrapper</a> <a title="ssb module breaks BCM4328 with ndiswrapper (regression from 2.6.24-10)" href="https://bugs.launchpad.net/ubuntu/+source/linux/+bug/197558" target="_blank">conflicts</a> with any of the following kernel modules:</p>
<ul>
<li>bcm43xx</li>
<li>b43</li>
<li>b43legacy</li>
<li>ssb</li>
<li>b44</li>
</ul>
<h2>Resolution</h2>
<ol>
<li>Blacklist the conflicting modules:
<pre><code>echo -e "blacklist bcm43xx\nblacklist b43\nblacklist b43legacy\nblacklist ssb\nblacklist b44" | sudo tee -a /etc/modprobe.d/blacklist.conf</code></pre>
</li>
<li>Install the Windows driver into ndiswrapper:
<pre><code>sudo ndiswrapper -i ~/drivers/bcmwl5.inf
sudo ndiswrapper -l
sudo ndiswrapper -m
sudo depmod -a
sudo modprobe ndiswrapper</code></pre>
</li>
<li>Configure nm-applet to load ndiswrapper:
<pre><code>echo -e "\nndiswrapper\n" | sudo tee -a /etc/modules</code></pre>
</li>
<li>Create a boot script to unload and re-load the conflicting kernel modules in the right order (thanks to <a href="https://bugs.launchpad.net/ubuntu/+source/linux/+bug/197558/comments/14" target="_blank">levmatta</a>):
<ol>
<li>Create a file in /etc/init.d/ndiswrapper with this text:
<pre><code>#! /bin/sh
### BEGIN INIT INFO
# Provides: ndiswrapper
# Required-Start:
# Required-Stop:
# Default-Start: S
# Default-Stop:
# Short-Description: enable to load ndiswrapper
# Description: enable to load ndiswrapper
### END INIT INFO

rmmod b44
rmmod ohci_hcd
rmmod ssb
rmmod ndiswrapper
modprobe ndiswrapper
modprobe ssb
modprobe ohci_hcd
modprobe b44

############# end file ############</code></pre>
</li>
<li>Set the file access permissions:
<pre><code>sudo chmod 755 /etc/init.d/ndiswrapper</code></pre>
</li>
<li>Set this script to run at startup:
<pre><code>sudo ln -s /etc/ndiswrapper /etc/rc2.d/S99ndiswrapper</code></pre>
</li>
</ol>
</li>
<li>Reboot.</li>
</ol>
<p>This worked for me on Linux Mint 11, which is based on Ubuntu 11.04. Your mileage may vary.</p>
<h2>References</h2>
<ul>
<li><a href="https://help.ubuntu.com/community/WifiDocs/Driver/Ndiswrapper" target="_blank">Ubuntu ndiswrapper community documentation</a></li>
<li>Ubuntu forum topic &#8220;<a href="http://ubuntuforums.org/showthread.php?t=766560" target="_blank">Broadcom Wireless Setup for Hardy Ubuntu</a>&#8220;</li>
<li>Fedora forum topic &#8220;<a href="http://forums.fedoraforum.org/showthread.php?t=182684">Network controller: Broadcom Corporation BCM4328 802.11a/b/g/n</a>&#8220;</li>
<li>Ubuntu bug #214917 &#8220;<a href="https://bugs.launchpad.net/ubuntu/+source/linux/+bug/214917" target="_blank">Module b44 interfering with ndiswrapper upon startup</a>&#8220;</li>
<li>Ubuntu bug #197558 &#8220;<a href="https://bugs.launchpad.net/ubuntu/+source/linux/+bug/197558" target="_blank">ssb module breaks BCM4328 with ndiswrapper (regression from 2.6.24-10)</a>&#8220;</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-10-04/enabling-a-dell-wireless-1370-network-adapter-bcm4328-on-ubuntu-linux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-10-04/enabling-a-dell-wireless-1370-network-adapter-bcm4328-on-ubuntu-linux/</feedburner:origLink></item>
		<item>
		<title>http_build_query(): surprise!</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/AG1QKsIM3OU/</link>
		<comments>http://jonathonhill.net/2011-09-30/http_build_query-surprise/#comments</comments>
		<pubDate>Fri, 30 Sep 2011 16:04:52 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[http_build_query()]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=302</guid>
		<description><![CDATA[Did you know that PHP&#8217;s http_build_query() drops any key from your query array if the value is NULL? I wonder how many subtle API client bugs are caused by this behavior. The workaround is easy, just set values that should be intentionally empty to boolean FALSE, integer 0, or an empty string.]]></description>
			<content:encoded><![CDATA[<p>Did you know that PHP&#8217;s <code>http_build_query()</code> drops any key from your query array if the value is <code>NULL</code>? I wonder how many subtle API client bugs are caused by this behavior.</p>
<p>The workaround is easy, just set values that should be intentionally empty to boolean <code>FALSE</code>, integer 0, or an empty string.</p>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-09-30/http_build_query-surprise/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-09-30/http_build_query-surprise/</feedburner:origLink></item>
		<item>
		<title>Master-Master MySQL Replication…that hurts less</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/JGu7WyvhjeA/</link>
		<comments>http://jonathonhill.net/2011-09-30/mysql-replication-that-hurts-less/#comments</comments>
		<pubDate>Fri, 30 Sep 2011 15:53:54 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Replication]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=288</guid>
		<description><![CDATA[If you have ever touched a MySQL slave, you know that they can and do frequently halt. While sync problems can be caused by many things—network outages, schema changes, etc—one of the most common problems in a dual-master setup is primary key collision. Primary Key Collision &#8230;happens when records are added on two different servers [...]]]></description>
			<content:encoded><![CDATA[<p>If you have ever touched a MySQL slave, you know that they can and do frequently halt. While sync problems can be caused by many things—network outages, schema changes, etc—one of the most common problems in a dual-master setup is primary key collision.</p>
<h2>Primary Key Collision</h2>
<p>&#8230;happens when records are added on two different servers to the same table and get the same <code>AUTO_INCREMENT</code> value. Fortunately, there is a trivially easy way to prevent this from happening.</p>
<h3><code>auto-increment-increment=N</code></h3>
<p>Adding this to your <code>my.cnf</code> or <code>my.ini</code> file will make <code>AUTO_INCREMENT</code> increment by <code>N</code> rather than by 1. <code>N</code> is the number of replicated servers that are masters.</p>
<p>Combine this setting with&#8230;</p>
<h3><code>auto-increment-offset=N</code></h3>
<p>&#8230;to ensure that each replicated master uses unique <code>AUTO_INCREMENT</code> values. <code>N</code> should match the <code>server-id</code> setting.</p>
<p>With these two settings in place, your primary keys will never collide.<span id="more-288"></span></p>
<h2>Example</h2>
<p>Let&#8217;s suppose you have three replicated master MySQL servers, each with a <code>`comments`</code> table with a last id of 1000. Your servers are configured as follows:</p>
<h3>Server 1:</h3>
<pre><code>server-id = 1
auto-increment-increment = 3
auto-increment-offset = 1</code></pre>
<p>Server 1 will generate <code>AUTO_INCREMENT</code> values of the form <code>1 + 3N</code> (4, 7, 10, 13&#8230;), where <code>N</code> is a sequential positive integer.</p>
<h3>Server 2:</h3>
<pre><code>server-id = 2
auto-increment-increment = 3
auto-increment-offset = 2</code></pre>
<p>Server 2 will generate <code>AUTO_INCREMENT</code> values of the form <code>2 + 3N</code> (5, 8, 11, 14&#8230;), where <code>N</code> is a sequential positive integer.</p>
<h3>Server 3:</h3>
<pre>server-id = 3
auto-increment-increment = 3
auto-increment-offset = 3</pre>
<p>Server 3 will generate <code>AUTO_INCREMENT</code> values of the form <code>3 + 3N</code> (6, 9, 12, 15&#8230;), where <code>N</code> is a sequential positive integer.</p>
<p>At the same instant, three different visitors to your web application comment. Here&#8217;s what happens to the <code>`comments`</code> table:</p>
<ul>
<li>Server 1: <code>AUTO_INCREMENT</code> = 1003 (1 + 334 * 3)</li>
<li>Server 2: <code>AUTO_INCREMENT</code> = 1004 (2 + 334 * 3)</li>
<li>Server 3: <code>AUTO_INCREMENT</code> = 1005 (3 + 334 * 3)</li>
</ul>
<p>Let&#8217;s suppose that replication stopped before these three new rows could be replicated on the other two servers, and three more visitors leave comments. What IDs will be assigned?</p>
<ul>
<li>Server 1: <code>AUTO_INCREMENT</code> = 1006 (1 + 335 * 3)</li>
<li>Server 2: <code>AUTO_INCREMENT</code> = 1007 (2 + 335 * 3)</li>
<li>Server 3: <code>AUTO_INCREMENT</code> = 1008 (2 + 335 * 3)</li>
</ul>
<p>Beautiful!</p>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-09-30/mysql-replication-that-hurts-less/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-09-30/mysql-replication-that-hurts-less/</feedburner:origLink></item>
		<item>
		<title>Heartbeat logging while consuming Twitter streams using Phirehose</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/113BfPjxDbg/</link>
		<comments>http://jonathonhill.net/2011-06-11/phirehose-heartbeat-logging/#comments</comments>
		<pubDate>Sat, 11 Jun 2011 18:15:49 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Phirehose]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Twitter]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=283</guid>
		<description><![CDATA[Phirehose is an awesomely useful Twitter Streaming API client library, written in PHP by Fenn Bailey. Heartbeat logging is something that I originally added for Rainmaker, and I finally got around to contributing those modifications, which you can see here on GitHub. Why log heartbeats in Phirehose? To gain assurance that Phirehose is still alive, [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://github.com/fennb/phirehose" target="_blank">Phirehose</a> is an awesomely useful <a href="http://dev.twitter.com/pages/streaming_api" target="_blank">Twitter Streaming API</a> client library, written in PHP by <a href="http://github.com/fennb" target="_blank">Fenn Bailey</a>.</p>
<p>Heartbeat logging is something that I originally added for <a href="http://rainmakerapp.com" target="_blank">Rainmaker</a>, and I finally got around to contributing those modifications, which you can see here on <a href="https://github.com/fennb/phirehose/commit/7118ca973f67998a72a303497f6f6a6ced1099b8" target="_blank">GitHub</a>.</p>
<h2>Why log heartbeats in Phirehose?</h2>
<ul>
<li>To gain assurance that Phirehose is still alive, and actually functioning. In our case, missing tweets means lost money and unhappy clients. We needed to monitor this very closely.</li>
<li>To enable automatically detecting connection drops and rewinding the count parameter to pick up those tweets, or backfilling them in using the Twitter Search API.</li>
<li>To collect usage data for reporting purposes.</li>
</ul>
<h2>Usage</h2>
<p>To use this, simply declare a <code>heartbeat(array $data)</code> method in your Phirehose child class.<span id="more-283"></span></p>
<p>Here is an example:</p>
<pre>&lt;?php

// USAGE:
//
//   require_once 'phirehose.php';
//   $dbh = mysql_connect($host, $username, $password);
//   PhirehoseConsumer::Initialize($dbh);
//   PhirehoseConsumer::start();

class PhirehoseConsumer extends Phirehose {

	private static $instance;
	private static $dbh;

	protected $consumer_start_time = 0;
	protected $consumer_uptime = 0;

	public static function Initialize($dbh) {
		if (! self::$instance instanceof self) {
			self::$dbh = $dbh;
			self::$instance = new self ( TWITTER_API_USERNAME, TWITTER_API_PASSWORD, Phirehose::METHOD_FILTER );
		}
	}

	protected static function get_last_heartbeat() {
		$sql = 'SELECT time_stamp FROM stream_log ORDER BY time_stamp DESC LIMIT 1';
		$result = mysql_query ( self::$dbh, $sql );
		$last_heartbeat_ts = mysql_result ( $result, 0, 'time_stamp' );
		if (strtotime ( $last_heartbeat_ts ) === FALSE)
			$last_heartbeat_ts = date ( 'Y-m-d H:i:s' ); // default to now
		return $last_heartbeat_ts;
	}

	protected static function get_missed_count() {

		// get timestamp of the most recent intake script heartbeat
		$last_heartbeat_ts = self::get_last_heartbeat ();

		// how long since we were last running?
		$downtime = ( int ) (time () - strtotime ( $last_heartbeat_ts ));

		// Based on the past average, how many tweets did we probably miss?
		//
		// This calculation is based on the statusRate averaged over a period of
		// time not greater than $downtime, and multiplied by a 1.5x fudge factor.
		$sql = &lt;&lt;&lt;SQL
SELECT CEIL(1.5 * AVG(statusRate) * $downtime) AS count
FROM stream_log WHERE
    (UNIX_TIMESTAMP('$last_heartbeat_ts') - UNIX_TIMESTAMP(time_stamp) &gt; 0) AND
    (UNIX_TIMESTAMP('$last_heartbeat_ts') - UNIX_TIMESTAMP(time_stamp) &gt; 0) &lt;= $downtime
SQL;
		$result = mysql_query ( self::$dbh, $sql );
		$count = mysql_result ( $result, 0, 'count' );

		return ($count === FALSE) ? 0 : ( int ) $count;
	}

	public static function start() {
		// get timestamp of the most recent heartbeat
		$last_heartbeat_ts = self::get_last_heartbeat ();

		// Estimate how many tweets were missed, and rewind.
		//
		// Note that if you're using the filter streaming method, this will fail with the following error:
		//   "The Streaming API count parameter is not allowed in role statusDefaultFiltered."
		$count = self::get_missed_count ();
		if ($count) {
			self::$instance-&gt;log ( "Using count: $count" );
			self::$instance-&gt;setCount ( $count );
		}

		// run backfill script in background - uses Twitter Search API
		self::$instance-&gt;log ( "Backfilling from $last_heartbeat_ts" );
		passthru ( "backfill.php '$last_heartbeat_ts' &gt;/dev/null &amp;" );

		self::$instance-&gt;checkFilterPredicates ();
		self::$instance-&gt;consume ();
	}

	public function enqueueStatus($status) {
		// do something with your tweet
		return TRUE;
	}

	public function heartbeat(array $data) {
		$sql = &lt;&lt;&lt;SQL
INSERT INTO stream_log SET
    time_stamp = CURRENT_TIMESTAMP,
    elapsed = $data[elapsed],
    statusRate = $data[statusRate],
    statusCount = $data[statusCount],
    enqueueSpent = $data[enqueueSpent],
    enqueueSpentAvg = $data[enqueueSpentAvg],
    filterCheckCount = $data[filterCheckCount],
    filterCheckSpent = $data[filterCheckSpent],
    idlePeriod = $data[idlePeriod],
    maxIdlePeriod = $data[maxIdlePeriod]
SQL;
		mysql_query ( $dbh, $sql );
	}

	protected function checkFilterPredicates() {
		// useful for debugging
		$this-&gt;consumer_uptime = time () - $this-&gt;consumer_start_time;
		$this-&gt;log ( sprintf ( 'Uptime: %s, memory usage: %skb', $this-&gt;consumer_uptime, number_format ( memory_get_usage () / 1024, 0 ) ) );

		// update filter predicates...
	}

	protected function connect() {
		$this-&gt;consumer_start_time = time ();
		parent::connect ();
	}

}</pre>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-06-11/phirehose-heartbeat-logging/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-06-11/phirehose-heartbeat-logging/</feedburner:origLink></item>
		<item>
		<title>Solved: “Access Denied” errors when calling signtool.exe from PHP</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/ctc-IG63xGw/</link>
		<comments>http://jonathonhill.net/2011-05-26/solved-access-denied-errors-when-calling-signtool-exe-from-php/#comments</comments>
		<pubDate>Thu, 26 May 2011 23:51:12 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[code signing]]></category>
		<category><![CDATA[permissions]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[signtool.exe]]></category>
		<category><![CDATA[WAMP]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=277</guid>
		<description><![CDATA[SIGHntool, why must you give me such grief? I have spent the last 8 hours trying to figure out why Microsoft&#8217;s signtool.exe code signing utility refuses to work when called from PHP&#8217;s system() or shell_exec() functions on my WAMP server: C:\build&#62; "C:\Program Files\InstallMate 7\Tools\signtool.exe" sign /v /f codesignedcert.pfx Setup.exe 2&#62;&#38;1 The following certificate was selected: [...]]]></description>
			<content:encoded><![CDATA[<h2>SIGHntool, why must you give me such grief?</h2>
<p>I have spent the last 8 hours trying to figure out why Microsoft&#8217;s <code>signtool.exe</code> code signing utility refuses to work when called from PHP&#8217;s system() or shell_exec() functions on my WAMP server:</p>
<pre>C:\build&gt; "C:\Program Files\InstallMate 7\Tools\signtool.exe" sign /v /f codesignedcert.pfx Setup.exe 2&gt;&amp;1

The following certificate was selected:
    Issued to: &lt;redacted&gt;.
    Issued by: UTN-USERFirst-Object
    Expires:   5/12/2012 6:59:59 PM
    SHA1 hash: &lt;redacted&gt;

Done Adding Additional Store

Attempting to sign: C:\build\Setup.exe

Number of files successfully Signed: 0
Number of warnings: 0
Number of errors: 1

SignTool Error: ISignedCode::Sign returned error: 0x80090010

	Access denied.

SignTool Error: An error occurred while attempting to sign: C:\build\Setup.exe</pre>
<p><em>Note: the <code>2&gt;&amp;1</code> at the end of the signtool call is essential if you want to capture error messages which are emitted to <a href="http://en.wikipedia.org/wiki/Standard_streams#Standard_error_.28stderr.29" target="_blank"><code>STDERR</code></a> instead of <a href="http://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29" target="_blank"><code>STDOUT</code></a>. Yes, I lost an hour or two just on that.<br />
</em></p>
<h2>Dead ends</h2>
<ul>
<li>Windows 7 apparently <a title="Fixing the Windows 7 Read Only Folder Blues" href="http://itexpertvoice.com/ad/fixing-the-windows-7-read-only-folder-blues/" target="_blank">sets the read-only attribute</a> on <em>all files</em>, and it isn&#8217;t easy to turn that attribute off. But since other file operations worked from PHP, this wasn&#8217;t the issue.</li>
<li>Prefacing the signtool call with <code>CMD /C</code> didn&#8217;t help.</li>
<li>Setting full control file permissions on the <code>C:\build</code> folder for Guest, SYSTEM, and any other user account I could think of didn&#8217;t help either.</li>
<li>Wrapping signtool in a batch file was an exercise in futility.</li>
</ul>
<p>The maddeningly frustrating thing was that signtool worked great when called from the command line &#8212; just not from PHP!</p>
<h2>An aha! moment</h2>
<p>The issue turned out to be pretty stupid, as they usually do. I merely had to change the account that Apache was running as to that of a normal user, instead of the default local system account.</p>
<p><a href="http://jonathonhill.net/wp-content/uploads/2011/05/apache-service-user.png"><img class="alignnone" src="/wp-content/uploads/2011/05/apache-service-user.png" alt="services.msc - changing the Apache user" width="100%" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-05-26/solved-access-denied-errors-when-calling-signtool-exe-from-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-05-26/solved-access-denied-errors-when-calling-signtool-exe-from-php/</feedburner:origLink></item>
		<item>
		<title>Uploading large files: covering all the bases</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/3h43EuJegLc/</link>
		<comments>http://jonathonhill.net/2011-05-05/uploading-large-files/#comments</comments>
		<pubDate>Thu, 05 May 2011 14:27:45 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[uploading]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=274</guid>
		<description><![CDATA[When uploading a file to a PHP script on an Apache web server, there are several configuration options that if improperly set can get in the way. I just encountered yet another one of these, and decided to catalog them here. Size, Time, and Memory There are three types of limits that affect file uploads, and [...]]]></description>
			<content:encoded><![CDATA[<p>When uploading a file to a PHP script on an Apache web server, there are several configuration options that if improperly set can get in the way. I just encountered yet <em>another</em> one of these, and decided to catalog them here.</p>
<h2>Size, Time, and Memory</h2>
<p>There are three types of limits that affect file uploads, and the weakest link in the chain is your effective limit.</p>
<p>If your size limit is set to 3gb, but your time limit does not allow for the time required to upload that much data, you&#8217;ll still be unable to upload those large files. Likewise, the ability to upload does no good if you do not have enough memory to process the file that was uploaded.</p>
<h2>Assumptions</h2>
<p>This post assumes an 8MB upload limit (8mb x 1024kb x 1024 bytes = 8388608). You will want to adjust this number up or down according to your needs.</p>
<p>Oddly, although 8mb is the default value for PHP&#8217;s <code>upload_max_filesize</code> setting, some of the other default settings are much lower (2mb, or in some cases, only 100k).</p>
<h2>PHP limits</h2>
<pre><code>upload_max_filesize = 8388608
post_max_size = 8388608
max_input_time = 60</code></pre>
<p>Depending on what you&#8217;re doing with the uploaded files, you may also need to increase your memory limit:</p>
<pre><code>memory_limit = 64MB</code></pre>
<h2>Apache limits</h2>
<p>If <a href="http://httpd.apache.org/docs/2.0/mod/core.html#limitrequestbody" target="_blank"><code>LimitRequestBody</code></a> is set to something non-zero, you may need to increase its value in your Apache <code>httpd.conf</code> file or <code>.htaccess</code> file:</p>
<pre><code>LimitRequestBody 8388608</code></pre>
<p>If you are using <a href="http://httpd.apache.org/mod_fcgid/" target="_blank"><code>mod_fcgid</code></a> (required to run the latest <a href="http://windows.php.net/download/#php-5.3-nts-VC9-x86" target="_blank">PHP 5.3 VC9 NTS build for Windows</a>), then you need to set the value of <a href="http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidmaxrequestlen" target="_blank"><code>FcgidMaxRequestLen</code></a>, which defaults to 100k if it is not set. (Note that some systems may put <code>mod_fcgid</code> settings in a file separate from the main <code>httpd.conf</code> file).</p>
<pre><code>FcgidMaxRequestLen 8388608
FcgidIOTimeout 60</code></pre>
<p>Happy uploading!</p>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-05-05/uploading-large-files/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-05-05/uploading-large-files/</feedburner:origLink></item>
		<item>
		<title>A plugged-in-and-giving-back mentality</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/VnqlN4SQF8o/</link>
		<comments>http://jonathonhill.net/2011-04-28/a-plugged-in-and-giving-back-mentality/#comments</comments>
		<pubDate>Thu, 28 Apr 2011 12:18:52 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[community]]></category>
		<category><![CDATA[open source]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=272</guid>
		<description><![CDATA[While working on the Chrome version of a browser extension for a client of my company, I subscribed to the chromium-extensions Google group. I did this both for my own edification as well as for others&#8217; benefit. Every now and then, a message comes through that relates to my personal experience or knowledge which has [...]]]></description>
			<content:encoded><![CDATA[<p>While working on the Chrome version of a browser extension for a client of <a title="Company52" href="http://company52.com" target="_blank">my company</a>, I  subscribed to the <a href="http://groups.google.com/group/chromium-extensions" target="_blank">chromium-extensions Google group</a>. I did this both for  my own edification as well as for others&#8217; benefit.</p>
<p>Every now and then, a message comes through that relates to my personal experience or knowledge  which has not yet been answered, and I will answer it.</p>
<p>When you contribute to an open-source  community, it does two things: it builds your reputation within the  community, and it increases the likelihood of getting quality help yourself when  you need it. There&#8217;s also the bonus of promoting your company if you use an  email signature. This has SEO value since most of the time these  messages wind up in search engines.</p>
<p>Besides chromium-extensions, I am also subscribed to <a href="http://groups.google.com/group/phirehose-users" target="_blank">phirehose-users</a> (my participation here <a title="SiteStreams OAuth support" href="http://groups.google.com/group/phirehose-users/browse_thread/thread/78c0c7f42567742d" target="_blank">earned commit access</a> as an official contributor), <a href="http://groups.google.com/group/twitter-development-talk" target="_blank">twitter-development-talk</a>, and some local PHP user groups lists.</p>
<p>I wish this kind of plugged-in-and-giving-back mentality were more prevalent. <strong>If you use an open-source project</strong> or technology, <strong>subscribe to the mailing list</strong> (or forum), <strong>and don&#8217;t just lurk</strong>. Answer questions, and where appropriate, share your code or your experiences.</p>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-04-28/a-plugged-in-and-giving-back-mentality/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-04-28/a-plugged-in-and-giving-back-mentality/</feedburner:origLink></item>
		<item>
		<title>Takeaways from PHPCon, day 2</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/vaPW9u228Mc/</link>
		<comments>http://jonathonhill.net/2011-04-25/phpcon-day-2/#comments</comments>
		<pubDate>Mon, 25 Apr 2011 15:36:45 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[continuous integration]]></category>
		<category><![CDATA[I18N]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[php6]]></category>
		<category><![CDATA[PHPCon]]></category>
		<category><![CDATA[phpunit]]></category>
		<category><![CDATA[testing]]></category>
		<category><![CDATA[unicode]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=266</guid>
		<description><![CDATA[Get my day one takeaways here. Day two at PHPCon in Nashville, TN was packed with lots of information that frankly, I&#8217;m still digesting. It was well worth the trip and ended much too quickly! Download my notes (PDF) Morning Keynote In his “brain dump,” Rasmus Lerdorf shared a collection of unrelated but very useful [...]]]></description>
			<content:encoded><![CDATA[<p><em>Get my <a title="Takeaways from PHPCon, day 1" href="http://jonathonhill.net/2011-04-21/phpcon-day-1/">day one takeaways here</a>.</em></p>
<p>Day two at PHPCon in Nashville, TN was packed with lots of information that frankly, I&#8217;m still digesting. It was well worth the trip and ended much too quickly!</p>
<p><a href="http://jonathonhill.net/wp-content/uploads/2011/04/PHPCon2.pdf"><strong>Download my notes (PDF)</strong></a></p>
<h2>Morning Keynote</h2>
<p>In his “brain dump,” <a title="Wikipedia entry for &quot;Rasmus Lerdorf&quot;" href="http://en.wikipedia.org/wiki/Rasmus_Lerdorf" target="_blank">Rasmus Lerdorf</a> shared a collection of unrelated but very useful tips and observations about PHP.</p>
<ul>
<li>PHP 	is the bottleneck, there is no significant difference between 	nginx/lighthttpd/apache</li>
<li>Error 	handling is not optimized in PHP because it should be an infrequent 	event. Set <code>error_reporting = -1</code></li>
<li>Insufficient 	<code>realpath_cache_size</code> in PHP 5.2 can cause excessive filesystem STATs</li>
<li>Use 	gearman for out-of-band processing. Threads aren&#8217;t needed in PHP.</li>
<li>Don&#8217;t 	overload apache (8 core CPU = 25-30 apache clients; never more than 	50)</li>
<li>Deploy 	processes should be atomic and robust (can use <a href="http://capify.org" target="_blank">Capistrano</a> or Rasmus&#8217; 	own <a href="http://www.wepay.com/dev/2010/11/30/weploy-wepays-deployment-tool/">weploy</a> script)</li>
<li><a href="http://nodejs.org" target="_blank">Node.js</a>-style 	event programming is fast and easy (see 	<a href="http://php.net/libevent">http://php.net/libevent</a>)</li>
</ul>
<p>Slides: <a href="http://talks.php.net/show/phpcon2011">http://talks.php.net/show/phpcon2011</a> (ya gotta love the homegrown web-based presentation system!)</p>
<h2>The Original Hypertext Preprocessor</h2>
<p><a href="http://allinthehead.com/" target="_blank">Drew McLellan</a>&#8216;s presentation was particularly relevant for me in my role as CTO at <a href="http://company52.com" target="_blank">Company52</a>, and I consider it one of the best presentations at PHPCon. Drew&#8217;s goal was not to market <a title="Perch CMS" href="http://grabaperch.com/" target="_blank">Perch</a> (although he did an awesome job of it without even trying), but rather to share his philosophy of what really great client support is all about, and how it has impacted his work.</p>
<div id="__ss_7708191" style="width: 425px;">
<p><strong style="display: block; margin: 12px 0 4px;"><a title="Original Hypertext Preprocessor" href="http://www.slideshare.net/drewm/original-hypertext-preprocessor">Original Hypertext Preprocessor</a></strong> <object id="__sse7708191" width="425" height="355"><param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=originalhypertextpreprocessor-110422152543-phpapp02&amp;rel=0&amp;stripped_title=original-hypertext-preprocessor&amp;userName=drewm" /><param name="allowFullScreen" value="true" /><param name="allowScriptAccess" value="always" /><embed type="application/x-shockwave-flash" width="425" height="355" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=originalhypertextpreprocessor-110422152543-phpapp02&amp;rel=0&amp;stripped_title=original-hypertext-preprocessor&amp;userName=drewm" name="__sse7708191" allowscriptaccess="always" allowfullscreen="true"></embed></object></p>
<div style="padding: 5px 0 12px;">View more <a href="http://www.slideshare.net/">presentations</a> from <a href="http://www.slideshare.net/drewm">Drew McLellan</a></div>
</div>
<ul>
<li>Throwing 	new features at a problem often doesn&#8217;t solve it. Functionality is 	not enough.</li>
<li>Find 	ways to reduce support requests.</li>
<li>Every 	support request should be unique (no FAQs).</li>
<li>Fix 	areas of confusion rapidly.</li>
<li>Support 	your own software – programmers should see issues firsthand.</li>
<li>It&#8217;s 	OK to be opinionated (“WYSIWYG is evil”), but don&#8217;t be 	dictatorial. It&#8217;s not our place to tell people how to work.</li>
<li>Help 	customers look good in front of their clients.</li>
<li>Accept 	when users are having problems.</li>
<li>Really 	great developers <em>solve problems</em>. Excuses simply are not helpful.</li>
</ul>
<h2>The WonderProxy story</h2>
<p><a href="http://blog.preinheimer.com/" target="_blank">Paul Reinheimer</a> shared the story of how he built (and self-funded) <a href="https://wonderproxy.com/" target="_blank">WonderProxy</a>, born out of a personal need to test applications that use IP-based <a title="&quot;Geolocation&quot; Wikipedia entry" href="http://en.wikipedia.org/wiki/Geolocation" target="_blank">geolocation</a>.</p>
<ul>
<li>Mistakes 	– “crouch and hope you don&#8217;t get hit”
<ul>
<li>No 		account de-activation</li>
<li>NIH 		– wrote paypal IPN code instead of re-using own code</li>
<li>Mixing 		Linux distros</li>
<li>Server 		account renewals</li>
<li>Afraid 		to look at profitability numbers</li>
</ul>
</li>
<li>Old 	strategy: blog about problems we encounter – competitors find 	posts</li>
<li>New 	strategy: blog about problems <em>customers</em> have – bring <em>customers</em> instead of competitors</li>
</ul>
<h2>Is it Handmade Code If You Use Power Tools?</h2>
<p><a title="@elblinkin" href="http://twitter.com/elblinkin" target="_blank">Laura Beth Denker</a> of <a href="http://etsy.com" target="_blank">Etsy</a> shared an overview of their <a title="&quot;Continuous Integration&quot; Wikipedia entry" href="http://en.wikipedia.org/wiki/Continuous_integration" target="_blank">continuous integration</a> processes, and how it has greatly improved both confidence and deployment speed. True to Etsy style, Laura came dressed in a handmade outfit from a Nashville-based Etsy vendor, earning a <a href="http://twitter.com/#!/jakemcgraw/statuses/61515712790937601" target="_blank">&#8220;too much swag&#8221; tweet</a> from one listener!</p>
<ul>
<li>NEED 	CONFIDENCE in your code</li>
<li>Effective 	testing strategies include functional (human) testing, integration 	testing (database), and unit testing (foundation)</li>
<li>Don&#8217;t 	use random data in unit tests</li>
<li>Test 	each case in control structures</li>
<li>Use 	DBUnit for testing database interaction</li>
<li>Tests 	should run rapidly</li>
<li>Group 	unit tests and target test groups to run
<ul>
<li>caches</li>
<li>databases</li>
<li>network 		tests (third-party APIs)</li>
<li>sleep</li>
<li>time</li>
<li>smoke, 		curl, regex</li>
<li>flaky</li>
</ul>
</li>
</ul>
<h2>What happened to Unicode in PHP</h2>
<p>Andrei Zmievski&#8217;s talk was a frank de-briefing of the failed attempt to bring native Unicode support to PHP6. Although this story is a rather personal one for Andrei, he was honest and incorporated a few surprisingly hilarious bits of humor. The conclusion: native Unicode support will only come to PHP if and when the community wants it &#8212; and is willing to put noses to the grindstone. The task is simply too big for his elite band of 10 (including Rasmus himself). Most of the content was historical in nature, but there were a few nice tidbits of information.</p>
<div id="__ss_7708787" style="width: 425px;"><strong style="display: block; margin: 12px 0 4px;"><a title="The Good, the Bad, and the Ugly: What Happened to Unicode and PHP 6" href="http://www.slideshare.net/andreizm/the-good-the-bad-and-the-ugly-what-happened-to-unicode-and-php-6">The Good, the Bad, and the Ugly: What Happened to Unicode and PHP 6</a></strong> <object id="__sse7708787" width="425" height="355"><param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=thegoodthebadandtheugly-110422170743-phpapp01&amp;rel=0&amp;stripped_title=the-good-the-bad-and-the-ugly-what-happened-to-unicode-and-php-6&amp;userName=andreizm" /><param name="allowFullScreen" value="true" /><param name="allowScriptAccess" value="always" /><embed type="application/x-shockwave-flash" width="425" height="355" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=thegoodthebadandtheugly-110422170743-phpapp01&amp;rel=0&amp;stripped_title=the-good-the-bad-and-the-ugly-what-happened-to-unicode-and-php-6&amp;userName=andreizm" name="__sse7708787" allowscriptaccess="always" allowfullscreen="true"></embed></object>&nbsp;</p>
<div style="padding: 5px 0 12px;">View more <a href="http://www.slideshare.net/">presentations</a> from <a href="http://www.slideshare.net/andreizm">Andrei Zmievski</a></div>
</div>
<ul>
<li>Complete 	I18N is more than language stuff:
<ul>
<li>Character 		set</li>
<li>Date/time 		formats</li>
<li>Currency 		formats</li>
<li>Collation 		(sorting, contractions – thanks to Andrei for finally helping me 		to understand what a “collation” is)</li>
</ul>
</li>
<li><code>pecl/intl</code> 	has some useful classes left over from the PHP6 unicode project 	(<code>Collator</code>, <code>NumberFormatter</code>, <code>MessageFormatter</code>)</li>
</ul>
<h2>Closing Keynote</h2>
<p>Terry Chay&#8217;s closing keynote wove a common thread through all of the presentations given at PHPCon, along with a heaping helping of humor (seedy at times). My favorite part were the Chayisms: <a href="http://phpdoc.info/chayism/">http://phpdoc.info/chayism/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-04-25/phpcon-day-2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-04-25/phpcon-day-2/</feedburner:origLink></item>
		<item>
		<title>Takeaways from PHPCon, day 1</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/pZBASfufRf4/</link>
		<comments>http://jonathonhill.net/2011-04-21/phpcon-day-1/#comments</comments>
		<pubDate>Fri, 22 Apr 2011 03:17:31 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[frontend]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHPCon]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=259</guid>
		<description><![CDATA[I&#8217;m here at PHPCon, the first PHP community developer conference in Nashville, TN. The first day consisted of two rather lengthy workshops, both of which were very informative. Download my notes (PDF) Web Services This talk was given by Lorna Jane Mitchell, whose totally awesome British accent I could listen to all day. I consider [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m here at <a href="http://phpcon.org" target="_blank">PHPCon</a>, the first PHP community developer conference in Nashville, TN. The first day consisted of two rather lengthy workshops, both of which were very informative.</p>
<p><a title="Notes from PHPCon '11, day 1" href="/wp-content/uploads/2011/04/PHPCon1.pdf">Download my notes (PDF)</a></p>
<h2>Web Services</h2>
<p>This talk was given by <a href="http://www.lornajane.net/" target="_blank">Lorna Jane Mitchell</a>, whose totally awesome British accent I could listen to all day. I consider myself no novice on consuming web services, but being a relative newcomer to <em>building</em> web services, I got a real education on how to do it right.</p>
<div id="__ss_7668316" style="width: 425px;"><strong style="display: block; margin: 12px 0 4px;"><a title="Web Services Tutorial" href="http://www.slideshare.net/lornajane/web-services-tutorial">Web Services Tutorial</a></strong> <object id="__sse7668316" width="425" height="355"><param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=web-services-110418173510-phpapp02&amp;rel=0&amp;stripped_title=web-services-tutorial&amp;userName=lornajane" /><param name="allowFullScreen" value="true" /><param name="allowScriptAccess" value="always" /><embed type="application/x-shockwave-flash" width="425" height="355" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=web-services-110418173510-phpapp02&amp;rel=0&amp;stripped_title=web-services-tutorial&amp;userName=lornajane" name="__sse7668316" allowscriptaccess="always" allowfullscreen="true"></embed></object></p>
<div style="padding: 5px 0 12px;">View more <a href="http://www.slideshare.net/">presentations</a> from <a href="http://www.slideshare.net/lornajane">Lorna Mitchell</a></div>
</div>
<p>Key takeaways:</p>
<ul>
<li><strong>Use curl</strong>, it eliminates points of failure for more accurate testing. Lorna rejects any bug ticket that does not come with a curl test case, which reduced support requests by 50%!</li>
<li>Every web service should have a <strong>heartbeat method</strong> that echos the variables you pass to it.</li>
<li>Every web service should have <strong>documentation</strong>, (real) <strong>examples</strong>, and a <strong>support mechanism</strong>. If you&#8217;re not going to do this, don&#8217;t bother building a web service, &#8217;cause nobody&#8217;s gonna use it.</li>
<li><strong>Utilize the HTTP protocol</strong> as fully as possible, including HTTP <strong>headers</strong> (Accept, Content-Type, User-Agent), <strong>verbs</strong> (GET [read], POST [create], PUT [update], DELETE [delete]), and <strong>status codes</strong> (HTTP 200, 201, 301, 302, 400, 401, 403, 404, and 500).</li>
<li><strong>Give consumers a choice of formats</strong>. text/html is useful for debugging purposes.</li>
<li><strong>Parse the Http-Accept header</strong> and deliver content in first format listed that you support.</li>
<li>Don&#8217;t confuse <strong>HTTP 401</strong> with <strong>HTTP 403</strong> (&#8220;I don&#8217;t know who you are, so I&#8217;m denying access&#8221; vs &#8220;I know who you are, and you don&#8217;t have permission&#8221;).</li>
<li><a title="PHP PECL HTTP module documentation" href="http://us.php.net/http" target="_blank"><strong>pecl_http</strong></a> is an easier way to access web services than <a title="PHP CURL module" href="http://us.php.net/manual/en/book.curl.php" target="_blank">curl</a>.</li>
<li><strong>Error handling defines API quality</strong>. Provide complete, useful, and consistent error messages<strong> in the expected response format</strong>.</li>
</ul>
<p>Link bundle: <a href="http://bit.ly/bundles/lornajane/2" target="_blank">http://bit.ly/bundles/lornajane/2</a></p>
<h2>Frontend Caching</h2>
<p>This talk was given by <a href="http://helgi.ws/" target="_blank">Helgi Þorbjörnsson</a> (I will not even attempt his Icelandic surname). Helgi is a long-time PEAR contributor and experienced front-end developer. Key takeaways:</p>
<ul>
<li><strong>80% of response time is spent downloading resources.</strong></li>
<li><strong>Don&#8217;t abuse cookies</strong>. Large cookies hurt performance because of slow upload speeds, and because they are sent with <em>every</em> request. When you use cookies, be sure to set an expiration date and limit them to only the domains they are needed on.</li>
<li>Browsers have <strong>per-domain concurrent download limits</strong>. You can spread static assets across 3-4 multiple subdomains as a workaround.</li>
<li><strong>Combine files judiciously</strong>. Be aware of the trade-off between fewer server requests and larger file size.</li>
<li><strong>Load above the fold first</strong>.</li>
<li><strong>Minify</strong> Javascript and CSS, preferably at build time.</li>
<li>Use <strong>gzip compression</strong><em>only</em> for text-based content.</li>
<li>Save HTTP 404 bandwidth by ensuring that you have a <strong>robots.txt</strong> file and a <strong>favicon</strong>.</li>
<li><strong>Compress images more</strong> (Photoshop doesn&#8217;t cut it; better alternatives include <a href="http://pmt.sourceforge.net/" target="_blank">pngcrush</a> and <a href="http://jpegclub.org/jpegtran/" target="_blank">jpegtran</a>).</li>
<li>Test with slower connections (tread the user&#8217;s path).</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-04-21/phpcon-day-1/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-04-21/phpcon-day-1/</feedburner:origLink></item>
		<item>
		<title>Catching the Javascript beforeunload event, the cross-browser way</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/bHBD26YCIHk/</link>
		<comments>http://jonathonhill.net/2011-03-04/catching-the-javascript-beforeunload-event-the-cross-browser-way/#comments</comments>
		<pubDate>Fri, 04 Mar 2011 19:36:40 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[beforeunload]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[onbeforeunload]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=248</guid>
		<description><![CDATA[Javascript&#8217;s window.onbeforeunload event is useful for catching users that try to browse away from your page without having completed a certain action. Modern web applications such as Gmail and WordPress have made good use of this event. Being a non-standard event originally invented by Microsoft back in the IE4 days, window.onbeforeunload has some real quirks, [...]]]></description>
			<content:encoded><![CDATA[<p>Javascript&#8217;s <code>window.onbeforeunload</code> event is useful for catching users that try to browse away from your page without having completed a certain action. Modern web applications such as <a href="http://gmail.com" target="_blank">Gmail</a> and <a href="http://wordpress.org" target="_blank">WordPress</a> have made good use of this event.</p>
<p>Being a non-standard event originally invented by Microsoft back in the IE4 days, <code>window.onbeforeunload</code> has some real quirks, although thankfully every major modern browser does support it.</p>
<h2>jQuery Doesn&#8217;t Help</h2>
<p>Prior to jQuery 4, you couldn&#8217;t even bind to <code>$(window).bind('beforeunload')</code> due to a <a href="http://bugs.jquery.com/ticket/4106" target="_blank">bug</a> that has been fixed.</p>
<p>However, this isn&#8217;t your average Javascript event. <code>window.onbeforeunload</code> pops up a native dialog box that provides very little opportunity for customization beyond the text that is displayed to the user. There is no known way to disable this native dialog box and prevent normal behavior.</p>
<p>Tapping into jQuery&#8217;s <code>$(window).unload()</code> event doesn&#8217;t allow you to prevent the page from being unloaded, and I couldn&#8217;t get <code>$(window).bind('beforeunload')</code> to work at all in Firefox 3.6.</p>
<h2>The Right Way</h2>
<p>The right way turned out to be quite easy using native Javascript code (thanks to the <a href="https://developer.mozilla.org/en/DOM/window.onbeforeunload" target="_blank">Mozilla Doc Center</a> for the working solution).</p>
<p>For IE and FF &lt; 4, set <code>window.event.returnValue</code> to the string you wish to display, and then return it for Safari (use <code>null</code> instead to allow normal behavior):</p>
<pre><code>window.onbeforeunload = function (e) {
    var e = e || window.event;

    // For IE and Firefox prior to version 4
    if (e) {
        e.returnValue = 'Any string';
    }

    // For Safari
    return 'Any string';
};</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2011-03-04/catching-the-javascript-beforeunload-event-the-cross-browser-way/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2011-03-04/catching-the-javascript-beforeunload-event-the-cross-browser-way/</feedburner:origLink></item>
		<item>
		<title>Three ways to tell if your are running PHP 5.3</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/T2qL3QTF1ds/</link>
		<comments>http://jonathonhill.net/2010-12-01/how-to-tell-if-your-are-running-php-5-3/#comments</comments>
		<pubDate>Wed, 01 Dec 2010 19:28:35 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP 5.3]]></category>
		<category><![CDATA[PHP_VERSION]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=240</guid>
		<description><![CDATA[A quick-n-dirty way If all you want to do is see if you are running PHP 5.3+, then just check for the existence of the array_replace() function, which was added in PHP 5.3: &#60;?php if(function_exists('array_replace')) { // running PHP 5.3+ } else { // running something prior to PHP 5.3 } ?&#62; The &#8220;right way&#8221; [...]]]></description>
			<content:encoded><![CDATA[<h2>A quick-n-dirty way</h2>
<p>If all you want to do is see if you are running PHP 5.3+, then just check for the existence of the <a href="http://php.net/array_replace" target="_blank"><code>array_replace()</code></a> function, which was added in PHP 5.3:</p>
<pre class="php"><code>&lt;?php
if(function_exists('array_replace')) {
    // running PHP 5.3+
} else {
    // running something prior to PHP 5.3
}
?&gt;</code></pre>
<h2>The &#8220;right way&#8221;</h2>
<p>The <a href="http://www.php.net/manual/en/function.version-compare.php" target="_blank"><code>version_compare()</code></a> method can be used in conjunction with the <code>PHP_VERSION</code> constant to compare standardized PHP version strings. It also makes for more readable code:</p>
<pre class="php"><code>if(version_compare(PHP_VERSION, '5.3.0') &gt;= 0) {
    echo 'I am at least PHP version 5.3.0, my version: ' . PHP_VERSION . "\n";
}
</code></pre>
<h2>Version constants</h2>
<p>If you need to get down to the nitty gritty specifics of the PHP version you are running, use the <code>PHP_VERSION_ID</code>, <code>PHP_MAJOR_VERSION</code>, <code>PHP_MINOR_VERSION</code>, and <code>PHP_RELEASE_VERSION</code> constants, which were <a href="http://www.php.net/manual/en/reserved.constants.php#reserved.constants.core" target="_blank">added in PHP 5.2.7</a>. To ensure backward compatibility, the following <a href="http://php.net/manual/en/function.phpversion.php#Examples" target="_blank">code snippet from php.net</a> will define these constants if they are undefined in your PHP version:</p>
<pre class="php"><code>&lt;?php
// PHP_VERSION_ID is available as of PHP 5.2.7, if our
// version is lower than that, then emulate it
if (!defined('PHP_VERSION_ID')) {
    $version = explode('.', PHP_VERSION);
    define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2]));
}

// PHP_VERSION_ID is defined as a number, where the higher the number
// is, the newer a PHP version is used. It's defined as used in the above
// expression:
//
// $version_id = $major_version * 10000 + $minor_version * 100 + $release_version;
//
// Now with PHP_VERSION_ID we can check for features this PHP version
// may have, this doesn't require to use version_compare() everytime
// you check if the current PHP version may not support a feature.
//
// For example, we may here define the PHP_VERSION_* constants thats
// not available in versions prior to 5.2.7

if (PHP_VERSION_ID &lt; 50207) {
    define('PHP_MAJOR_VERSION',   $version[0]);
    define('PHP_MINOR_VERSION',   $version[1]);
    define('PHP_RELEASE_VERSION', $version[2]);
    // and so on, ...
}
?&gt;</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2010-12-01/how-to-tell-if-your-are-running-php-5-3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2010-12-01/how-to-tell-if-your-are-running-php-5-3/</feedburner:origLink></item>
		<item>
		<title>How to send e-mail…that gets delivered</title>
		<link>http://feedproxy.google.com/~r/JonathonHill/~3/xoYILsyLqtY/</link>
		<comments>http://jonathonhill.net/2010-06-16/how-to-send-e-mail-that-gets-delivered/#comments</comments>
		<pubDate>Thu, 17 Jun 2010 00:47:01 +0000</pubDate>
		<dc:creator>Jonathon</dc:creator>
				<category><![CDATA[Notebook]]></category>
		<category><![CDATA[dkim]]></category>
		<category><![CDATA[dns]]></category>
		<category><![CDATA[E-Mail]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[Jeff Atwood]]></category>
		<category><![CDATA[ptr]]></category>
		<category><![CDATA[spam]]></category>

		<guid isPermaLink="false">http://jonathonhill.net/?p=236</guid>
		<description><![CDATA[I just got a really good education on how to make sure your (legit) email will navigate common spam blockers and be delivered successfully, thanks to Jeff Atwood. Summary Make sure the computer sending the email has a Reverse PTR record. Your ISP has to do it, not your DNS provider or web host. Sign [...]]]></description>
			<content:encoded><![CDATA[<p>I just got a really good education on how to make sure your (legit) email will navigate common spam blockers and be delivered successfully, <a title="So You'd Like to Send Some Email (Through Code)" href="http://www.codinghorror.com/blog/2010/04/so-youd-like-to-send-some-email-through-code.html" target="_blank">thanks to Jeff Atwood</a>.</p>
<h2>Summary</h2>
<ol>
<li><strong>Make sure the computer sending the email has a <a href="http://aplawrence.com/Blog/B961.html" target="_blank">Reverse PTR record</a></strong>. Your ISP has to do it, not your DNS provider or web host.</li>
<li><strong>Sign your messages using <a href="http://en.wikipedia.org/wiki/DKIM" target="_blank">DomainKeys Identified Mail</a></strong>. Requires DNS and code changes.</li>
<li><strong>Set up a <a href="http://en.wikipedia.org/wiki/Sender_ID" target="_blank">SenderID DNS record</a>.</strong> Far less critical than the first two, but still nice to have.</li>
</ol>
<h2>Did it work?</h2>
<ol>
<li><strong>Send a message to a GMail account</strong>&#8211;they provide excellent diagnostic headers. Look for <code>Received-SPF</code> and <code>Authentication-Results</code>.</li>
<li><strong>Use the <a href="mailto:check-auth@verifier.port25.com">Port25 diagnostic service</a></strong> (check-auth@verifier.port25.com). You can ignore a DomainKeys check fail if the DKIM check passes.</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://jonathonhill.net/2010-06-16/how-to-send-e-mail-that-gets-delivered/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://jonathonhill.net/2010-06-16/how-to-send-e-mail-that-gets-delivered/</feedburner:origLink></item>
	</channel>
</rss>

