tag:blogger.com,1999:blog-74496686459558186752024-02-20T05:31:14.273+01:00Patrick Allaert's Blog about Free SoftwarePatrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.comBlogger21125tag:blogger.com,1999:blog-7449668645955818675.post-37628368850785020562013-03-08T18:14:00.001+01:002013-03-08T18:19:30.566+01:00Benchmarking Composer autoloading, Symfony and eZ Publish 5<h2>Context</h2>
One fine day, I decided to benchmark how fast can <a class="reference external" href="https://github.com/ezsystems/ezpublish-community/">eZ Publish 5</a>, and the underlying stack, serve requests without using a true <a class="reference external" href="http://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a>, in which case I would benchmark the reverse proxy and not the PHP implementation that lies behind the product.<br />
What I did in the first place, is installing a fresh version of <a class="reference external" href="https://github.com/ezsystems/ezpublish-community/">eZ Publish 5</a> directly from GitHub (commit ID: <a class="reference external" href="https://github.com/ezsystems/ezpublish-community/commit/3af1f1ad78264f9b36eb250a14ce4301d556e7ab">3af1f1ad78264f9b36eb250a14ce4301d556e7ab</a>), setting up a site based on <a class="reference external" href="https://github.com/ezsystems/ezdemo">ezdemo</a> and take some base results:<br />
<pre class="literal-block">$ sudo ab -k -n 1000 http://ezpublish-community/eng/
[...]
Requests per second: 31.38 [#/sec] (mean)
Time per request: 31.870 [ms] (mean)
Time per request: 31.870 [ms] (mean, across all concurrent requests)
Transfer rate: 1181.94 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 30 32 2.7 32 109
Waiting: 28 30 2.6 30 106
Total: 30 32 2.7 32 109
</pre>
About <strong>32 reqs/second</strong> is what I can expect from a stock <a class="reference external" href="https://github.com/ezsystems/ezpublish-community/">eZ Publish 5</a> installation of <a class="reference external" href="https://github.com/ezsystems/ezdemo">ezdemo</a>.<br />
The first thing to speed this up is to activate the <strong>content view caching</strong> using TTL, this is done by adding:<br />
<pre class="literal-block">eng:
content:
view_cache: true
ttl_cache: true
default_ttl: 30
</pre>
under the configuration of the siteaccess (here: "eng") in the <tt class="docutils literal">ezpublish/config/ezpublish.yml</tt> file.<br />
After I cleaned the cache manually (<tt class="docutils literal">$ rm <span class="pre">-rf</span> ezpublish/cache/prod/*</tt>), I ran the benchmark again:<br />
<pre class="literal-block">$ sudo ab -k -n 1000 http://ezpublish-community/eng/
[...]
Requests per second: 213.50 [#/sec] (mean)
Time per request: 4.684 [ms] (mean)
Time per request: 4.684 [ms] (mean, across all concurrent requests)
Transfer rate: 8030.29 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 4 5 0.2 5 7
Waiting: 4 4 0.2 4 6
Total: 4 5 0.2 5 7
</pre>
This is of course a very impressive win since it allows caching the content of the whole page without having to connect to the database. Again, a real proxy server would outperform this by <em>far</em>, but this isn't the goal of this article which focus more on the PHP parts.<br />
<strong>213 reqs/second</strong> is certainly not bad, but let's analyze where the time is spent to see if this can be improved further.
To diagnose where the possible bottlenecks are, I am going to use <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a>, that gives me the following numbers:<br />
<table border="1" class="docutils">
<colgroup>
<col width="81%"></col>
<col width="19%"></col>
</colgroup>
<tbody valign="top">
<tr><td>Total Incl. Wall Time (µs):</td>
<td>6,510</td>
</tr>
<tr><td>Number of Function Calls:</td>
<td>2,700</td>
</tr>
</tbody>
</table>
<table border="1" class="docutils">
<colgroup>
<col width="60%"></col>
<col width="5%"></col>
<col width="6%"></col>
<col width="9%"></col>
<col width="6%"></col>
<col width="9%"></col>
<col width="6%"></col>
</colgroup>
<thead valign="bottom">
<tr><th class="head">Function Name</th>
<th class="head">Calls</th>
<th class="head">Calls%</th>
<th class="head">Incl. Wall
Time (µs)</th>
<th class="head">IWall%</th>
<th class="head">Excl. Wall
Time (µs)</th>
<th class="head">EWall%</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>load::prod/classes.php</td>
<td>1</td>
<td>0.0%</td>
<td>518</td>
<td>8.0%</td>
<td>518</td>
<td>8.0%</td>
</tr>
<tr><td>Composer\Autoload\ClassLoader::findFile</td>
<td>17</td>
<td>0.6%</td>
<td>389</td>
<td>6.0%</td>
<td>308</td>
<td>4.7%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ResponseHeaderBag::set</td>
<td>41</td>
<td>1.5%</td>
<td>630</td>
<td>9.7%</td>
<td>263</td>
<td>4.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::set</td>
<td>59</td>
<td>2.2%</td>
<td>303</td>
<td>4.7%</td>
<td>237</td>
<td>3.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ServerBag::getHeaders</td>
<td>4</td>
<td>0.1%</td>
<td>231</td>
<td>3.5%</td>
<td>230</td>
<td>3.5%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::get</td>
<td>48</td>
<td>1.8%</td>
<td>194</td>
<td>3.0%</td>
<td>194</td>
<td>3.0%</td>
</tr>
<tr><td>run_init::composer/autoload_classmap.php</td>
<td>1</td>
<td>0.0%</td>
<td>157</td>
<td>2.4%</td>
<td>157</td>
<td>2.4%</td>
</tr>
<tr><td>run_init::prod/classes.php</td>
<td>1</td>
<td>0.0%</td>
<td>136</td>
<td>2.1%</td>
<td>130</td>
<td>2.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::normalizeQueryString</td>
<td>12</td>
<td>0.4%</td>
<td>127</td>
<td>2.0%</td>
<td>120</td>
<td>1.8%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::has</td>
<td>45</td>
<td>1.7%</td>
<td>119</td>
<td>1.8%</td>
<td>119</td>
<td>1.8%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ParameterBag::get</td>
<td>92</td>
<td>3.4%</td>
<td>114</td>
<td>1.8%</td>
<td>114</td>
<td>1.8%</td>
</tr>
<tr><td>main()</td>
<td>1</td>
<td>0.0%</td>
<td>6,51</td>
<td>100.0%</td>
<td>112</td>
<td>1.7%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareBaseUrl</td>
<td>4</td>
<td>0.1%</td>
<td>462</td>
<td>7.1%</td>
<td>111</td>
<td>1.7%</td>
</tr>
<tr><td>file_get_contents</td>
<td>6</td>
<td>0.2%</td>
<td>89</td>
<td>1.4%</td>
<td>89</td>
<td>1.4%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::prepare</td>
<td>4</td>
<td>0.1%</td>
<td>154</td>
<td>2.4%</td>
<td>87</td>
<td>1.3%</td>
</tr>
<tr><td>DateTime::__construct</td>
<td>12</td>
<td>0.4%</td>
<td>84</td>
<td>1.3%</td>
<td>84</td>
<td>1.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::__construct</td>
<td>8</td>
<td>0.3%</td>
<td>635</td>
<td>9.8%</td>
<td>81</td>
<td>1.2%</td>
</tr>
<tr><td>ComposerAutoloaderInit5b5f57c871b8c5b6e57138c3683a3315::getLoader</td>
<td>1</td>
<td>0.0%</td>
<td>399</td>
<td>6.1%</td>
<td>81</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::sendContent</td>
<td>1</td>
<td>0.0%</td>
<td>80</td>
<td>1.2%</td>
<td>80</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::parseCacheControl</td>
<td>12</td>
<td>0.4%</td>
<td>127</td>
<td>2.0%</td>
<td>70</td>
<td>1.1%</td>
</tr>
<tr><td>file_exists</td>
<td>17</td>
<td>0.6%</td>
<td>70</td>
<td>1.1%</td>
<td>70</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::create</td>
<td>3</td>
<td>0.1%</td>
<td>420</td>
<td>6.5%</td>
<td>69</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\Store::lookup</td>
<td>4</td>
<td>0.1%</td>
<td>1,198</td>
<td>18.4%</td>
<td>66</td>
<td>1.0%</td>
</tr>
<tr><td>DateTime::createFromFormat</td>
<td>8</td>
<td>0.3%</td>
<td>61</td>
<td>0.9%</td>
<td>61</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::getAge</td>
<td>11</td>
<td>0.4%</td>
<td>343</td>
<td>5.3%</td>
<td>61</td>
<td>0.9%</td>
</tr>
<tr><td>preg_match_all</td>
<td>12</td>
<td>0.4%</td>
<td>57</td>
<td>0.9%</td>
<td>57</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::handle@1</td>
<td>2</td>
<td>0.1%</td>
<td>2,351</td>
<td>36.1%</td>
<td>56</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::isSecure</td>
<td>16</td>
<td>0.6%</td>
<td>87</td>
<td>1.3%</td>
<td>55</td>
<td>0.8%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareRequestUri</td>
<td>4</td>
<td>0.1%</td>
<td>250</td>
<td>3.8%</td>
<td>53</td>
<td>0.8%</td>
</tr>
<tr><td>Symfony\Component\ClassLoader\ClassCollectionLoader::load</td>
<td>1</td>
<td>0.0%</td>
<td>827</td>
<td>12.7%</td>
<td>52</td>
<td>0.8%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::initialize</td>
<td>4</td>
<td>0.1%</td>
<td>398</td>
<td>6.1%</td>
<td>52</td>
<td>0.8%</td>
</tr>
<tr><td>get_declared_classes</td>
<td>1</td>
<td>0.0%</td>
<td>51</td>
<td>0.8%</td>
<td>51</td>
<td>0.8%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::getHost</td>
<td>8</td>
<td>0.3%</td>
<td>112</td>
<td>1.7%</td>
<td>51</td>
<td>0.8%</td>
</tr>
</tbody>
</table>
Note that the initial benchmark was done without <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a> loaded so that the performances are optimal. This is why you can see request time about 6ms with <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a>, while the average is in reality about 4.684ms. This is due to <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a>'s own overhead.<br />
The first thing to note is that the most time consuming operation (based on "Excl. Wall Time" which doesn't take into account the time spent in children calls) is: <tt class="docutils literal"><span class="pre">load::prod/classes.php</span></tt> with about 8%. This file is supposed to contain the most frequently used classes combined in one file to minimize stats calls when a PHP bytecode cache mechanism is used like <a class="reference external" href="http://www.php.net/manual/book.apc.php">APC</a> or <a class="reference external" href="https://github.com/zend-dev/ZendOptimizerPlus">Zend Optimizer+</a>. This is triggered by: <tt class="docutils literal"><span class="pre">$kernel->loadClassCache();</span></tt> in <tt class="docutils literal">web/index.php</tt> file.<br />
Removing that call and benchmarking the page again, here is the result I get:<br />
<pre class="literal-block">$ sudo ab -k -n 1000 http://ezpublish-community/eng/
[...]
Requests per second: 254.53 [#/sec] (mean)
Time per request: 3.929 [ms] (mean)
Time per request: 3.929 [ms] (mean, across all concurrent requests)
Transfer rate: 9573.73 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 4 4 0.2 4 5
Waiting: 3 4 0.2 4 5
Total: 4 4 0.2 4 5
</pre>
The improvement from <strong>213.50 to 254.53 requests/sec.</strong> by removing the call to <tt class="docutils literal">loadClassCache()</tt> means that by including this file we are loading way more classes than what is necessary to serve a cached page. This can probably be tuned, but since we just have better performance by omitting this optimization, let's just continue without it.<br />
Running <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a> again, those are the new profiling numbers.<br />
<table border="1" class="docutils">
<colgroup>
<col width="81%"></col>
<col width="19%"></col>
</colgroup>
<tbody valign="top">
<tr><td>Total Incl. Wall Time (µs):</td>
<td>6,061</td>
</tr>
<tr><td>Number of Function Calls:</td>
<td>2,784</td>
</tr>
</tbody>
</table>
<table border="1" class="docutils">
<colgroup>
<col width="64%"></col>
<col width="4%"></col>
<col width="5%"></col>
<col width="8%"></col>
<col width="5%"></col>
<col width="8%"></col>
<col width="5%"></col>
</colgroup>
<thead valign="bottom">
<tr><th class="head">Function Name</th>
<th class="head">Calls</th>
<th class="head">Calls%</th>
<th class="head">Incl. Wall
Time (µs)</th>
<th class="head">IWall%</th>
<th class="head">Excl. Wall
Time (µs)</th>
<th class="head">EWall%</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>Composer\Autoload\ClassLoader::findFile</td>
<td>23</td>
<td>0.8%</td>
<td>468</td>
<td>7.7%</td>
<td>394</td>
<td>6.5%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ResponseHeaderBag::set</td>
<td>41</td>
<td>1.5%</td>
<td>647</td>
<td>10.7%</td>
<td>261</td>
<td>4.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::set</td>
<td>59</td>
<td>2.1%</td>
<td>310</td>
<td>5.1%</td>
<td>245</td>
<td>4.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ServerBag::getHeaders</td>
<td>4</td>
<td>0.1%</td>
<td>239</td>
<td>3.9%</td>
<td>238</td>
<td>3.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::get</td>
<td>48</td>
<td>1.7%</td>
<td>208</td>
<td>3.4%</td>
<td>200</td>
<td>3.3%</td>
</tr>
<tr><td>run_init::composer/autoload_classmap.php</td>
<td>1</td>
<td>0.0%</td>
<td>149</td>
<td>2.5%</td>
<td>149</td>
<td>2.5%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::has</td>
<td>45</td>
<td>1.6%</td>
<td>135</td>
<td>2.2%</td>
<td>135</td>
<td>2.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::normalizeQueryString</td>
<td>12</td>
<td>0.4%</td>
<td>140</td>
<td>2.3%</td>
<td>129</td>
<td>2.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareBaseUrl</td>
<td>4</td>
<td>0.1%</td>
<td>513</td>
<td>8.5%</td>
<td>114</td>
<td>1.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ParameterBag::get</td>
<td>92</td>
<td>3.3%</td>
<td>111</td>
<td>1.8%</td>
<td>110</td>
<td>1.8%</td>
</tr>
<tr><td>main()</td>
<td>1</td>
<td>0.0%</td>
<td>6,061</td>
<td>100.0%</td>
<td>106</td>
<td>1.7%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::prepare</td>
<td>4</td>
<td>0.1%</td>
<td>182</td>
<td>3.0%</td>
<td>90</td>
<td>1.5%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::__construct</td>
<td>8</td>
<td>0.3%</td>
<td>665</td>
<td>11.0%</td>
<td>88</td>
<td>1.5%</td>
</tr>
<tr><td>file_get_contents</td>
<td>6</td>
<td>0.2%</td>
<td>86</td>
<td>1.4%</td>
<td>86</td>
<td>1.4%</td>
</tr>
<tr><td>DateTime::__construct</td>
<td>12</td>
<td>0.4%</td>
<td>84</td>
<td>1.4%</td>
<td>84</td>
<td>1.4%</td>
</tr>
<tr><td>ComposerAutoloaderInit5b5f57c871b8c5b6e57138c3683a3315::getLoader</td>
<td>1</td>
<td>0.0%</td>
<td>372</td>
<td>6.1%</td>
<td>78</td>
<td>1.3%</td>
</tr>
<tr><td>Composer\Autoload\ClassLoader::loadClass</td>
<td>13</td>
<td>0.5%</td>
<td>1,022</td>
<td>16.9%</td>
<td>75</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::parseCacheControl</td>
<td>12</td>
<td>0.4%</td>
<td>128</td>
<td>2.1%</td>
<td>74</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::sendContent</td>
<td>1</td>
<td>0.0%</td>
<td>71</td>
<td>1.2%</td>
<td>71</td>
<td>1.2%</td>
</tr>
<tr><td>file_exists</td>
<td>23</td>
<td>0.8%</td>
<td>71</td>
<td>1.2%</td>
<td>71</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::initialize</td>
<td>4</td>
<td>0.1%</td>
<td>586</td>
<td>9.7%</td>
<td>69</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\Store::lookup</td>
<td>4</td>
<td>0.1%</td>
<td>1,380</td>
<td>22.8%</td>
<td>68</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::create</td>
<td>3</td>
<td>0.1%</td>
<td>414</td>
<td>6.8%</td>
<td>66</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::getAge</td>
<td>11</td>
<td>0.4%</td>
<td>339</td>
<td>5.6%</td>
<td>61</td>
<td>1.0%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::handle@1</td>
<td>2</td>
<td>0.1%</td>
<td>2,512</td>
<td>41.4%</td>
<td>60</td>
<td>1.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::isSecure</td>
<td>16</td>
<td>0.6%</td>
<td>90</td>
<td>1.5%</td>
<td>58</td>
<td>1.0%</td>
</tr>
<tr><td>DateTime::createFromFormat</td>
<td>8</td>
<td>0.3%</td>
<td>57</td>
<td>0.9%</td>
<td>57</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::getHost</td>
<td>8</td>
<td>0.3%</td>
<td>125</td>
<td>2.1%</td>
<td>56</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareRequestUri</td>
<td>4</td>
<td>0.1%</td>
<td>281</td>
<td>4.6%</td>
<td>54</td>
<td>0.9%</td>
</tr>
<tr><td>preg_match_all</td>
<td>12</td>
<td>0.4%</td>
<td>54</td>
<td>0.9%</td>
<td>54</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::getCacheControlHeader</td>
<td>7</td>
<td>0.3%</td>
<td>66</td>
<td>1.1%</td>
<td>52</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::handle</td>
<td>1</td>
<td>0.0%</td>
<td>4,172</td>
<td>68.8%</td>
<td>50</td>
<td>0.8%</td>
</tr>
<tr><td>eZ\Publish\Core\MVC\Symfony\Cache\Http\LocationAwareStore::getPath</td>
<td>8</td>
<td>0.3%</td>
<td>99</td>
<td>1.6%</td>
<td>50</td>
<td>0.8%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::restoreResponseBody@1</td>
<td>2</td>
<td>0.1%</td>
<td>1,047</td>
<td>17.3%</td>
<td>49</td>
<td>0.8%</td>
</tr>
<tr><td>load::HttpFoundation/Request.php</td>
<td>1</td>
<td>0.0%</td>
<td>47</td>
<td>0.8%</td>
<td>47</td>
<td>0.8%</td>
</tr>
</tbody>
</table>
Those numbers shows us that <a class="reference external" href="http://www.php.net/manual/language.oop5.autoload.php">autoloading</a> is one of the part that takes the most time and made me remember about the <tt class="docutils literal">web/index.php</tt> file containing some <a class="reference external" href="http://www.php.net/manual/book.apc.php">APC</a> related commented code:<br />
<pre class="literal-block">// Use APC for autoloading to improve performance:
// Change 'ezpublish' to a unique prefix in order to prevent cache key conflicts
// with other applications also using APC.
//
// ( Not needed when using `php composer.phar dump-autoload --optimize` )
/*
$loader = new ApcClassLoader( 'ezpublish', $loader );
$loader->register( true );
*/
</pre>
Reading at this comment, I realize I should also give a try to the <tt class="docutils literal"><span class="pre">--optimize</span></tt> flag proposed by <a class="reference external" href="http://getcomposer.org/">Composer</a>. Let's try both approaches.
<h2>Approach 1: Activating ApcClassLoader</h2>
After uncommenting the relevant lines in web/index.php file, I run the benchmark again:<br />
<pre class="literal-block">$ sudo ab -k -n 1000 http://ezpublish-community/eng/
[...]
Requests per second: 270.42 [#/sec] (mean)
Time per request: 3.698 [ms] (mean)
Time per request: 3.698 [ms] (mean, across all concurrent requests)
Transfer rate: 10171.41 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 3 4 0.2 4 5
Waiting: 3 3 0.2 3 4
Total: 3 4 0.2 4 5
</pre>
Performing that simple change <strong>decreased the request time by about 6%</strong>, let's look at <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a>'s data again:<br />
<table border="1" class="docutils">
<colgroup>
<col width="81%"></col>
<col width="19%"></col>
</colgroup>
<tbody valign="top">
<tr><td>Total Incl. Wall Time (µs):</td>
<td>5,696</td>
</tr>
<tr><td>Number of Function Calls:</td>
<td>2,549</td>
</tr>
</tbody>
</table>
<table border="1" class="docutils">
<colgroup>
<col width="60%"></col>
<col width="5%"></col>
<col width="6%"></col>
<col width="9%"></col>
<col width="6%"></col>
<col width="9%"></col>
<col width="6%"></col>
</colgroup>
<thead valign="bottom">
<tr><th class="head">Function Name</th>
<th class="head">Calls</th>
<th class="head">Calls%</th>
<th class="head">Incl. Wall
Time (µs)</th>
<th class="head">IWall%</th>
<th class="head">Excl. Wall
Time (µs)</th>
<th class="head">EWall%</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>Symfony\Component\HttpFoundation\ServerBag::getHeaders</td>
<td>4</td>
<td>0.2%</td>
<td>278</td>
<td>4.9%</td>
<td>269</td>
<td>4.7%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ResponseHeaderBag::set</td>
<td>41</td>
<td>1.6%</td>
<td>627</td>
<td>11.0%</td>
<td>249</td>
<td>4.4%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::set</td>
<td>59</td>
<td>2.3%</td>
<td>307</td>
<td>5.4%</td>
<td>241</td>
<td>4.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::get</td>
<td>48</td>
<td>1.9%</td>
<td>187</td>
<td>3.3%</td>
<td>187</td>
<td>3.3%</td>
</tr>
<tr><td>run_init::composer/autoload_classmap.php</td>
<td>1</td>
<td>0.0%</td>
<td>164</td>
<td>2.9%</td>
<td>164</td>
<td>2.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::has</td>
<td>45</td>
<td>1.8%</td>
<td>133</td>
<td>2.3%</td>
<td>129</td>
<td>2.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::normalizeQueryString</td>
<td>12</td>
<td>0.5%</td>
<td>132</td>
<td>2.3%</td>
<td>119</td>
<td>2.1%</td>
</tr>
<tr><td>main()</td>
<td>1</td>
<td>0.0%</td>
<td>5,696</td>
<td>100.0%</td>
<td>116</td>
<td>2.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareBaseUrl</td>
<td>4</td>
<td>0.2%</td>
<td>469</td>
<td>8.2%</td>
<td>116</td>
<td>2.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ParameterBag::get</td>
<td>92</td>
<td>3.6%</td>
<td>111</td>
<td>1.9%</td>
<td>111</td>
<td>1.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::__construct</td>
<td>8</td>
<td>0.3%</td>
<td>644</td>
<td>11.3%</td>
<td>89</td>
<td>1.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::prepare</td>
<td>4</td>
<td>0.2%</td>
<td>167</td>
<td>2.9%</td>
<td>87</td>
<td>1.5%</td>
</tr>
<tr><td>file_get_contents</td>
<td>6</td>
<td>0.2%</td>
<td>87</td>
<td>1.5%</td>
<td>87</td>
<td>1.5%</td>
</tr>
<tr><td>DateTime::__construct</td>
<td>12</td>
<td>0.5%</td>
<td>86</td>
<td>1.5%</td>
<td>86</td>
<td>1.5%</td>
</tr>
<tr><td>ComposerAutoloaderInit155d41b24b236d0fd561c62d5063d8bb::getLoader</td>
<td>1</td>
<td>0.0%</td>
<td>400</td>
<td>7.0%</td>
<td>84</td>
<td>1.5%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::parseCacheControl</td>
<td>12</td>
<td>0.5%</td>
<td>129</td>
<td>2.3%</td>
<td>72</td>
<td>1.3%</td>
</tr>
<tr><td>Symfony\Component\ClassLoader\ApcClassLoader::loadClass</td>
<td>12</td>
<td>0.5%</td>
<td>633</td>
<td>11.1%</td>
<td>72</td>
<td>1.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::sendContent</td>
<td>1</td>
<td>0.0%</td>
<td>71</td>
<td>1.2%</td>
<td>71</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\Store::lookup</td>
<td>4</td>
<td>0.2%</td>
<td>1,313</td>
<td>23.1%</td>
<td>70</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::create</td>
<td>3</td>
<td>0.1%</td>
<td>452</td>
<td>7.9%</td>
<td>69</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::initialize</td>
<td>4</td>
<td>0.2%</td>
<td>561</td>
<td>9.8%</td>
<td>69</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::getAge</td>
<td>11</td>
<td>0.4%</td>
<td>341</td>
<td>6.0%</td>
<td>63</td>
<td>1.1%</td>
</tr>
<tr><td>load::HttpFoundation/Request.php</td>
<td>1</td>
<td>0.0%</td>
<td>59</td>
<td>1.0%</td>
<td>59</td>
<td>1.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::isSecure</td>
<td>16</td>
<td>0.6%</td>
<td>90</td>
<td>1.6%</td>
<td>58</td>
<td>1.0%</td>
</tr>
<tr><td>DateTime::createFromFormat</td>
<td>8</td>
<td>0.3%</td>
<td>58</td>
<td>1.0%</td>
<td>58</td>
<td>1.0%</td>
</tr>
<tr><td>Composer\Autoload\ClassLoader::findFile</td>
<td>2</td>
<td>0.1%</td>
<td>68</td>
<td>1.2%</td>
<td>57</td>
<td>1.0%</td>
</tr>
<tr><td>preg_match_all</td>
<td>12</td>
<td>0.5%</td>
<td>57</td>
<td>1.0%</td>
<td>57</td>
<td>1.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::getHost</td>
<td>8</td>
<td>0.3%</td>
<td>114</td>
<td>2.0%</td>
<td>55</td>
<td>1.0%</td>
</tr>
</tbody>
</table>
From those numbers, we can see that <a class="reference external" href="http://getcomposer.org/">Composer</a>'s methods that appeared previously in the topmost part of the most costing operations have disappeared<br />
Now is time to compare this with the suggested second approach.
<h2>Approach 2: Using Composer classmap generation</h2>
<a class="reference external" href="http://getcomposer.org/">Composer</a> has a feature that generates a classmap of all classes by running:<br />
<pre class="literal-block">$ ./composer.phar dump-autoload --optimize
</pre>
This command created a 662KiB <tt class="docutils literal">vendor/composer/autoload_classmap.php</tt> file containing an array that is a hash composed of the class name as index and the path to the file containing the class definition as value. At the time I am writing this post, this array is composed of 4168 entries.<br />
Benchmarking again to see how it improves the average request time:<br />
<pre class="literal-block">$ sudo ab -k -n 1000 http://ezpublish-community/eng/
[...]
Requests per second: 197.95 [#/sec] (mean)
Time per request: 5.052 [ms] (mean)
Time per request: 5.052 [ms] (mean, across all concurrent requests)
Transfer rate: 7445.35 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 5 5 4.0 5 131
Waiting: 4 5 3.8 4 126
Total: 5 5 4.0 5 131
</pre>
Although it should give us the most efficiant <a class="reference external" href="http://www.php.net/manual/language.oop5.autoload.php">autoloading</a> mechanism, it actually <strong>slows things down (from 254.53 reqs/second to 197.95)</strong>. The reason being that even if the file is cached by <a class="reference external" href="http://www.php.net/manual/book.apc.php">APC</a>, the PHP array containing the map with more than 4100 entries needs to be re-created at every single request. This is easily discovered looking at <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a> run report:<br />
<table border="1" class="docutils">
<colgroup>
<col width="81%"></col>
<col width="19%"></col>
</colgroup>
<tbody valign="top">
<tr><td>Total Incl. Wall Time (µs):</td>
<td>6,169</td>
</tr>
<tr><td>Number of Function Calls:</td>
<td>2,480</td>
</tr>
</tbody>
</table>
<table border="1" class="docutils">
<colgroup>
<col width="60%"></col>
<col width="5%"></col>
<col width="6%"></col>
<col width="9%"></col>
<col width="6%"></col>
<col width="9%"></col>
<col width="6%"></col>
</colgroup>
<thead valign="bottom">
<tr><th class="head">Function Name</th>
<th class="head">Calls</th>
<th class="head">Calls%</th>
<th class="head">Incl. Wall
Time (µs)</th>
<th class="head">IWall%</th>
<th class="head">Excl. Wall
Time (µs)</th>
<th class="head">EWall%</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>run_init::composer/autoload_classmap.php</td>
<td>1</td>
<td>0.0%</td>
<td>775</td>
<td>12.6%</td>
<td>775</td>
<td>12.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ServerBag::getHeaders</td>
<td>4</td>
<td>0.2%</td>
<td>260</td>
<td>4.2%</td>
<td>258</td>
<td>4.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ResponseHeaderBag::set</td>
<td>41</td>
<td>1.7%</td>
<td>616</td>
<td>10.0%</td>
<td>248</td>
<td>4.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::set</td>
<td>59</td>
<td>2.4%</td>
<td>303</td>
<td>4.9%</td>
<td>237</td>
<td>3.8%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::get</td>
<td>48</td>
<td>1.9%</td>
<td>187</td>
<td>3.0%</td>
<td>187</td>
<td>3.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::has</td>
<td>45</td>
<td>1.8%</td>
<td>122</td>
<td>2.0%</td>
<td>122</td>
<td>2.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::normalizeQueryString</td>
<td>12</td>
<td>0.5%</td>
<td>133</td>
<td>2.2%</td>
<td>118</td>
<td>1.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareBaseUrl</td>
<td>4</td>
<td>0.2%</td>
<td>499</td>
<td>8.1%</td>
<td>115</td>
<td>1.9%</td>
</tr>
<tr><td>main()</td>
<td>1</td>
<td>0.0%</td>
<td>6,169</td>
<td>100.0%</td>
<td>112</td>
<td>1.8%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ParameterBag::get</td>
<td>92</td>
<td>3.7%</td>
<td>114</td>
<td>1.8%</td>
<td>111</td>
<td>1.8%</td>
</tr>
<tr><td>file_get_contents</td>
<td>6</td>
<td>0.2%</td>
<td>97</td>
<td>1.6%</td>
<td>97</td>
<td>1.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::__construct</td>
<td>8</td>
<td>0.3%</td>
<td>629</td>
<td>10.2%</td>
<td>86</td>
<td>1.4%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::prepare</td>
<td>4</td>
<td>0.2%</td>
<td>156</td>
<td>2.5%</td>
<td>85</td>
<td>1.4%</td>
</tr>
<tr><td>ComposerAutoloaderInit4fe043dafc1de5ca2f306c3fb5c6bd90::getLoader</td>
<td>1</td>
<td>0.0%</td>
<td>1,012</td>
<td>16.4%</td>
<td>82</td>
<td>1.3%</td>
</tr>
<tr><td>DateTime::__construct</td>
<td>12</td>
<td>0.5%</td>
<td>80</td>
<td>1.3%</td>
<td>80</td>
<td>1.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::sendContent</td>
<td>1</td>
<td>0.0%</td>
<td>77</td>
<td>1.2%</td>
<td>77</td>
<td>1.2%</td>
</tr>
<tr><td>Composer\Autoload\ClassLoader::loadClass</td>
<td>13</td>
<td>0.5%</td>
<td>593</td>
<td>9.6%</td>
<td>75</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::parseCacheControl</td>
<td>12</td>
<td>0.5%</td>
<td>129</td>
<td>2.1%</td>
<td>72</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::create</td>
<td>3</td>
<td>0.1%</td>
<td>434</td>
<td>7.0%</td>
<td>68</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\Store::lookup</td>
<td>4</td>
<td>0.2%</td>
<td>1,296</td>
<td>21.0%</td>
<td>66</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::initialize</td>
<td>4</td>
<td>0.2%</td>
<td>535</td>
<td>8.7%</td>
<td>66</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::getAge</td>
<td>11</td>
<td>0.4%</td>
<td>342</td>
<td>5.5%</td>
<td>63</td>
<td>1.0%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::handle@1</td>
<td>2</td>
<td>0.1%</td>
<td>2,384</td>
<td>38.6%</td>
<td>58</td>
<td>0.9%</td>
</tr>
<tr><td>DateTime::createFromFormat</td>
<td>8</td>
<td>0.3%</td>
<td>58</td>
<td>0.9%</td>
<td>58</td>
<td>0.9%</td>
</tr>
<tr><td>preg_match_all</td>
<td>12</td>
<td>0.5%</td>
<td>57</td>
<td>0.9%</td>
<td>57</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::isSecure</td>
<td>16</td>
<td>0.6%</td>
<td>89</td>
<td>1.4%</td>
<td>57</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareRequestUri</td>
<td>4</td>
<td>0.2%</td>
<td>268</td>
<td>4.3%</td>
<td>54</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::getHost</td>
<td>8</td>
<td>0.3%</td>
<td>129</td>
<td>2.1%</td>
<td>53</td>
<td>0.9%</td>
</tr>
<tr><td>load::HttpFoundation/Request.php</td>
<td>1</td>
<td>0.0%</td>
<td>48</td>
<td>0.8%</td>
<td>48</td>
<td>0.8%</td>
</tr>
</tbody>
</table>
Knowing that most of the classes involved in the stack relies on <a class="reference external" href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md">PSR-0</a> let's try a simpler approach...
<h2>Simplifying the autoloading</h2>
<a class="reference external" href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md">PSR-0</a> gives us a nice "fully qualified classname to filename" approach but things gets a bit more complicated given that the different dependencies installed by <a class="reference external" href="http://getcomposer.org/">Composer</a> may have common namespaces (sub)paths and all of them starting from a different root directory.<br />
Using Linux, I thought about leveraging my filesystem abilities and <a class="reference external" href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md">PSR-0</a> at the same time: let's create a tree made of symbolink links to recreate a straightforward "fully qualified classname to filename" strategy again:<br />
<pre class="literal-block">$ mkdir vendor/PSR-0
$ cd vendor/PSR-0
$ ln -s ../kriswallsmith/assetic/src/Assetic/ ../ezsystems/ezpublish-kernel/eZ/ ../ezsystems/demobundle/EzSystems/ ../monolog/monolog/src/Monolog/ ../psr/log/Psr/ ../sensio/generator-bundle/Sensio/ ../twig/twig/lib/Twig/ ../qafoo/rmf/src/main/Qafoo/ .
$ mkdir Symfony
$ cd Symfony
$ ln -s ../../symfony/symfony/src/Symfony/Bridge/ ../../../vendor/symfony-cmf/routing/Symfony/Cmf/ ../../symfony/symfony/src/Symfony/Component/ .
$ mkdir Bundle
$ cd Bundle
$ ln -s ../../../symfony/assetic-bundle/Symfony/Bundle/AsseticBundle/ ../../../symfony/symfony/src/Symfony/Bundle/FrameworkBundle/ ../../../symfony/monolog-bundle/Symfony/Bundle/MonologBundle/ ../../../symfony/symfony/src/Symfony/Bundle/SecurityBundle/ ../../../symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle/ ../../../symfony/symfony/src/Symfony/Bundle/TwigBundle/ ../../../symfony/symfony/src/Symfony/Bundle/WebProfilerBundle/ .
</pre>
... and modify the autoloader file (ezpublish/autoload.php) to contain only:<br />
<pre class="literal-block">spl_autoload_register(
function ( $className )
{
if (
strpos( $className, "Symfony\\" ) === 0 ||
strpos( $className, "Sensio\\" ) === 0 ||
strpos( $className, "eZ\\" ) === 0 ||
strpos( $className, "EzSystems\\" ) === 0 ||
strpos( $className, "Monolog\\" ) === 0 ||
strpos( $className, "Psr\\" ) === 0 ||
strpos( $className, "Twig_" ) === 0 ||
strpos( $className, "Assetic\\" ) === 0
)
{
@include __DIR__ . "/../vendor/PSR-0/" . str_replace( array( "_", "\\" ), "/", $className ) . ".php";
}
},
true
);
require __DIR__ . "/../ezpublish_legacy/autoload.php";
</pre>
Now that this is in place, let's benchmark it:<br />
<pre class="literal-block">$ sudo ab -k -n 1000 http://ezpublish-community/eng/
[...]
Requests per second: 286.38 [#/sec] (mean)
Time per request: 3.492 [ms] (mean)
Time per request: 3.492 [ms] (mean, across all concurrent requests)
Transfer rate: 11286.49 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 3 3 0.2 3 6
Waiting: 3 3 0.2 3 5
Total: 3 3 0.2 3 6
</pre>
Using this technique, we gained yet another 16 requests/sec over ApcClassLoader that was the best results so far. Let's inspect <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a> once again:<br />
<table border="1" class="docutils">
<colgroup>
<col width="61%"></col>
<col width="5%"></col>
<col width="5%"></col>
<col width="9%"></col>
<col width="5%"></col>
<col width="9%"></col>
<col width="5%"></col>
</colgroup>
<thead valign="bottom">
<tr><th class="head">Function Name</th>
<th class="head">Calls</th>
<th class="head">Calls%</th>
<th class="head">Incl. Wall
Time (µs)</th>
<th class="head">IWall%</th>
<th class="head">Excl. Wall
Time (µs)</th>
<th class="head">EWall%</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>Symfony\Component\HttpFoundation\ResponseHeaderBag::set</td>
<td>42</td>
<td>1.7%</td>
<td>669</td>
<td>11.8%</td>
<td>267</td>
<td>4.7%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ServerBag::getHeaders</td>
<td>4</td>
<td>0.2%</td>
<td>262</td>
<td>4.6%</td>
<td>259</td>
<td>4.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::set</td>
<td>60</td>
<td>2.4%</td>
<td>307</td>
<td>5.4%</td>
<td>243</td>
<td>4.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::get</td>
<td>48</td>
<td>1.9%</td>
<td>204</td>
<td>3.6%</td>
<td>204</td>
<td>3.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::has</td>
<td>45</td>
<td>1.8%</td>
<td>134</td>
<td>2.4%</td>
<td>129</td>
<td>2.3%</td>
</tr>
<tr><td>{closure}</td>
<td>15</td>
<td>0.6%</td>
<td>713</td>
<td>12.6%</td>
<td>126</td>
<td>2.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::normalizeQueryString</td>
<td>12</td>
<td>0.5%</td>
<td>131</td>
<td>2.3%</td>
<td>119</td>
<td>2.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ParameterBag::get</td>
<td>92</td>
<td>3.6%</td>
<td>118</td>
<td>2.1%</td>
<td>118</td>
<td>2.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareBaseUrl</td>
<td>4</td>
<td>0.2%</td>
<td>479</td>
<td>8.5%</td>
<td>116</td>
<td>2.1%</td>
</tr>
<tr><td>main()</td>
<td>1</td>
<td>0.0%</td>
<td>5,646</td>
<td>100.0%</td>
<td>110</td>
<td>1.9%</td>
</tr>
<tr><td>DateTime::createFromFormat</td>
<td>8</td>
<td>0.3%</td>
<td>103</td>
<td>1.8%</td>
<td>103</td>
<td>1.8%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::prepare</td>
<td>4</td>
<td>0.2%</td>
<td>171</td>
<td>3.0%</td>
<td>97</td>
<td>1.7%</td>
</tr>
<tr><td>DateTime::__construct</td>
<td>12</td>
<td>0.5%</td>
<td>96</td>
<td>1.7%</td>
<td>96</td>
<td>1.7%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::__construct</td>
<td>8</td>
<td>0.3%</td>
<td>678</td>
<td>12.0%</td>
<td>92</td>
<td>1.6%</td>
</tr>
<tr><td>file_get_contents</td>
<td>6</td>
<td>0.2%</td>
<td>88</td>
<td>1.6%</td>
<td>88</td>
<td>1.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::parseCacheControl</td>
<td>12</td>
<td>0.5%</td>
<td>137</td>
<td>2.4%</td>
<td>71</td>
<td>1.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::create</td>
<td>3</td>
<td>0.1%</td>
<td>434</td>
<td>7.7%</td>
<td>70</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\Store::lookup</td>
<td>4</td>
<td>0.2%</td>
<td>1,347</td>
<td>23.9%</td>
<td>70</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::initialize</td>
<td>4</td>
<td>0.2%</td>
<td>549</td>
<td>9.7%</td>
<td>68</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::getAge</td>
<td>11</td>
<td>0.4%</td>
<td>411</td>
<td>7.3%</td>
<td>66</td>
<td>1.2%</td>
</tr>
<tr><td>preg_match_all</td>
<td>12</td>
<td>0.5%</td>
<td>66</td>
<td>1.2%</td>
<td>66</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::sendContent</td>
<td>1</td>
<td>0.0%</td>
<td>63</td>
<td>1.1%</td>
<td>63</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::getCacheControlHeader</td>
<td>7</td>
<td>0.3%</td>
<td>75</td>
<td>1.3%</td>
<td>62</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::handle@1</td>
<td>2</td>
<td>0.1%</td>
<td>2,500</td>
<td>44.3%</td>
<td>61</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::isSecure</td>
<td>16</td>
<td>0.6%</td>
<td>93</td>
<td>1.6%</td>
<td>60</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareRequestUri</td>
<td>4</td>
<td>0.2%</td>
<td>259</td>
<td>4.6%</td>
<td>53</td>
<td>0.9%</td>
</tr>
<tr><td>load::HttpFoundation/Request.php</td>
<td>1</td>
<td>0.0%</td>
<td>52</td>
<td>0.9%</td>
<td>52</td>
<td>0.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::getHost</td>
<td>8</td>
<td>0.3%</td>
<td>115</td>
<td>2.0%</td>
<td>51</td>
<td>0.9%</td>
</tr>
</tbody>
</table>
It looks like from the latest technique used, we finally get rid of the <a class="reference external" href="http://www.php.net/manual/language.oop5.autoload.php">autoloading</a> performance impact. Maybe <a class="reference external" href="http://getcomposer.org/">Composer</a> could automate the creation of such a <a class="reference external" href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md">PSR-0</a> tree? Well, if you care, this has been asked already ;-) <a class="reference external" href="https://github.com/composer/composer/issues/1529">https://github.com/composer/composer/issues/1529</a><br />
From the latest <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a> report, it seems that Symfony HttpFoundation components are the next bottlenecks.
<h2>Some craziness: s/Symfony/Cymfony/</h2>
Now, what if... those Symfony components were built in C? Well, the idea sounds a bit crazy at first but I started creating a simple PHP extension written in C (that I named "Cymfony") that implements natively the <tt class="docutils literal"><span class="pre">Symfony\\Component\\HttpFoundation\\HeaderBag</span></tt> and <tt class="docutils literal"><span class="pre">Symfony\\Component\\HttpFoundation\\ParameterBag</span></tt> classes. Those were simple enough to be written quickly while appearing in the <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a> report.<br />
Let's activate the extension and run yet another benchmark:<br />
<pre class="literal-block">$ sudo ab -k -n 1000 http://ezpublish-community/eng/
[...]
Requests per second: 328.93 [#/sec] (mean)
Time per request: 3.040 [ms] (mean)
Time per request: 3.040 [ms] (mean, across all concurrent requests)
Transfer rate: 12963.84 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 3 3 0.2 3 5
Waiting: 3 3 0.2 3 4
Total: 3 3 0.2 3 5
</pre>
This is a pretty good result! By reimplementing classes natively, with 100% compatibility, I have been able to jump from <strong>286.38 request/sec to 328.93</strong> which is about a 15% boost. This percentage is very close to the original amount of time spent in the classes in their PHP implementation:<br />
<table border="1" class="docutils">
<colgroup>
<col width="91%"></col>
<col width="9%"></col>
</colgroup>
<tbody valign="top">
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::set</td>
<td>4.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::get</td>
<td>3.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::has</td>
<td>2.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ParameterBag::get</td>
<td>2.1%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::__construct</td>
<td>1.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::parseCacheControl</td>
<td>1.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::getCacheControlHeader</td>
<td>1.1%</td>
</tr>
<tr><td></td>
<td>17.4%</td>
</tr>
</tbody>
</table>
Let's see if <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a> confirms those methods are not being the top most consuming parts:<br />
<table border="1" class="docutils">
<colgroup>
<col width="64%"></col>
<col width="4%"></col>
<col width="5%"></col>
<col width="8%"></col>
<col width="5%"></col>
<col width="8%"></col>
<col width="5%"></col>
</colgroup>
<thead valign="bottom">
<tr><th class="head">Function Name</th>
<th class="head">Calls</th>
<th class="head">Calls%</th>
<th class="head">Incl. Wall
Time (µs)</th>
<th class="head">IWall%</th>
<th class="head">Excl. Wall
Time (µs)</th>
<th class="head">EWall%</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>Symfony\Component\HttpFoundation\ResponseHeaderBag::set</td>
<td>42</td>
<td>2.3%</td>
<td>436</td>
<td>9.2%</td>
<td>283</td>
<td>6.0%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\ServerBag::getHeaders</td>
<td>4</td>
<td>0.2%</td>
<td>255</td>
<td>5.4%</td>
<td>253</td>
<td>5.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareBaseUrl</td>
<td>4</td>
<td>0.2%</td>
<td>396</td>
<td>8.3%</td>
<td>129</td>
<td>2.7%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::normalizeQueryString</td>
<td>12</td>
<td>0.7%</td>
<td>134</td>
<td>2.8%</td>
<td>125</td>
<td>2.6%</td>
</tr>
<tr><td>{closure}</td>
<td>13</td>
<td>0.7%</td>
<td>591</td>
<td>12.4%</td>
<td>119</td>
<td>2.5%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::prepare</td>
<td>4</td>
<td>0.2%</td>
<td>123</td>
<td>2.6%</td>
<td>112</td>
<td>2.4%</td>
</tr>
<tr><td>main()</td>
<td>1</td>
<td>0.1%</td>
<td>4,756</td>
<td>100.0%</td>
<td>111</td>
<td>2.3%</td>
</tr>
<tr><td>file_get_contents</td>
<td>6</td>
<td>0.3%</td>
<td>95</td>
<td>2.0%</td>
<td>95</td>
<td>2.0%</td>
</tr>
<tr><td>DateTime::__construct</td>
<td>12</td>
<td>0.7%</td>
<td>89</td>
<td>1.9%</td>
<td>89</td>
<td>1.9%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::set</td>
<td>42</td>
<td>2.3%</td>
<td>80</td>
<td>1.7%</td>
<td>80</td>
<td>1.7%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::create</td>
<td>3</td>
<td>0.2%</td>
<td>380</td>
<td>8.0%</td>
<td>77</td>
<td>1.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::__construct</td>
<td>8</td>
<td>0.4%</td>
<td>400</td>
<td>8.4%</td>
<td>76</td>
<td>1.6%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::initialize</td>
<td>4</td>
<td>0.2%</td>
<td>421</td>
<td>8.9%</td>
<td>70</td>
<td>1.5%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\Store::lookup</td>
<td>4</td>
<td>0.2%</td>
<td>1,121</td>
<td>23.6%</td>
<td>69</td>
<td>1.5%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::getAge</td>
<td>11</td>
<td>0.6%</td>
<td>257</td>
<td>5.4%</td>
<td>67</td>
<td>1.4%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::prepareRequestUri</td>
<td>4</td>
<td>0.2%</td>
<td>197</td>
<td>4.1%</td>
<td>67</td>
<td>1.4%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\HeaderBag::getDate</td>
<td>8</td>
<td>0.4%</td>
<td>66</td>
<td>1.4%</td>
<td>66</td>
<td>1.4%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::isSecure</td>
<td>16</td>
<td>0.9%</td>
<td>63</td>
<td>1.3%</td>
<td>63</td>
<td>1.3%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::sendContent</td>
<td>1</td>
<td>0.1%</td>
<td>63</td>
<td>1.3%</td>
<td>63</td>
<td>1.3%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::handle@1</td>
<td>2</td>
<td>0.1%</td>
<td>2,049</td>
<td>43.1%</td>
<td>59</td>
<td>1.2%</td>
</tr>
<tr><td>load::HttpFoundation/Request.php</td>
<td>1</td>
<td>0.1%</td>
<td>55</td>
<td>1.2%</td>
<td>55</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Request::getHost</td>
<td>8</td>
<td>0.4%</td>
<td>90</td>
<td>1.9%</td>
<td>55</td>
<td>1.2%</td>
</tr>
<tr><td>Symfony\Component\HttpFoundation\Response::getMaxAge</td>
<td>10</td>
<td>0.6%</td>
<td>95</td>
<td>2.0%</td>
<td>54</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::restoreResponseBody@1</td>
<td>2</td>
<td>0.1%</td>
<td>885</td>
<td>18.6%</td>
<td>51</td>
<td>1.1%</td>
</tr>
<tr><td>Symfony\Component\HttpKernel\HttpCache\HttpCache::handle</td>
<td>1</td>
<td>0.1%</td>
<td>3,460</td>
<td>72.8%</td>
<td>49</td>
<td>1.0%</td>
</tr>
<tr><td>load::HttpKernel/Kernel.php</td>
<td>1</td>
<td>0.1%</td>
<td>49</td>
<td>1.0%</td>
<td>49</td>
<td>1.0%</td>
</tr>
<tr><td>eZ\Publish\Core\MVC\Symfony\Cache\Http\LocationAwareStore::getPath</td>
<td>8</td>
<td>0.4%</td>
<td>87</td>
<td>1.8%</td>
<td>48</td>
<td>1.0%</td>
</tr>
</tbody>
</table>
The rewritten classes are effectively more efficient and doesn't take as much processing time. This is encouraging to try reimplementing classes like <tt class="docutils literal"><span class="pre">Symfony\\Component\\HttpFoundation\\ResponseHeaderBag</span></tt>, <tt class="docutils literal"><span class="pre">Symfony\\Component\\HttpFoundation\\ServerBag</span></tt> or <tt class="docutils literal"><span class="pre">Symfony\\Component\\HttpFoundation\\Request</span></tt>. Those are, however, much more time consuming to implement in C.
<h2>Conclusion</h2>
In my <a class="reference external" href="http://patrickallaert.blogspot.be/2013/01/speeding-up-class-autoloading-with.html">previous blog post</a> I was suggesting using a PSR-0 tree already, but I lacked some stats, this issue is now solved.<br />
Spending some time tracking the bottlenecks can drastically improve the throughput, <a class="reference external" href="http://www.php.net/manual/language.oop5.autoload.php">autoloading</a> being frequently one of them. Hopefully, we do have <a class="reference external" href="http://xdebug.org/">Xdebug</a> and <a class="reference external" href="https://github.com/facebook/xhprof">xhprof</a> that are two great tools to profile a PHP application.<br />
The Cymfony project that I started as a proof-of-concept might, in fact, interest some people working with <a class="reference external" href="http://en.wikipedia.org/wiki/Reverse_proxy">Symfony 2</a> and that want to optimize some parts of the framework just by installing a PHP extension. However, speeding up the path of delivering cached page is probably not the best use case because if someone is able to install a PHP extension, he is probably able to install a decent <a class="reference external" href="http://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a> which would speed up the throughput much more! What are the classes that might be worth being optimized in <a class="reference external" href="http://en.wikipedia.org/wiki/Reverse_proxy">Symfony 2</a> according to you?
Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com6tag:blogger.com,1999:blog-7449668645955818675.post-43405504393345795362013-01-26T12:46:00.001+01:002013-01-27T11:30:19.675+01:00Composer: Speeding up class autoloading<h2>
The Use Case</h2>
Those of you using <a href="http://getcomposer.org/">Composer</a> have probably tried speeding up the <a href="http://www.php.net/manual/en/language.oop5.autoload.php">PHP autoloading</a> mechanism using the option:<br />
<blockquote class="tr_bq">
<strong>--optimize-autoloader (-o):</strong> Convert PSR-0 autoloading to classmap to get a faster autoloader. This is recommended especially for production, but can take a bit of time to run so it is currently not done by default.</blockquote>
<h2>
The Goal</h2>
While it can certainly make your autoloading faster it can be further optimized if you are using <a href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md">PSR-0</a>-like autoloading mechanism and have quite a lot of classes.<br />
<h2>
The Problem</h2>
The problem with the classmap strategy and the nature of <a href="http://www.php.net/">PHP</a> is that there is no (easy) way to have a persistent variable across requests containing the classmap. Even if you are using <a href="http://pecl.php.net/package/APC">APC</a>, a PHP file returning a big array will always take some CPU cycles and memory because the array still needs to be recreated from the cached opcodes. This can even take a big portion of your request's response time when you have hundreds or thousands of classes like it is the case with <a href="http://share.ez.no/">eZ Publish 5</a> being based on <a href="http://symfony.com/">Symfony</a>, where about <b>2 600 classes are involved</b>.<br />
<h2>
The Solution</h2>
<div>
I came across a quite simple solution that can surely be automatized from inside Composer itself.</div>
<div>
Given that most dependencies of eZ Publish 5 are using the <b>PSR-0 standard</b>, I created a <i>vendor/PSR-0</i> directory containing symbolic links to all relevant elements in such a way that there is a direct match between a namespaced class and the file system from a unique root directory:<br />
<br />
<pre>$ tree vendor/PSR-0/
vendor/PSR-0/
├── Assetic -> ../kriswallsmith/assetic/src/Assetic/
├── eZ -> ../ezsystems/ezpublish/eZ/
├── EzSystems -> ../../src/EzSystems/
├── Monolog -> ../monolog/monolog/src/Monolog/
├── Psr -> ../psr/log/Psr
├── Sensio -> ../sensio/generator-bundle/Sensio/
├── Symfony
│ ├── Bridge -> ../../symfony/symfony/src/Symfony/Bridge/
│ ├── Bundle
│ │ ├── AsseticBundle -> ../../../symfony/assetic-bundle/Symfony/Bundle/AsseticBundle/
│ │ ├── FrameworkBundle -> ../../../symfony/symfony/src/Symfony/Bundle/FrameworkBundle/
│ │ ├── MonologBundle -> ../../../symfony/monolog-bundle/Symfony/Bundle/MonologBundle/
│ │ ├── SecurityBundle -> ../../../symfony/symfony/src/Symfony/Bundle/SecurityBundle/
│ │ ├── SwiftmailerBundle -> ../../../symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle/
│ │ ├── TwigBundle -> ../../../symfony/symfony/src/Symfony/Bundle/TwigBundle/
│ │ └── WebProfilerBundle -> ../../../symfony/symfony/src/Symfony/Bundle/WebProfilerBundle/
│ ├── Cmf -> ../../../vendor/symfony-cmf/routing/Symfony/Cmf/
│ └── Component -> ../../symfony/symfony/src/Symfony/Component/
└── Twig -> ../twig/twig/lib/Twig/</pre>
</div>
<br />
Once this has been setup, instead of using the usual Composer's <i>autoload.php</i> file, a custom autoloader is created for loading classes directly from that tree:<br />
<br />
<pre>spl_autoload_register(
function ( $className )
{
if (
strpos( $className, "Symfony\\" ) === 0 ||
strpos( $className, "Sensio\\" ) === 0 ||
strpos( $className, "eZ\\" ) === 0 ||
strpos( $className, "EzSystems\\" ) === 0 ||
strpos( $className, "Monolog\\" ) === 0 ||
strpos( $className, "Psr\\" ) === 0 ||
strpos( $className, "Twig_" ) === 0 ||
strpos( $className, "Assetic\\" ) === 0
)
{
if (
file_exists(
$path = __DIR__ . "/../vendor/PSR-0/" . str_replace( array( "_", "\\" ), "/", $className ) . ".php"
)
)
{
require $path;
}
}
else
{
return;
}
},
true
);</pre>
<h2>
Conclusion</h2>
<div>
Once I did that change on a typical eZ Publish site configured with ezdemo package, my laptop was able to serve <b>227 reqs/second instead of the initial 176 reqs/second</b> that is a <b>29%</b> boost.</div>
<div>
<br /></div>
<h2>
Action point</h2>
<div>
<b>Composer team</b>: any chance to be able to generate this automagically? :-)</div>
Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com15tag:blogger.com,1999:blog-7449668645955818675.post-52544784308448595342010-05-27T10:37:00.001+02:002010-05-27T11:23:42.691+02:00Readable PHP code #2 Make your API handle more!<h4>Context</h4><p>APIs are often designed to operate with scalars. As described in the following example, the function <em>addContact()</em> operates on a single element:</p><code> <span style="color: #0000BB"><?php<br /> </span><span style="color: #007700">class </span><span style="color: #0000BB">User </span><span style="color: #007700">{<br /> protected </span><span style="color: #0000BB">$contacts </span><span style="color: #007700">= array();<br /> <br /> function </span><span style="color: #0000BB">addContact</span><span style="color: #007700">(</span><span style="color: #0000BB">$contact</span><span style="color: #007700">) {<br /> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">contacts</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$contact</span><span style="color: #007700">;<br /> }<br /> }<br /> </span><span style="color: #0000BB">?><br /> </span> </code> <p>In a context where many contacts have to be added using the above API, code usually looks like:</p><code> <span style="color: #0000BB"><?php<br /> </span><span style="color: #FF8000">// Some contacts<br /> </span><span style="color: #0000BB">$contacts </span><span style="color: #007700">= array(</span><span style="color: #DD0000">"Paul"</span><span style="color: #007700">, </span><span style="color: #DD0000">"John"</span><span style="color: #007700">, </span><span style="color: #DD0000">"Maria"</span><span style="color: #007700">);<br /> <br /> </span><span style="color: #0000BB">$user </span><span style="color: #007700">= new </span><span style="color: #0000BB">User</span><span style="color: #007700">();<br /> </span><span style="color: #FF8000">// Looping over contacts to add them<br /> </span><span style="color: #007700">foreach (</span><span style="color: #0000BB">$contacts </span><span style="color: #007700">as </span><span style="color: #0000BB">$contact</span><span style="color: #007700">) {<br /> </span><span style="color: #0000BB">$user</span><span style="color: #007700">-></span><span style="color: #0000BB">addContact</span><span style="color: #007700">(</span><span style="color: #0000BB">$contact</span><span style="color: #007700">);<br /> }<br /> </span><span style="color: #0000BB">?><br /> </span> </code> <h4>Inner looping</h4><p>A way to avoid repeating this looping everywhere would be to design the API to work with array of contacts:</p><code> <span style="color: #a4a4eb"><?php<br /> </span><span style="color: #74e874">class </span><span style="color: #a4a4eb">User </span><span style="color: #74e874">{<br /> protected </span><span style="color: #a4a4eb">$contacts </span><span style="color: #74e874">= array();<br /> <br /> </span><span style="color: #007700">function </span><span style="color: #0000BB">addContacts</span><span style="color: #007700">(</span><span style="color: #0000BB">$contacts</span><span style="color: #007700">) {<br /> </span><span style="color: #007700">foreach (</span><span style="color: #0000BB">$contacts </span><span style="color: #007700">as </span><span style="color: #0000BB">$contact</span><span style="color: #007700">) {<br /> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">contacts</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$contact</span><span style="color: #007700">;<br /> }<br /> }</span><span style="color: #74e874"><br /> }<br /> </span><span style="color: #a4a4eb">?><br /> </span> </code> <p>This might make the code, where loops are in use, somewhat clearer:</p><code> <span style="color: #a4a4eb"><?php<br /> </span><span style="color: #ffd9b2">// Some contacts<br /> </span><span style="color: #a4a4eb">$contacts </span><span style="color: #74e874">= array(</span><span style="color: #eaa4a4">"Paul"</span><span style="color: #74e874">, </span><span style="color: #eaa4a4">"John"</span><span style="color: #74e874">, </span><span style="color: #eaa4a4">"Maria"</span><span style="color: #74e874">);<br /> <br /> </span><span style="color: #a4a4eb">$user </span><span style="color: #74e874">= new </span><span style="color: #a4a4eb">User</span><span style="color: #74e874">();<br /> </span><span style="color: #0000BB">$user</span><span style="color: #007700">-></span><span style="color: #0000BB">addContacts</span><span style="color: #007700">(</span><span style="color: #0000BB">$contacts</span><span style="color: #007700">);<br /> </span><span style="color: #a4a4eb">?><br /> </span> </code> <p>With the benefit of speeding the processing a little bit as only one function call is issued!</p><p>The side effect is that adding only one contact is not as elegant:</p><code> <span style="color: #a4a4eb"><?php<br /> </span><span style="color: #0000BB">$contact </span><span style="color: #007700">= </span><span style="color: #DD0000">"Julia"</span><span style="color: #007700">;<br /> <br /> </span><span style="color: #a4a4eb">$user </span><span style="color: #74e874">= new </span><span style="color: #a4a4eb">User</span><span style="color: #74e874">();<br /> </span><span style="color: #0000BB">$user</span><span style="color: #007700">-></span><span style="color: #0000BB">addContacts</span><span style="color: #007700">(array(</span><span style="color: #0000BB">$contact</span><span style="color: #007700">));<br /> </span><span style="color: #a4a4eb">?><br /> </span> </code> <h4>A nice PHP trick</h4><p>The good news is that PHP provides a nice array cast operator: <em>(array)</em> which will transform a scalar value into an array. As described on the manual page about <a href="http://www.php.net/manual/en/language.types.array.php#language.types.array.casting">arrays</a>:</p><blockquote cite="http://www.php.net/manual/en/language.types.array.php#language.types.array.casting"><p>"For any of the types: <em>integer</em>, <em>float</em>, <em>string</em>, <em>boolean</em> and <em>resource</em>, converting a value to an array results in an array with a single element with index zero and the value of the scalar which was converted. In other words, <em>(array)$scalarValue</em> is exactly the same as <em>array($scalarValue)</em>."</p></blockquote><p>Previous example can be transformed to play nicely with both scalars and arrays:</p><code> <span style="color: #a4a4eb"><?php<br /> </span><span style="color: #74e874">class </span><span style="color: #a4a4eb">User </span><span style="color: #74e874">{<br /> protected </span><span style="color: #a4a4eb">$contacts </span><span style="color: #74e874">= array();<br /> <br /> function </span><span style="color: #a4a4eb">addContacts</span><span style="color: #74e874">(</span><span style="color: #a4a4eb">$contacts</span><span style="color: #74e874">) {<br /> foreach (</span><span style="color: #007700">(array) </span><span style="color: #a4a4eb">$contacts </span><span style="color: #74e874">as </span><span style="color: #a4a4eb">$contact</span><span style="color: #74e874">) {<br /> </span><span style="color: #a4a4eb">$this</span><span style="color: #74e874">-></span><span style="color: #a4a4eb">contacts</span><span style="color: #74e874">[] = </span><span style="color: #a4a4eb">$contact</span><span style="color: #74e874">;<br /> }<br /> }<br /> }<br /> </span><span style="color: #a4a4eb">?><br /> </span> </code> <p>Function <em>addContacts()</em> can now be used the following way with scalars:</p><code> <span style="color: #a4a4eb"><?php<br /> $contact </span><span style="color: #74e874">= </span><span style="color: #eaa4a4">"Julia"</span><span style="color: #74e874">;<br /> <br /> </span><span style="color: #a4a4eb">$user </span><span style="color: #74e874">= new </span><span style="color: #a4a4eb">User</span><span style="color: #74e874">();<br /> </span><span style="color: #a4a4eb">$user</span><span style="color: #74e874">-></span><span style="color: #a4a4eb">addContacts</span><span style="color: #74e874">(</span><span style="color: #0000BB">$contact</span><span style="color: #74e874">);<br /> </span><span style="color: #a4a4eb">?><br /> </span> </code> <h4>OOPs!</h4><p>This is a nice trick to define APIs to be used with both scalars and arrays, however this will not work when using objects! PHP is able to cast an objects as an array, but this will give you an access to its properties which is not the intended purpose.</p><p>If your contacts are objects you will have to modify the <em>addContacts()</em> function to something like:</p><code> <span style="color: #a4a4eb"><?php<br /> </span><span style="color: #74e874">class </span><span style="color: #a4a4eb">User </span><span style="color: #74e874">{<br /> protected </span><span style="color: #a4a4eb">$contacts </span><span style="color: #74e874">= array();<br /> <br /> function </span><span style="color: #a4a4eb">addContacts</span><span style="color: #74e874">(</span><span style="color: #a4a4eb">$contacts</span><span style="color: #74e874">) {<br /> </span><span style="color: #007700">if (</span><span style="color: #0000BB">is_array</span><span style="color: #007700">(</span><span style="color: #0000BB">$contacts</span><span style="color: #007700">)) {<br /> foreach (</span><span style="color: #0000BB">$contacts </span><span style="color: #007700">as </span><span style="color: #0000BB">$contact</span><span style="color: #007700">) {<br /> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">contacts</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$contact</span><span style="color: #007700">;<br /> }<br /> } else {<br /> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">contacts</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$contacts</span><span style="color: #007700">;<br /> }<br /> </span><span style="color: #74e874">}<br /> }<br /> </span><span style="color: #a4a4eb">?><br /> </span> </code> <p>This may not be as elegant as the array casting method. However it will enable your API to work seemlessly with both scalars and arrays when objects are used.</p><p>The benefit of working with an array capable API is that you might sometimes optimize the operations. For example, in the case of retrieving or deleting elements from a database, you might want to use the "WHERE id IN (...)" syntax to match multiple elements rather than one by one. In the X examples that has been used in this article, an interesting optimization is to use the native array_merge() function which avoids reinventing the wheel by looping over elements using a foreach construct and adding elements one by one:</p><code> <span style="color: #a4a4eb"><?php<br /> </span><span style="color: #74e874">class </span><span style="color: #a4a4eb">User </span><span style="color: #74e874">{<br /> protected </span><span style="color: #a4a4eb">$contacts </span><span style="color: #74e874">= array();<br /> <br /> function </span><span style="color: #a4a4eb">addContacts</span><span style="color: #74e874">(</span><span style="color: #a4a4eb">$contacts</span><span style="color: #74e874">) {<br /> if (</span><span style="color: #a4a4eb">is_array</span><span style="color: #74e874">(</span><span style="color: #a4a4eb">$contacts</span><span style="color: #74e874">)) {<br /> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">contacts </span><span style="color: #007700">= </span><span style="color: #0000BB">array_merge</span><span style="color: #007700">(</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">contacts</span><span style="color: #007700">, </span><span style="color: #0000BB">$contacts</span><span style="color: #007700">);<br /> </span><span style="color: #74e874">} else {<br /> </span><span style="color: #a4a4eb">$this</span><span style="color: #74e874">-></span><span style="color: #a4a4eb">contacts</span><span style="color: #74e874">[] = </span><span style="color: #a4a4eb">$contacts</span><span style="color: #74e874">;<br /> }<br /> }<br /> }<br /> </span><span style="color: #a4a4eb">?><br /> </span> </code> <h4>Conclusion</h4><p>In this article we have seen the advantages of creating an API which can handle multiple elements at once. If there is a benefit in terms of speed (which heavely depends on your business logic), don't forget that code readability is of higher importance too! Hopefully, this tip should improve both</p><p>For those who mind about performance, doing:</p><code> <span style="color: #0000BB"><?php<br /> </span><span style="color: #FF8000">// Adding 5.000.000 contacts<br /> </span><span style="color: #0000BB">$user</span><span style="color: #007700">-></span><span style="color: #0000BB">addContacts</span><span style="color: #007700">(</span><span style="color: #0000BB">range</span><span style="color: #007700">(</span><span style="color: #0000BB">1</span><span style="color: #007700">, </span><span style="color: #0000BB">5e6</span><span style="color: #007700">));<br /> </span><span style="color: #0000BB">?><br /> </span> </code> <p>is about 40% faster than:</p><code> <span style="color: #0000BB"><?php<br /> </span><span style="color: #007700">foreach (</span><span style="color: #0000BB">range</span><span style="color: #007700">(</span><span style="color: #0000BB">1</span><span style="color: #007700">, </span><span style="color: #0000BB">5e6</span><span style="color: #007700">) as </span><span style="color: #0000BB">$contact</span><span style="color: #007700">) {<br /> </span><span style="color: #0000BB">$user</span><span style="color: #007700">-></span><span style="color: #0000BB">addContact</span><span style="color: #007700">(</span><span style="color: #0000BB">$contact</span><span style="color: #007700">);<br /> }<br /> </span><span style="color: #0000BB">?><br /> </span> </code> <p>Thanks to Paul Dragoonis, Paul Borgermans and <a href="http://jrenard.info/blog/">Jérôme Renard</a> for reviewing this article</p>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com3tag:blogger.com,1999:blog-7449668645955818675.post-16972820334841665612009-11-04T16:13:00.001+01:002009-11-04T16:14:22.653+01:00eZ Publish 4: Enterprise Web Sites Step-by-Step<div class="separator" style="clear: both; text-align: center;"><a href="http://www.packtpub.com/ez-publish-4-enterprise-web-sites-step-by-step?utm_source=patrickallaert.blogspot.com&utm_medium=bookrev&utm_content=blog&utm_campaign=mdb_001124" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="http://www.packtpub.com/images/full/1904811647.jpg" width="161" /></a><br />
</div>Today I received my copy of "<a href="http://www.packtpub.com/ez-publish-4-enterprise-web-sites-step-by-step?utm_source=patrickallaert.blogspot.com&utm_medium=bookrev&utm_content=blog&utm_campaign=mdb_001124">eZ Publish 4: Enterprise Web Sites Step-by-Step</a>" by <a href="http://www.packtpub.com/author_view_profile/id/301">Francesco Fullone</a> and <a href="http://www.packtpub.com/author_view_profile/id/300">Francesco Trucchia</a> kindly offered by <a href="http://www.packtpub.com/">Packt Publishing</a> for review.<br />
I will post my review in the following weeks on this blog, for the impatients, you way also find this <a href="http://www.stuffandcontent.com/2009/10/review-ez-publish-4-enterprise-web.html">interesting review of this book</a> by Bruce Morrison.Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com0tag:blogger.com,1999:blog-7449668645955818675.post-25382444695464964812009-10-29T14:48:00.007+01:002009-11-02T15:11:21.596+01:00Coding standards: converts PHP4 style constructors to PHP5 oneA quick way to convert all occurences of old PHP4 constructors like in:<br />
<code><span style="color: rgb(0, 0, 0);"><br />
<span style="color: rgb(0, 119, 0);">class </span><span style="color: rgb(0, 0, 187);">XYZ </span><span style="color: rgb(0, 119, 0);">{<br />
</span><span style="color: rgb(255, 128, 0);"> /**<br />
* Constructor of XYZ.<br />
*/<br />
</span><span style="color: rgb(0, 119, 0);">function </span><strong style="color: rgb(0, 0, 187);">XYZ</strong><span style="color: rgb(0, 119, 0);">() {<br />
}<br />
}<br />
</span><br />
</span></code><br />
to PHP5's __construct():<br />
<code><span style="color: rgb(0, 0, 0);"><br />
<span style="color: rgb(0, 119, 0);">class </span><span style="color: rgb(0, 0, 187);">XYZ </span><span style="color: rgb(0, 119, 0);">{<br />
</span><span style="color: rgb(255, 128, 0);"> /**<br />
* Constructor of XYZ.<br />
*/<br />
</span><span style="color: rgb(0, 119, 0);">function </span><strong style="color: rgb(0, 0, 187);">__construct</strong><span style="color: rgb(0, 119, 0);">() {<br />
}<br />
}<br />
</span><br />
</span></code><br />
is done using a quick Perl Regular Expression like in the following Linux shell command:<br />
<br />
<code>$ perl -i -e 'undef $/;while($_=<>){s/^(class\s+(\w+)\b.*^\s+function\s+)\2\b/\1__construct/gms;print $_;}' $(find -name "*.php")</code><br />
<br />
Once you have done converting your constructors definition you still may have to fix constructor calls like:<br />
<br />
<code><span style="color: rgb(0, 0, 0);"><span style="color: rgb(0, 0, 187);">MyClass</span><span style="color: rgb(0, 119, 0);">::</span><span style="color: rgb(0, 0, 187);">MyClass</span><span style="color: rgb(0, 119, 0);">();<br />
</span><span style="color: rgb(0, 0, 187);">parent</span><span style="color: rgb(0, 119, 0);">::</span><span style="color: rgb(0, 0, 187);">MyClass</span><span style="color: rgb(0, 119, 0);">();<br />
</span><span style="color: rgb(0, 0, 187);">$this</span><span style="color: rgb(0, 119, 0);">-></span><span style="color: rgb(0, 0, 187);">MyClass</span><span style="color: rgb(0, 119, 0);">();</span><br />
</span></code><br />
<br />
First of all, you need to know what classes to search for, because you would be crazy to work without a <a href="http://en.wikipedia.org/wiki/Revision_control">software revision control</a> tool (like <a href="http://en.wikipedia.org/wiki/Git_%28software%29">Git</a>, <a href="http://en.wikipedia.org/wiki/Subversion_%28software%29">SubVersion</a>, <a href="http://en.wikipedia.org/wiki/Mercurial">Mercurial</a>,...), use the <em>diff</em> output to extract the changes you just made with previous command. Next command extract class names from the SubVersion diff output:<br />
<br />
<code style="color: rgb(0, 0, 187);">$ svn diff | grep "^-[^-]" | sed -r "s/-\s*function\s*([a-zA-Z0-9_]*).*$/\1/"</code><br />
<br />
The regular expression to convert all three types of constructor call is the following one:<br />
<br />
<code style="color: rgb(0, 119, 0);">s/((?:parent|\2)::|\$this->)(Class1|Class2|Class3|...)\b/parent::__construct/g</code><br />
<br />
To embed this in the regular expression needed, we modify the output of the command with <em>echo</em> to join all the lines on one line and <em>sed</em> to replace this space separated list of classes with pipes (|):<br />
<br />
<code>$ echo '<span style="color: rgb(0, 119, 0);">s/((?:parent|\2)::|\$this->)('$(<span style="color: rgb(255, 128, 0);">echo $(<span style="color: rgb(0, 0, 187);">svn diff | grep "^-[^-]" | sed -r "s/-\s*function\s*([a-zA-Z0-9_]*).*$/\1/"</span>) | sed 's/ /|/g'</span>)')\b/parent::__construct/g'</span></code><br />
<br />
(green: regular expression, blue: command extracting class names, orange: joining lines with pipes)<br />
<br />
Last step is to use this regular expression with perl:<br />
<br />
<code>$ perl -pi -e 's/((?:parent|\2)::|\$this->)('$(echo $(svn diff | grep "^-[^-]" | sed -r "s/-\s*function\s*([a-zA-Z0-9_]*).*$/\1/") | sed 's/ /|/g')')\b/parent::__construct/g' $(find -name "*.php")</code><br />
<br />
For the one-liners out there, here is the full command you might execute (SubVersion based):<br />
<br />
<code>$ phpfiles=$(find -name "*.php") && perl -i -e 'undef $/;while($_=<>){s/^(class\s+(\w+)\b.*^\s+function\s+)\2\b/\1__construct/gms;print $_;}' $phpfiles && perl -pi -e 's/((?:parent|\2)::|\$this->)('$(echo $(svn diff | grep "^-[^-]" | sed -r "s/-\s*function\s*([a-zA-Z0-9_]*).*$/\1/") | sed 's/ /|/g')')\b/parent::__construct/g' $phpfiles</code><br />
<br />
I leave as exercise the reader to port these Linux commands to Microsoft Windows' native command shell.<br />
<br />
This article assumes your classes are always declared with the <em>class</em> keyword starting at the beginning of the line and that your files have the <em>.php</em> extension.<br />
Modify the commands to match your standards.Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com3tag:blogger.com,1999:blog-7449668645955818675.post-57833015962406949932009-08-27T20:59:00.000+02:002009-08-27T20:59:01.180+02:00Speaking at PHP Forum 2009 in ParisMid September I will be giving a talk with <a href="http://davidemendolia.blogspot.com/">Davide Mendolia</a> on <a href="http://code.google.com/p/peclapm/">Alternative PHP Monitor (APM) the PHP monitoring extension</a> we are building together, as well as on <a href="http://pinba.org/">Pinba</a> at the <a href="http://afup.org/pages/forumphp2009/">PHP Forum Paris 2009</a> organized by the <a href="http://afup.org/">AFUP</a>.<br />
See you <a href="http://afup.org/pages/forumphp2009/acces.php">there</a>.Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com0Paris, France48.8566667 2.350987148.7437227 2.1175276000000003 48.9696107 2.5844466tag:blogger.com,1999:blog-7449668645955818675.post-27288129870937314922009-03-01T21:00:00.002+01:002009-07-02T12:42:36.102+02:00MVC = Make Venerated Code?In the same spirit as my previous post entitled: "<a href="http://patrickallaert.blogspot.com/2008/06/apache-as-mvc-controller.html">Apache as an MVC controller</a>", I presented on February the 18th a session on the <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">MVC pattern</a> at the first event of <a href="http://www.phpbelgium.be/">PHPBelgium</a> in Liège.<br />The idea was to transform a simple <i>spaghetti</i> application into a well structured, MVC-aware and framework-free application with nice URL just using regular PHP and Apache's configuration.<br />You will find all the presentation materials, including source code, at: <a href="http://users.telenet.be/patrick_allaert/presentations/20090218_MVC_Make_Venerated_Code/">http://users.telenet.be/patrick_allaert/presentations/20090218_MVC_Make_Venerated_Code/</a><br />Here are the slides for the impatient:<br /><div style="width:425px;text-align:left" id="__ss_1667906"><a style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" href="http://www.slideshare.net/patrick.allaert/mvc-make-venerated-code-1667906" title="MVC = Make Venerated Code?">MVC = Make Venerated Code?</a><object style="margin:0px" width="425" height="355"><param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=mvc-090701095707-phpapp02&stripped_title=mvc-make-venerated-code-1667906" /><param name="allowFullScreen" value="true"/><param name="allowScriptAccess" value="always"/><embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=mvc-090701095707-phpapp02&stripped_title=mvc-make-venerated-code-1667906" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"></embed></object><div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;">View more <a style="text-decoration:underline;" href="http://www.slideshare.net/">documents</a> from <a style="text-decoration:underline;" href="http://www.slideshare.net/patrick.allaert">Patrick Allaert</a>.</div></div>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com0tag:blogger.com,1999:blog-7449668645955818675.post-27942773502660218232009-01-03T17:55:00.001+01:002009-01-04T00:32:40.759+01:00Seven Things - Tagged by Michelangelo van DamOuch! I've been <a href="http://www.dragonbe.com/2009/01/seven-things-tagged-by-rob-allen.html"><strike>caught</strike> tagged</a> by <a href="http://www.dragonbe.com/">Michelangelo van Dam</a>! And I don't see any exit, I just can't escape! Ok then, let's chaining... :-)<br />
<br />
Seven things you may not know about me:<br />
<ol><li>I succeed to break my legs in two different points at the age of... 9 months, before I was even able to walk.</li>
<li>I play bowling since 1990 and I scored my very first <a href="http://en.wikipedia.org/wiki/Perfect_game_%28bowling%29">perfect game (300 points)</a> a Friday the 13th!</li>
<li>I enjoy very much dancing Rock and Roll and Salsa :-)</li>
<li>My first real contribution to PHP is probably the <a href="http://code.google.com/p/peclapm/">Alternative PHP Monitor (APM)</a>.</li>
<li>I am vegetarian since the age of 15.</li>
<li>My mother started computing using... Linux. It explains a lot of things ;-)</li>
<li>I am very proud I was the 7th Zend Certified Engineer in the world and having successfully passed the <a href="http://en.wikipedia.org/wiki/Mensa_International">Mensa</a> test :-)</li>
</ol><br />
Here are the people I'd like to reference (order has no importance):<br />
<ul><li><a href="http://toys.lerdorf.com/">Rasmus Lerdorf</a>, for having made <a href="http://www.php.net/">PHP</a></li>
<li><a href="http://davidemendolia.blogspot.com/">Davide Mendolia</a>, which is a colleague I like to work with and help me developing <a href="http://code.google.com/p/peclapm/">APM</a></li>
<li><a href="http://blog.lunitechs.be/">Jeroen Serpieters</a>, a colleague with whom I share many opinions</li>
<li><a href="http://sebastian-bergmann.de/">Sebastian Bergmann</a>, for his work on <a href="http://www.phpunit.de/">PHPUnit</a></li>
<li><a href="http://derickrethans.nl/">Derick Rethans</a>, for his work on <a href="http://www.xdebug.org/">Xdebug</a>, the <a href="http://www.ezcomponents.org/">eZ Components</a> and all the <a href="http://derickrethans.nl/talks.php">interesting presentations</a> he made</li>
<li><a href="http://www.dragonbe.com/">Michelangelo van Dam</a> and <a href="http://felix.phpbelgium.be/blog/">Felix De Vliegher</a> for starting <a href="http://www.phpbelgium.be/">PHPBelgium</a></li>
<li><a href="http://davidiachetta.blogspot.com/">David Iachetta</a>, for his incredible <strike>technical sk...</strike> sense of humour :-)</li>
</ul><br />
And here are the rules I'm supposed to pass on to the above bloggers: <br />
<ul><li>Link your original tagger(s), and list these rules on your blog.</li>
<li>Share seven facts about yourself in the post - some random, some wierd.</li>
<li>Tag seven people at the end of your post by leaving their names and the links to their blogs.</li>
<li>Let them know they've been tagged by leaving a comment on their blogs and/or Twitter.</li>
</ul>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com0tag:blogger.com,1999:blog-7449668645955818675.post-12065894360359942152008-10-27T10:33:00.002+01:002008-10-27T20:31:56.220+01:00Benchmarking Zend Platform, APC and Xdebug<h2>Introduction</h2>Thanks to <a href="http://www.ausy.be/">AUSY</a>, I had the occasion to learn thoroughly the <a href="http://www.zend.com/products/platform/">Zend Platform</a> during a workshop. One thing I had to test is the performance impact of such platform and comparing this to some of their free software counterparts like <a href="http://www.xdebug.org/">Xdebug</a> and <a href="http://pecl.php.net/package/APC">APC</a> respectively for debugging and bytecode caching.<br />
<h2>Making of</h2>The benchmark has been realized on an Intel® Core™2 Duo CPU T7500 @ 2.20GHz with 2Gb of RAM running Gentoo with a 2.6.25-r7 linux kernel.<br />
<em><a href="http://httpd.apache.org/docs/2.2/programs/ab.html">ab</a></em>, the Apache Benchmark tool, has been used for the benchmark with 3000 requests and three concurrency modes: -c1, -c5 and -c50 which represents respectively 1, 5 and 50 simultaneous users.<br />
The application tested is <a href="http://ez.no/ezpublish">eZ Publish 4.0.1</a> with default configuration using the "plain_site" example.<br />
Different scenarios have been tested:<br />
<ul><li><strong>PHP</strong>: plain version of PHP 5.2.6 as released by Gentoo, this is the base that is used in the other scenarios</li>
<li><strong>PHP+APC</strong>: base PHP + APC as released under Gentoo with default configuration</li>
<li><strong>PHP+APC-stat</strong>: base PHP + APC as released under Gentoo with apc.stat="0"</li>
<li><strong>PHP+APC+Xdebug</strong>: base PHP + APC + Xdebug as released under Gentoo with default configuration</li>
<li><strong>PHP+Xdebug</strong>: base PHP + Xdebug as released under Gentoo with default configuration</li>
<li><strong>PHP+ZP-accel</strong>: base PHP + Zend Platform without acceleration (bytecode caching)</li>
<li><strong>PHP+ZP+accel</strong>: base PHP + Zend Platform with acceleration (bytecode caching)</li>
<li><strong>PHP+ZP+accel(extreme)</strong>: base PHP + Zend Platform with acceleration (bytecode caching) configured with "extreme" performance</li>
</ul><h2>The results</h2>Enough speaking, here are the results (in requests/second served):<br />
<table border="1"><tr><td></td><th>PHP</th><th>PHP+APC</th><th>PHP+APC-stat</th><th>PHP+APC+Xdebug</th><th>PHP+Xdebug</th><th>PHP+ZP-accel</th><th>PHP+ZP+accel</th><th>PHP+ZP+accel(extreme)</th></tr>
<tr><th>-c1</th><td>7.30</td><td>36.88</td><td>36.74</td><td>22.50</td><td>5.92</td><td>2.79</td><td>11.18</td><td>31.24</td></tr>
<tr><th>-c5</th><td>14.41</td><td>63.02</td><td>65.49</td><td>42.09</td><td>12.60</td><td>4.56</td><td>21.53</td><td>57.35</td></tr>
<tr><th>-c50</th><td>14.50</td><td>64.66</td><td>64.85</td><td>40.65</td><td>12.47</td><td>4.55</td><td>21.26</td><td>54.59</td></tr>
</table>Click on the next picture for a full size graph:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpIf0mg019dvjKnUqUOmKeMaJIkCrDKVUcsmpitBn5hUOb-KR-ShlsvoZeOBNTUCfultaWeVNPXAtEqRxWJfUn0XRk9D5CyD40D3XGajCC5d-TdjPZHYyHItVrLMHbeidI6bT8I9XbLw/s1600-h/benchmark.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy2JcWW2L12LZmp8r1Ydo1Pyb4ThxYrTujCTfnGk3Snozdygo4bo6SlPMYyoSovMJBYt-LNWoJJtMz0UptFXIsxDfalrcHidfsX3azFqggIHCMKgDCbNMtiSCLBHvhJhl6wnWltBIIAg/s400-r/benchmark.png" /></a></div><h2>Conclusions</h2>As expected, the usage of Xdebug and especially the Zend Platform both without a bytecode cache will dramatically decrease the performance of your system. If you are a Zend Platform user, be sure to activate the bytecode cache (Zend Accelerator)! If performance is your primary concern, then choose APC, it is <strong>much</strong> faster than Zend Accelerator even in <em>extreme</em> mode which I don't recommend because it will disable all the real benefit you can take of the Zend Platform!Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com5tag:blogger.com,1999:blog-7449668645955818675.post-66166613249362324892008-10-06T09:20:00.009+02:002008-10-17T10:06:43.936+02:00Readable PHP code #1 Return ASAP<h2>Introduction</h2><br />
This is the first article of a series I will dedicate to tips to write PHP code that is easier to maintain, review, refactor,... These tips may be applied for other languages but are mainly focused on PHP.<br />
<br />
The first one could be entitled as "return as soon as possible"™. It may be summarized as changing:<br />
<code><span style="color: #000000"><br />
<span style="color: #007700">if (</span><span style="color: #0000BB">$conditionToPerform</span><span style="color: #007700">) {<br />
</span><span style="color: #0000BB">perform</span><span style="color: #007700">();<br />
} else {<br />
return </span><span style="color: #0000BB">false</span><span style="color: #007700">;<br />
}<br />
</span></span></code><br />
into:<br />
<code><span style="color: #000000"><br />
<span style="color: #007700">if (!</span><span style="color: #0000BB">$conditionToPerform</span><span style="color: #007700">) {<br />
return </span><span style="color: #0000BB">false</span><span style="color: #007700">;<br />
}<br />
</span><span style="color: #0000BB">perform</span><span style="color: #007700">();<br />
</span></span></code><br />
The benefit of writing code this way may not be obvious, but take a closer look at the next implementations of the setPassword() function of an imaginary User class:<br />
<strong>Update: please, don't focus on the questionable usage of the exceptions here, the goal is to provide different behaviors that exit from the function. <em>Returns</em> has to be understood as a family containing: <a href="http://www.php.net/return">return</a>, <a href="http://www.php.net/throw">throw</a>, <a href="http://www.php.net/trigger_error">trigger_error</a>, <a href="http://www.php.net/die">die</a>, <a href="http://www.php.net/exit">exit</a>,...</strong><br />
<code><span style="color: #000000"><br />
<span style="color: #007700">function </span><span style="color: #0000BB">setPassword</span><span style="color: #007700">(</span><span style="color: #0000BB">$password</span><span style="color: #007700">, </span><span style="color: #0000BB">$oldPassword</span><span style="color: #007700">) {<br />
if (</span><span style="color: #0000BB">$password </span><span style="color: #007700">=== </span><span style="color: #0000BB">$oldPassword</span><span style="color: #007700">) {<br />
return;<br />
}<br />
if (!</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">checkPassword</span><span style="color: #007700">(</span><span style="color: #0000BB">$oldPassword</span><span style="color: #007700">)) {<br />
throw new </span><span style="color: #0000BB">Exception</span><span style="color: #007700">(</span><span style="color: #DD0000">'Wrong password.'</span><span style="color: #007700">);<br />
}<br />
if (</span><span style="color: #0000BB">strlen</span><span style="color: #007700">(</span><span style="color: #0000BB">$password</span><span style="color: #007700">) < </span><span style="color: #0000BB">8</span><span style="color: #007700">) {<br />
throw new </span><span style="color: #0000BB">Exception</span><span style="color: #007700">(</span><span style="color: #DD0000">'Password too short.'</span><span style="color: #007700">);<br />
}<br />
if (empty(</span><span style="color: #0000BB">$password</span><span style="color: #007700">)) {<br />
throw new </span><span style="color: #0000BB">Exception</span><span style="color: #007700">(</span><span style="color: #DD0000">'Empty password not permitted.'</span><span style="color: #007700">);<br />
}<br />
</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">password </span><span style="color: #007700">= </span><span style="color: #0000BB">$password</span><span style="color: #007700">;<br />
</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><spanstyle="color: #0000BB">save</span><span style="color: #007700">();<br />
}<br />
</span></span></code><br />
compared to:<br />
<code><span style="color: #000000"><br />
<span style="color: #007700">function </span><span style="color: #0000BB">setPassword</span><span style="color: #007700">(</span><span style="color: #0000BB">$password</span><span style="color: #007700">, </span><span style="color: #0000BB">$oldPassword</span><span style="color: #007700">) {<br />
if (</span><span style="color: #0000BB">$password </span><span style="color: #007700">!== </span><span style="color: #0000BB">$oldPassword</span><span style="color: #007700">) {<br />
if (</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">checkPassword</span><span style="color: #007700">(</span><span style="color: #0000BB">$oldPassword</span><span style="color: #007700">)) {<br />
if (</span><span style="color: #0000BB">strlen</span><span style="color: #007700">(</span><span style="color: #0000BB">$password</span><span style="color: #007700">) >= </span><span style="color: #0000BB">8</span><span style="color: #007700">) {<br />
if (!empty(</span><span style="color: #0000BB">$password</span><span style="color: #007700">)) {<br />
</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">password </span><span style="color: #007700">= </span><span style="color: #0000BB">$password</span><span style="color: #007700">;<br />
</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">save</span><span style="color: #007700">();<br />
} else {<br />
throw new </span><span style="color: #0000BB">Exception</span><span style="color: #007700">(</span><span style="color: #DD0000">'Empty password not permitted.'</span><span style="color: #007700">);<br />
}<br />
} else {<br />
throw new </span><span style="color: #0000BB">Exception</span><span style="color: #007700">(</span><span style="color: #DD0000">'Password too short.'</span><span style="color: #007700">);<br />
}<br />
} else {<br />
throw new </span><span style="color: #0000BB">Exception</span><span style="color: #007700">(</span><span style="color: #DD0000">'Wrong password.'</span><span style="color: #007700">);<br />
}<br />
}<br />
}<br />
</span></span></code><br />
From this two implementations, we can see several benefits of the first one:<br />
<ul><li>it is easier to read</li>
<li>reordering conditions is easier because they are not nested</li>
<li>the link between a condition and the corresponding Exception is more obvious</li>
<li>keep the indentation lower, which makes life easier with the limit of 80 chars/line</li>
<li>less line are changed across revisions which decrease the number of SVN conflicts possible and easier the code review. Take a look at the following pictures showing the same change requests (doing nothing if old and new password are the same + checking that password contains 8+ chars) implemented with the two styles:</li>
</ul><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUgeHIC85wdaq0iHVBSX1rGBKMR6f8xXRerONvgXfXyx-pA0qkuQ1T6FUwiuLXwDXpAt9ZB-W_Tz9NZ4-Rtt8qvZSZx4fG-nX9JgJ63TSVBYmxQNzA8Vpk-z4xNrXhfHFehZguUuzrjA/s1600-h/diff_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQgxS4ZSpKwH0iu3R58mh4ZPjmambd5NSADR9MEoDHEYoXzYJG2S3azHhU12pwl_PL2fz-caIajb7booY_LgGYZJimn5ZP42jFOKUhc2a3yZcD_Ke1B1iOBYDG2QkbVo4y_fW1uXJaOA/s400-r/diff_2.png" /></a><br />
Using "Return as soon as possible"™</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcS6PYtewEqPBGbVPEYPJVm4xU8rt-OXW_zQ4k4pCDjghVNuxHolkVxKIzCgA3M_gMJFGkaXbiWBDr7ZvIXK1ICrjDAHADwHuA21JW7AioQINrWmvIK7IpeFCjS7aowNyASvxuPpMzYw/s1600-h/diff_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7Lct6ZjzB2Yzyd_w2SOcHMQkSULL3xTJli-CFZK5PKT-4AnAT_93r7YRU1dlj4FNHLfycVELWsOXA_hJfgW1lljgEoF_imxDU27SnAT5dgYS_pRPvJpO9UhdmKc_GZxFBLsI3A469rA/s400-r/diff_1.png" /></a><br />
<br />
Using nested conditions</div>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com6tag:blogger.com,1999:blog-7449668645955818675.post-51436911922346622072008-06-08T16:01:00.010+02:002009-03-01T21:02:04.190+01:00Apache as an MVC controller<p>My colleagues know I am not a big fan of PHP frameworks, this is probably why I somewhat agree with the <a href="http://toys.lerdorf.com/archives/38-The-no-framework-PHP-MVC-framework.html">no-framework PHP MVC framework</a> of <a href="http://lerdorf.com/">Rasmus Lerdorf</a> or with the idea of <a href="http://bitmeta.org/">Akash Mehta</a> thinking, to an extent, that <a href="http://www.sitepoint.com/blogs/2008/06/08/last-we-checked-php-is-a-framework/">PHP IS a framework</a>. Keeping this global picture in mind, let me show you how Apache could take the <em>C</em> of <em>MVC</em>.</p><p>Because not mixing the business logic and the presentation is always a good idea, this leads up to have two types of PHP files: those with business logic only (the <em>M</em> of <em>MVC</em>), whether it's OOP or procedural programming, and those that are just templates containing mostly HTML (the <em>V</em> of <em>MVC</em>).</p><p>Mapping URLs to actions/views is the job of Apache, by default, it will look at your URL and make the match with the corresponding script on the filesystem. This is why I consider Apache as a controller.</p><h4>Nice URL</h4><p>One of the goal of a web controller is also to provide nice URL's to your application, I will present you here two methods to achieve this with Apache exclusively.</p><h5>Method #1</h5><p>It is quiet common Apache being configured with <div class="code"><code class="apacheConfig"><span class="apacheKeyword">DirectoryIndex</span> index.php</code></div>Knowing this you can architect your directories with the same structure as your URLs always with an <em>index.php</em> file handling the request. Let's take the example of a web project management application having a dedicated page for projects and users. Your URLs might be:<ul><li>http://project-management/projects/?id=<em>xxx</em></li>
<li>http://project-management/projects/remove/?id=<em>xxx</em></li>
<li>http://project-management/users/?id=<em>xxx</em></li>
<li>http://project-management/users/add/</li>
</ul>Handling this could be done using the filesystem layout as shown on the following picture:<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigHtfwFo2xgP6QHhZnnrPnP_lxW_y6IZLCPA1atNOVMFWXHpK6J485IosdvZyvOtHIjtIoLhnoWKU2eMwBfrx_QtbTOBzcR6q7nTDfBVVm6mcPYOO5RmVKmPTe0nKbgBjnm41l96-a2Q/s1600-h/method1.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigHtfwFo2xgP6QHhZnnrPnP_lxW_y6IZLCPA1atNOVMFWXHpK6J485IosdvZyvOtHIjtIoLhnoWKU2eMwBfrx_QtbTOBzcR6q7nTDfBVVm6mcPYOO5RmVKmPTe0nKbgBjnm41l96-a2Q/s400/method1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5209560317427269458" /></a></p><h5>Method #2</h5>Another method, which may provide you even nicer URLs, relies on Apache's <a href="http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html">mod_rewrite</a>. Let's change our URLs a little bit:<ul><li>http://project-management/projects/<em>xxx</em></li>
<li>http://project-management/projects/remove/<em>xxx</em></li>
<li>http://project-management/users/<em>xxx</em></li>
<li>http://project-management/users/add/</li>
</ul>To handle this, I use a flatter filesystem layout: <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL0iI8xQ2C0V5pubzCp5ixlQGyfLeUxdvs64uq0KuzL7P4sj4sy4awJN_W78RkrLbAVWE7DmtWnODM-r6KY_V9GGPqXCEyNWSIztI4SHXSlGYiogvq9w5wUTT1pH-OSREsau5W62o6pA/s1600-h/method2.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL0iI8xQ2C0V5pubzCp5ixlQGyfLeUxdvs64uq0KuzL7P4sj4sy4awJN_W78RkrLbAVWE7DmtWnODM-r6KY_V9GGPqXCEyNWSIztI4SHXSlGYiogvq9w5wUTT1pH-OSREsau5W62o6pA/s400/method2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5209560344553744098" /></a>with the following rewrite rules:</p><div class="code"><code class="apacheConfig"><span class="apacheKeyword">RewriteEngine</span> On<br />
<br />
<span class="apacheComment"># Preventing access to php files directly</span><br />
<span class="apacheKeyword">RewriteRule</span> \.php$ /NotFound [L]<br />
<br />
<span class="apacheComment"># Simple URL mapping</span><br />
<span class="apacheKeyword">RewriteRule</span> ^/$ /index.php [L]<br />
<span class="apacheKeyword">RewriteRule</span> ^/users/$ /ListUsers.php [L]<br />
<span class="apacheKeyword">RewriteRule</span> ^/projects/$ /ListProjects.php [L]<br />
<span class="apacheKeyword">RewriteRule</span> ^/users/add/$ /AddUser.php [L]<br />
<span class="apacheKeyword">RewriteRule</span> ^/projects/add/$ /AddProject.php [L]<br />
<br />
<span class="apacheComment"># URL mapping with captured IDs</span><br />
<span class="apacheKeyword">RewriteRule</span> ^/users/([0-9]+)$ /ViewUser.php?id=$1 [L]<br />
<span class="apacheKeyword">RewriteRule</span> ^/projects/([0-9]+)$ /ViewProject.php?id=$1 [L]<br />
<span class="apacheKeyword">RewriteRule</span> ^/users/remove/([0-9]+)$ /RemoveUser.php?id=$1 [L]<br />
<span class="apacheKeyword">RewriteRule</span> ^/projects/remove/([0-9]+)$ /RemoveProject.php?id=$1 [L]<br />
</code></div><p>The first thing done is preventing direct access to PHP files, this is not mandatory but it adds some more security. The only way to reach the desired script is to match strictly the regular expression making some input filtering at the same time. Then comes the real and interesting rewrite rules. First ones are very simple mapping while the 4 last ones takes care of extracting a numerical ID from the URL and passing it to PHP as a $_GET parameter.</p><p>What do you think about such approach? Please, leave some comments :)</p>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com2tag:blogger.com,1999:blog-7449668645955818675.post-17626943553473345572008-05-26T01:10:00.004+02:002009-07-26T17:21:35.487+02:00Hierarchical data in MySQL (and other RDBMS)<h4>Introduction</h4><p>There are lot of cases we want to store hierarchical data into relational database instead of hierarchical ones like <a href="http://en.wikipedia.org/wiki/XML_database">XML databases</a>. Several approaches exist and are already <a href="#related-links">well explained</a>, the most well known are the <a href="#adjacency-list"><em>adjacency list</em></a> and the <a href="#nested-set"><em>nested set</em></a> models. After briefly introducing those models I will present <a href="#path-enumeration">an extension</a> to the first one making it much more usable. This extension has already been presented by <a href="http://www.celko.com/">Joe Celko</a> under the name <a href="http://www.onlamp.com/pub/a/onlamp/2004/08/05/hierarchical_sql.html">Path enumeration</a></p><h4 id="adjacency-list">The adjacency list model</h4><p>The most common way to store such data is using the adjacency list model as introduced by former IBM fellow <a href="http://en.wikipedia.org/wiki/Edgar_F._Codd">Edgar F. Codd</a> (the father of <a href="http://en.wikipedia.org/wiki/Relational_database">relational database</a> theory):</p><table border="1" style="margin: 10px; float: left;"><thead><tr><th>id</th><th>name</th><th>boss</th></tr></thead><tbody><tr><td>1</td><td>Anne</td><td><em>NULL</em></td></tr><tr><td>2</td><td>Bernard</td><td>1</td></tr><tr><td>3</td><td>Charlie</td><td>1</td></tr><tr><td>4</td><td>Delphine</td><td>3</td></tr><tr><td>5</td><td>Elodie</td><td>3</td></tr><tr><td>6</td><td>Fanny</td><td>3</td></tr><tr><td>7</td><td>Georges</td><td>5</td></tr></tbody></table><a style="margin: 10px; float: left; display: block;" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3EoGDYaZWHkTCSIge0JR4sd68W9D_NKr5AORJ6N8Pwr5amyrLdr2dk3TXtsDaBiJ_AiC4TY8ao8zVfwoBaZp8YsHFQFA8ZmLhUb-S1UOPoRSui18JPlHVKY_AmxTX91fiHd5LigO79w/s1600-h/AdjacencyListModel.png"><img style="cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3EoGDYaZWHkTCSIge0JR4sd68W9D_NKr5AORJ6N8Pwr5amyrLdr2dk3TXtsDaBiJ_AiC4TY8ao8zVfwoBaZp8YsHFQFA8ZmLhUb-S1UOPoRSui18JPlHVKY_AmxTX91fiHd5LigO79w/s400/AdjacencyListModel.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5205320146402232594" /></a><div style='clear: both;'></div><p>Such model respects fully the relational idea based on primary and foreign keys, but this system shows his limit when we have to retrieve the full path to a node: "Anne > Charlie > Elodie > Georges" or when we need all people working below Charlie. Such question typically requires recursion with many queries which may become very slow:</p><div class="code"><code><em>-- Path to Georges?</em><br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> id = 7; <em>-- boss = 5</em><br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> id = 5; <em>-- boss = 3</em><br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> id = 3; <em>-- boss = 1</em><br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> id = 1; <em>-- boss = NULL (STOP)</em><br /><br />-- People under Charlie?<br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> boss <strong>IN</strong> (3); -- id = 4,5,6<br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> boss <strong>IN</strong> (4,5,6); -- id = 7<br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> boss <strong>IN</strong> (7); -- no results (STOP)<br /></code></div><h4 id="nested-set">The nested set model</h4><p>The second popular approach is to make use of the <a href="http://en.wikipedia.org/wiki/Depth-first_search">depth first traversal algorithm</a> to assign a <strong>left</strong> and <strong>right</strong> number to any node of the tree:</p><table border="1" style="margin: 10px; float: left;"><thead><tr><th>id</th><th>name</th><th>lft</th><th>rgt</th></tr></thead><tbody><tr><td>1</td><td>Anne</td><td>1</td><td>14</td></tr><tr><td>2</td><td>Bernard</td><td>2</td><td>3</td></tr><tr><td>3</td><td>Charlie</td><td>4</td><td>13</td></tr><tr><td>4</td><td>Delphine</td><td>5</td><td>6</td></tr><tr><td>5</td><td>Elodie</td><td>7</td><td>10</td></tr><tr><td>6</td><td>Fanny</td><td>11</td><td>12</td></tr><tr><td>7</td><td>Georges</td><td>8</td><td>9</td></tr></tbody></table><a style="margin: 10px; float: left; display: block;" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDQ7bWHAUjLu81WRlSUX3dfRisuicXaQ8iEKktsLQdCqe48uqUquV3KhA-6EHPJo_Phvo5ERkKwdia_bRm4i06tzUkLa31ArehGj1Cco-yhdFksMQuVpKkeVuEMTRdx2dRP0kkvQXkWw/s1600-h/NestedSetModel.png"><img style="cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDQ7bWHAUjLu81WRlSUX3dfRisuicXaQ8iEKktsLQdCqe48uqUquV3KhA-6EHPJo_Phvo5ERkKwdia_bRm4i06tzUkLa31ArehGj1Cco-yhdFksMQuVpKkeVuEMTRdx2dRP0kkvQXkWw/s400/NestedSetModel.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5205323904498616626" /></a><div style='clear: both;'></div><p>This model is algorithmically beautiful! It solves in an elegant way the problem of running multiple queries to retrieve hierarchical information:</p><div class="code"><code><em>-- Path to Georges?</em><br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> 8 <strong>BETWEEN</strong> lft <strong>AND</strong> rgt <strong>ORDER BY</strong> lft;<br /><br />-- People under Charlie?<br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> lft <strong>BETWEEN</strong> 4 <strong>AND</strong> 13 <strong>ORDER BY</strong> lft;<br /></code></div><p>While this model is very good at retrieving hierarchical information, it is somewhat more complex and less competitive at updating the hierarchy (inserting, moving or removing nodes) since it requires to update several records even if it is feasible in one query. Not to mention that this model is not relational at all and does not prevent through integrity constraints the accidental removal of a boss! However, this is easily circumvented by adding the <em>boss</em> column and then mixing both models.</p><h4 id="path-enumeration">Path enumeration model</h4><p>The path enumeration model is based on the adjacency list one, the idea is quite simple: add a column materializing the full path (which is unique) to your node.</p><table border="1" style="margin: 10px;"><thead><tr><th>id</th><th>name</th><th>boss</th><th>path</th></tr></thead><tbody><tr><td>1</td><td>Anne</td><td><em>NULL</em></td><td>/1/</td></tr><tr><td>2</td><td>Bernard</td><td>1</td><td>/1/2/</td></tr><tr><td>3</td><td>Charlie</td><td>1</td><td>/1/3/</td></tr><tr><td>4</td><td>Delphine</td><td>3</td><td>/1/3/4/</td></tr><tr><td>5</td><td>Elodie</td><td>3</td><td>/1/3/5/</td></tr><tr><td>6</td><td>Fanny</td><td>3</td><td>/1/3/6/</td></tr><tr><td>7</td><td>Georges</td><td>5</td><td>/1/3/5/7/</td></tr></tbody></table><p>The path is always computed by taking the path of the parent node concatenated with "<ID>/". Retrieving the path to Georges or people working for Charlie is a kid's game:</p><div class="code"><code><em>-- Path to Georges?</em><br /><strong>SELECT</strong> path <strong>FROM</strong> people <strong>WHERE</strong> id = 7; <em>-- path = /1/3/5/7/</em><br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> id <strong>IN</strong> (1,3,5,7) <strong>ORDER BY</strong> path;<br /><br />-- People under Charlie?<br /><strong>SELECT</strong> * <strong>FROM</strong> people <strong>WHERE</strong> path <strong>LIKE</strong> '/1/3/%' <strong>ORDER BY</strong> path;<br /></code></div><p>To benefit from all the speed of this solution, you should have a <strong>UNIQUE KEY</strong> on your <em>path</em> column. Unfortunately it isn't (yet?) possible to know the assigned auto-increment ID inside an insert-trigger using MySQL, it is then mandatory to work in two phases while inserting a record. A first solution is to insert dummy data in the <em>path</em> while taking care of the <strong>UNIQUE KEY</strong> constraint and then updating the record using <em>LAST_INSERT_ID()</em>. My preference goes to using another table (people_tree) with a 1..1 relation. Here is an example:</p><div class="code"><code><em>-- Adding Helena below Bernard</em><br /><strong>INSERT INTO</strong> people (name, boss) <strong>VALUES</strong> ('Helena', 2);<br /><strong>INSERT INTO</strong> people_tree<br /><strong>SELECT</strong> p.id, <strong>CONCAT</strong>(pt.path, p.id, '/') <strong>FROM</strong> people p <strong>JOIN</strong> people_tree pt <strong>ON</strong> p.boss = pt.id <strong>WHERE</strong> p.id = <strong>LAST_INSERT_ID</strong>();</code></div><p>Of course, this complexity may be hidden in a stored procedure.</p><h4 id="related-links">Related links</h4><ul><li><a href="http://dev.mysql.com/tech-resources/articles/hierarchical-data.html">Managing Hierarchical Data in MySQL</a></li><li><a href="http://www.sitepoint.com/article/hierarchical-data-database">Storing Hierarchical Data in a Database</a></li><li><a href="http://www.onlamp.com/pub/a/onlamp/2004/08/05/hierarchical_sql.html">Hierarchical SQL</a></li></ul>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com7tag:blogger.com,1999:blog-7449668645955818675.post-43441688853219516402007-12-13T12:36:00.000+01:002007-12-13T13:54:32.088+01:00Dutch Government promotes Open Standard and FOSS<p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgc9Nc4WSqNtYvaEGaeknn4i8AITWmnIVyXsZK401onwKYNWfisN2v_FiZSth8sog_S3VmCA-cxZVfd_YFQsnMh09OynLtKjlFQjtIy2jgo1V6SnsD1Qap0oOHG16GWugqBtoKGBuqztQ/s1600-h/NL_-_COA.png"><img style="float:right; margin:0 0 10px 10px; cursor:pointer; cursor:hand; width: 120px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgc9Nc4WSqNtYvaEGaeknn4i8AITWmnIVyXsZK401onwKYNWfisN2v_FiZSth8sog_S3VmCA-cxZVfd_YFQsnMh09OynLtKjlFQjtIy2jgo1V6SnsD1Qap0oOHG16GWugqBtoKGBuqztQ/s320/NL_-_COA.png" border="0" alt="Dutch coat of arms" id="BLOGGER_PHOTO_ID_5143438745279133682" /></a>A great news this week for <a href="http://en.wikipedia.org/wiki/FOSS"><acronym title="Free and Open Source Software">FOSS</acronym></a> evangelists! The Dutch Government and Parliament approved an action plan which promotes Open Standard and FOSS over <a href="http://en.wikipedia.org/wiki/Proprietary_format">proprietary format</a> and <a href="http://en.wikipedia.org/wiki/Proprietary_software">software</a>. Quoting <a href="http://www.omat.nl/">Tom Albers</a>:</p><p class="cite"><cite>From spring 2008 the central governement will make <strong>Open Standards mandatory for their IT</strong>. If not possible it has to be explained why it is not possible, including a time line to the standard. <strong>Closed standards will have to be phased out.</strong> Their will be a commission for people to complain to if the governement uses a closed standard where an open standard can do the trick as well.</cite></p><p>Read more about this on <a href="http://opensourcelearning.info/blog/?p=704">opensourcelearning.info</a>, <a href="http://www.omat.nl/drupal/our-governement-has-seen-light-open-standards-and-">Tom Albers' blog</a> and <a href="http://www.kdedevelopers.org/node/3142">KDE Developer's Journals</a>.</p>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com1tag:blogger.com,1999:blog-7449668645955818675.post-21722396976953550512007-12-03T00:32:00.000+01:002007-12-03T00:55:08.983+01:00Templatize your Apache configuration with mod_macro<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://httpd.apache.org/"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 200px;" src="http://httpd.apache.org/docs/2.2/images/feather.gif" alt="" /></a><h4>Background</h4><p>In a previous post I presented a way to <a href="/2007/11/modularize-your-apache-configuration.html">clean up your Apache's configuration</a> by putting your sites' configuration apart from your <em>httpd.conf</em>. In this one I will show you how to build a scheme to easier the maintenance of your Apache related configuration by avoiding some <a href="http://en.wikipedia.org/wiki/Copy_and_paste_programming">nasty copy/paste</a>.</p><h4>Current situation</h4><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTf-80qncHNxnQf1ymoKaP702GjEDdi8mhY0iVItJDouEaDZ_frb-5Wr63IaMPzpznfJeiYUXfAWAhc3F-bHpDWFjGmLs_czfOHo9qdeQ19g5U1lO-P6xmc1ZLrFJLUQYulBzO2eCq1A/s1600-h/apache2_dir0.png"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTf-80qncHNxnQf1ymoKaP702GjEDdi8mhY0iVItJDouEaDZ_frb-5Wr63IaMPzpznfJeiYUXfAWAhc3F-bHpDWFjGmLs_czfOHo9qdeQ19g5U1lO-P6xmc1ZLrFJLUQYulBzO2eCq1A/s320/apache2_dir0.png" alt="Current apache structure" id="BLOGGER_PHOTO_ID_5134135755364188578" /></a><p>Let's analyze the structure of the current <em>/etc/apache2/</em> directory displayed in the picture on the right:</p><ul><li><em>httpd.conf</em> is the single entry point for Apache's configuration, it contains the <strong>bare minimum configuration</strong> needed for the server's instance.</li><li><em>modules.d</em> is the directory containing all <strong>modules and features configuration</strong> files, it is <a href="http://www.gentoo.org/">Gentoo</a>'s equivalent to handle the <em>mods-enabled</em> directory of <a href="http://www.debian.org/misc/children-distros">Debian and derivatives</a>. Those files are included from the <em>httpd.conf</em> with:<div class="code"><code class="apacheConfig"><span class="apacheKeyword">Include</span> /etc/apache2/modules.d/*.conf</code></div></li><li><em>vhosts.d</em> is the directory containing all <strong>virtual host configuration</strong> files as explained in my <a href="/2007/11/modularize-your-apache-configuration.html">previous post</a>. This one is the equivalent of <em>sites-enabled</em>. Those files are included the same way than <em>modules.d</em> with:<div class="code"><code class="apacheConfig"><span class="apacheKeyword">Include</span> /etc/apache2/vhosts.d/*.conf</code></div></li></ul><p>Lets have the configuration of <em>www.example.org</em>, <em>wiki.example.org</em> and <em>forum.example.org</em> all hosted by the same Apache instance.</p><div style='clear: both;'></div><p>Content of <em>vhosts.d/www.example.org</em>:</p><div class="code"><code class="apacheConfig"><span class="apacheBlock"><VirtualHost</span><span class="apacheBlockParam"> *:80</span><span class="apacheBlock">></span><br /> <span class="apacheKeyword">ServerName</span> www.example.org<br /> <span class="apacheKeyword">DocumentRoot</span> /var/www/www.example.org<br /><br /> <span class="apacheKeyword">ErrorLog</span> /var/log/apache2/www.example.org/error_log<br /> <span class="apacheKeyword">CustomLog</span> /var/log/apache2/www.example.org/access_log common<br /> <span class="apacheKeyword">php_admin_value error_log</span> "/var/log/apache2/www.example.org/php_error_log"<br /><span class="apacheBlock"></VirtualHost></span></code></div><p>Content of <em>vhosts.d/wiki.example.org</em>:</p><div class="code"><code class="apacheConfig"><span class="apacheBlock"><VirtualHost</span><span class="apacheBlockParam"> *:80</span><span class="apacheBlock">></span><br /> <span class="apacheKeyword">ServerName</span> wiki.example.org<br /> <span class="apacheKeyword">DocumentRoot</span> /var/www/wiki.example.org<br /><br /> <span class="apacheKeyword">ErrorLog</span> /var/log/apache2/wiki.example.org/error_log<br /> <span class="apacheKeyword">CustomLog</span> /var/log/apache2/wiki.example.org/access_log common<br /> <span class="apacheKeyword">php_admin_value error_log</span> "/var/log/apache2/wiki.example.org/php_error_log"<br /><span class="apacheBlock"></VirtualHost></span></code></div><p>Content of <em>vhosts.d/forum.example.org</em>:</p><div class="code"><code class="apacheConfig"><span class="apacheBlock"><VirtualHost</span><span class="apacheBlockParam"> *:80</span><span class="apacheBlock">></span><br /> <span class="apacheKeyword">ServerName</span> forum.example.org<br /> <span class="apacheKeyword">DocumentRoot</span> /var/www/forum.example.org<br /><br /> <span class="apacheKeyword">ErrorLog</span> /var/log/apache2/forum.example.org/error_log<br /> <span class="apacheKeyword">CustomLog</span> /var/log/apache2/forum.example.org/access_log common<br /> <span class="apacheKeyword">php_admin_value error_log</span> "/var/log/apache2/forum.example.org/php_error_log"<br /><span class="apacheBlock"></VirtualHost></span></code></div><p>We may notice that strong conventions are used:</p><ol><li>Document root is located in /var/www/<a href="http://en.wikipedia.org/wiki/Fully_qualified_domain_name">$FullyQualifiedDomainName</a></li><li>All logs are stored inside /var/log/apache2/<a href="http://en.wikipedia.org/wiki/Fully_qualified_domain_name">$FullyQualifiedDomainName</a></li><li>Only the <a href="http://en.wikipedia.org/wiki/Fully_qualified_domain_name">$FullyQualifiedDomainName</a> differs from one configuration to another.</li></ol><h4>mod_macro in action</h4><p>Because the configuration of the virtual hosts follow all the same rules, this is a perfect candidate for <a href="http://www.coelho.net/home.html">Fabien COELHO</a>'s <a href="http://www.cri.ensmp.fr/~coelho/mod_macro/">mod_macro</a>. This Apache module lets you define and use macros within Apache runtime configuration files. Just take a look at the following macro:</p><div class="code"><code class="apacheConfig"><span class="apacheComment"># Macro definition for a generic virtual host</span><br /><span class="apacheBlock"><Macro</span><span class="apacheBlockParam"> VHost</span> <span class="apacheVariable">$fqdn</span><span class="apacheBlock">></span><br /> <span class="apacheBlock"><VirtualHost</span><span class="apacheBlockParam"> *:80</span><span class="apacheBlock">></span><br /> <span class="apacheKeyword">ServerName</span> <span class="apacheVariable">$fqdn</span><br /> <span class="apacheKeyword">DocumentRoot</span> /var/www/<span class="apacheVariable">$fqdn</span><br /><br /> <span class="apacheKeyword">ErrorLog</span> /var/log/apache2/<span class="apacheVariable">$fqdn</span>/error_log<br /> <span class="apacheKeyword">CustomLog</span> /var/log/apache2/<span class="apacheVariable">$fqdn</span>/access_log common<br /> <span class="apacheKeyword">php_admin_value error_log</span> "/var/log/apache2/<span class="apacheVariable">$fqdn</span>/php_error_log"<br /> <span class="apacheBlock"></VirtualHost></span><br /><span class="apacheBlock"></Macro></span></code></div><p>Here is how the content of my virtual hosts configuration:</p><p>Content of <em>vhosts.d/www.example.org</em>:</p><div class="code"><code class="apacheConfig"><span class="apacheBlock">Use</span><span class="apacheBlockParam"> VHost</span> <span class="apacheParam">www.example.org</span></code></div><p>Content of <em>vhosts.d/wiki.example.org</em>:</p><div class="code"><code class="apacheConfig"><span class="apacheBlock">Use</span><span class="apacheBlockParam"> VHost</span> <span class="apacheParam">wiki.example.org</span></code></div><p>Content of <em>vhosts.d/forum.example.org</em>:</p><div class="code"><code class="apacheConfig"><span class="apacheBlock">Use</span><span class="apacheBlockParam"> VHost</span> <span class="apacheParam">forum.example.org</span></code></div><p>While this solution is just perfect in case every virtual hosts should be configured exactly the same way (in that case you may be interested in <a href="http://httpd.apache.org/docs/2.2/mod/mod_vhost_alias.html">mod_vhost_alias</a>) it doesn't provide much flexibility to modify a specific virtual host afterwards even temporarily. To overcome to this shortcoming, here is my final structure:<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQeGolpfL416KVDI_25fpyNjDeotcGc8uIGoIHN-rsBkhAeTAeOdD9uei5T-qf1MHgvA9l_CzALztiRYVnauzo4mM_8Qmu6Pel9INAn_orLmJ-G6rY0SCEXOy2c1Io5AM3GruXKzg88w/s1600-r/apache2_dir1.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguGRR7DUVjbWljwIXmj7RUNlGdXKg9Rvh4LduwTNvcrvO4IeAHn_v36n82hfbIblQ-ejc2exbjaKomvC902BKlbrT1HRgLo4AZzEW-r7iZDdKg4l-wsT3WJposVCEPeFlhly0CWaHtqw/s400/apache2_dir1.png" border="0" alt="Updated Apache structure" id="BLOGGER_PHOTO_ID_5139523242638702562" /></a></p><ul><li>an additional <em>macros.d</em> to holds macros definitions into <em>.conf</em> files loaded with: <div class="code"><code class="apacheConfig"><span class="apacheKeyword">Include</span> /etc/apache2/macros.d/*.conf</code></div></li><li>a home made VirtualHost definition made in two parts:<div class="code"><code class="apacheConfig"><span class="apacheComment"># Macro definition for a generic virtual host</span><br /><span class="apacheBlock"><Macro</span><span class="apacheBlockParam"> BeginVHost</span> <span class="apacheVariable">$fqdn</span><span class="apacheBlock">></span><br /> <span class="apacheBlock"><VirtualHost</span><span class="apacheBlockParam"> *:80</span><span class="apacheBlock">></span><br /> <span class="apacheKeyword">ServerName</span> <span class="apacheVariable">$fqdn</span><br /> <span class="apacheKeyword">DocumentRoot</span> /var/www/<span class="apacheVariable">$fqdn</span><br /><br /> <span class="apacheKeyword">ErrorLog</span> /var/log/apache2/<span class="apacheVariable">$fqdn</span>/error_log<br /> <span class="apacheKeyword">CustomLog</span> /var/log/apache2/<span class="apacheVariable">$fqdn</span>/access_log common<br /> <span class="apacheKeyword">php_admin_value error_log</span> "/var/log/apache2/<span class="apacheVariable">$fqdn</span>/php_error_log"<br /><span class="apacheBlock"></Macro></span><br /><span class="apacheBlock"><Macro</span><span class="apacheBlockParam"> EndVHost</span><span class="apacheBlock">></span><br /> <span class="apacheBlock"></VirtualHost></span><br /><span class="apacheBlock"></Macro></span></code></div></li><li>virtual hosts defined with:<div class="code"><code class="apacheConfig"><span class="apacheBlock">Use</span><span class="apacheBlockParam"> BeginVHost</span> <span class="apacheParam">www.example.org</span><br /><span class="apacheComment"> # Define specific virtual host configuration here.<br /> # Example:<br /> # Use ZendFramework</span><br /><span class="apacheBlock">Use</span><span class="apacheBlockParam"> EndVHost</span></code></div></li></ul><h4>Conclusions</h4><p>This post doesn't attempt to state the best way to manage tons of virtual host configuration, for each environment, there are different considerations to be taken and there's no perfect way. The main reason I wrote this post was to share the lessons learned with mod_macro and Gentoo's way to organize things and maybe helping others to improve their Apache configuration.</p>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com1tag:blogger.com,1999:blog-7449668645955818675.post-73207581236140405872007-11-18T12:02:00.000+01:002007-12-03T00:55:08.983+01:00Modularize your apache configuration<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://httpd.apache.org/"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 200px;" src="http://httpd.apache.org/docs/2.2/images/feather.gif" alt="" /></a><h4>First symptoms</h4><p>The very first time I had to modify something into <a href="http://httpd.apache.org/">Apache</a>'s configuration, the <a href="http://httpd.apache.org/docs/">documentation</a> tells me I had to do so in a file named <em>httpd.conf</em>. With the time, this file became more and more unmanageable: tons of unsorted <a href="http://httpd.apache.org/docs/trunk/mod/core.html#virtualhost"><em>VirtualHost</em></a> and <a href="http://httpd.apache.org/docs/trunk/mod/core.html#directory"><em>Directory</em></a> blocks mixed with custom changes and Linux distribution specific configuration.</p><h4>First aid</h4><p>Everything became cleaner when I modularized the configuration into separate files thanks to the <a href="http://httpd.apache.org/docs/trunk/mod/core.html#include">Include directive</a> where every virtual hosts were defined into separate files. The only change made to the <em>httpd.conf</em> was to append:</p><div class="code"><code class="apacheConfig"><span class="apacheKeyword">Include</span> /etc/apache2/vhosts.d/*.conf</code></div><p>Every sites can then be configured into their own <em>vhosts.d/<a href="http://en.wikipedia.org/wiki/Fully_qualified_domain_name">$FullyQualifiedDomainName</a>.conf</em> file.</p><p>Example with <em>vhosts.d/wiki.example.org</em>:</p><div class="code"><code class="apacheConfig"><span class="apacheBlock"><VirtualHost</span><span class="apacheBlockParam"> *:80</span><span class="apacheBlock">></span><br /> <span class="apacheKeyword">ServerName</span> wiki.example.org<br /> <span class="apacheKeyword">DocumentRoot</span> /var/www/wiki.example.org<br /><br /> <span class="apacheBlock"><Directory</span><span class="apacheBlockParam"> /var/www/wiki.example.org</span><span class="apacheBlock">></span><br /> <span class="apacheKeyword">AllowOverride</span> <span class="apacheConst">None</span><br /> <span class="apacheBlock"></Directory></span><br /><br /> <span class="apacheKeyword">ErrorLog</span> /var/log/apache2/wiki.example.org/error_log<br /> <span class="apacheKeyword">CustomLog</span> /var/log/apache2/wiki.example.org/access_log common<br /> <span class="apacheKeyword">php_admin_value error_log</span> "/var/log/apache2/wiki.example.org/php_error_log"<br /><span class="apacheBlock"></VirtualHost></span></code></div><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTf-80qncHNxnQf1ymoKaP702GjEDdi8mhY0iVItJDouEaDZ_frb-5Wr63IaMPzpznfJeiYUXfAWAhc3F-bHpDWFjGmLs_czfOHo9qdeQ19g5U1lO-P6xmc1ZLrFJLUQYulBzO2eCq1A/s1600-h/apache2_dir0.png"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTf-80qncHNxnQf1ymoKaP702GjEDdi8mhY0iVItJDouEaDZ_frb-5Wr63IaMPzpznfJeiYUXfAWAhc3F-bHpDWFjGmLs_czfOHo9qdeQ19g5U1lO-P6xmc1ZLrFJLUQYulBzO2eCq1A/s320/apache2_dir0.png" alt="" id="BLOGGER_PHOTO_ID_5134135755364188578" /></a><h4>Health check</h4><p>After this reorganization I was up to disable a site simply by renaming the related <em>.conf</em> file without editing it, every virtual hosts had their own file and, the most important <acronym title="in my opinion">IMO</acronym>, sites configuration were not mixed with the rest! This is how <a href="http://www.gentoo.org/">Gentoo Linux</a> organize its apache's configuration by default. My <em>/etc/apache2</em> directory now looks like the image on the right.</p>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com0tag:blogger.com,1999:blog-7449668645955818675.post-25997835586170416162007-10-30T17:51:00.000+01:002007-12-02T20:07:10.476+01:00Ausy goes to Paris<p>I am really happy to see my colleagues (<a href="http://vincentdupont.blogspot.com/">Vincent DUPONT</a> & David IACHETTA) at <a href="http://www.ausy.be/">Ausy</a> being <a href="http://www.afup.org/pages/forumphp2007/conferenciers.php">speakers</a> the <a href="http://www.afup.org/pages/forumphp2007/sessions.php#jour1">first day</a> of the <a href="http://www.afup.org/pages/forumphp2007/">PHP Forum in Paris</a> organized yearly by the <a href="http://www.afup.org/"><acronym title="Association Française des Utilisateurs de PHP">AFUP</acronym></a>. I hope they will present things the same way the guy below does at the <a href="http://www.ripe.net/ripe/meetings/ripe-55/"><acronym title="Réseaux IP Européens">RIPE</acronym> 55 conference in Amsterdam</a>. Warm up your vocal cords guys!</p><object width="425" height="355" style="float: right;"><param name="movie" value="http://www.youtube.com/v/_y36fG2Oba0&rel=1"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/_y36fG2Oba0&rel=1" type="application/x-shockwave-flash" wmode="transparent" width="425" height="355"></embed></object><p>Lyrics:</p><pre>a long long time ago<br />i can still remember<br />when my laptop could connect elsewhere<br /><br />and i tell you all there was a day<br />the network card i threw away<br />had a purpose - and worked for you and me....<br /><br />But 18 years completely wasted<br />with each address we've aggregated<br />the tables overflowing<br />the traffic just stopped flowing....<br /><br />And now we're bearing all the scars<br />and all my traceroutes showing stars...<br />the packets would travel faster in cars...<br />the day....the routers died<br /><br /><br /><br />Chorus (ALL!!!!!)<br /><br />So bye bye, folks at RIPE 55<br />Be persuaded to upgrade it or your network will die<br />IPv6 just makes me let out a sigh<br />But I spose we'd better give it a try<br />I suppose we'd better give it a try<br /><br />Now did you write an RFC<br />That dictated how we all should be<br />Did we listen like we should that day<br /><br />Now were you back at RIPE fifty-four<br />Where we heard the same things months before<br />And the people knew they'd have to change their ways....<br /><br />And we - knew that all the ISPs<br />Could be - future proof for centuries<br /><br />But that was then not now<br />Spent too much time playing WoW<br /><br />ooh there was time we sat on IRC<br />Making jokes on how this day would be<br />Now there's no more use for TCP<br />The day the routers died...<br /><br />Chorus (chime in now)<br /><br />So bye bye, folks at RIPE 55<br />Be persuaded to upgrade it or your network will die<br />IPv6 just makes me let out a sigh<br />But I spose we'd better give it a try<br />I suppose we'd better give it a try<br /><br />I remember those old days I mourn<br />Sitting in my room, downloading porn<br />Yeah that's how it used to be....<br /><br />When the packets flowed from A to B<br />via routers that could talk IP<br />There was data..that could be exchanged between you and me....<br /><br />Oh but - I could see you all ignore<br />The fact - we'd fill up IPv4<br /><br />But we all lost the nerve<br />And we got what we deserved!<br /><br />And while...we threw our network kit away<br />And wished we'd heard the things they say<br />Put all our lives in disarray<br /><br />The day...the routers died...<br /><br />Chorus (those silent will be shot)<br /><br />So bye bye, folks at RIPE 55<br />Be persuaded to upgrade it or your network will die<br />IPv6 just makes me let out a sigh<br />But I spose we'd better give it a try<br />I suppose we'd better give it a try<br /><br /><br />Saw a man with whom I used to peer<br />Asked him to rescue my career<br />He just sighed and turned away..<br /><br />I went down to the net cafe<br />that I used to visit everyday<br />But the man there said I might as well just leave...<br /><br />And now we've all lost our purpose..<br />my cisco shares completely worthless...<br /><br />No future meetings for me<br />At the Hotel Krasnapolsky<br /><br />and the men that make us push and push<br />Like Geoff Huston and Randy Bush<br />Should've listened to what they told us....<br />The day...the routers....died<br /><br />Chorus (time to lose your voice)<br /><br />So bye bye, folks at RIPE 55<br />Be persuaded to upgrade it or your network will die<br />IPv6 just makes me let out a sigh<br />But I spose we'd better give it a try<br />I suppose we'd better give it a try</pre>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com0tag:blogger.com,1999:blog-7449668645955818675.post-89468336343246269052007-10-29T22:04:00.000+01:002007-10-30T18:00:00.410+01:00PHP interactive shell<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcjyS3pvUun8L97Q2dBjNaAX1Mr-7ZU1NYbQ45Nq2UR43AM3Z9SfBJuCxEUhaQvrWKXClizzoqHLGNb3IMEbLJDj49XC4k6r2dJ0c_xNsO6RqJfy0xX14OlCWTzdHRHLH78yo2cr5Y9w/s1600-h/konsole.png"><img style="float:right; margin:0 0 10px 10px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcjyS3pvUun8L97Q2dBjNaAX1Mr-7ZU1NYbQ45Nq2UR43AM3Z9SfBJuCxEUhaQvrWKXClizzoqHLGNb3IMEbLJDj49XC4k6r2dJ0c_xNsO6RqJfy0xX14OlCWTzdHRHLH78yo2cr5Y9w/s320/konsole.png" border="0" alt="" /></a><br /><p>A feature of PHP I use in my day to day life as developer — but that lots of PHP developers seems to ignore — is the <i>Interactive shell</i> of PHP:</p><code>$ php -a</code><p>Like with Python, Ruby, Perl and many others, PHP benefits also of this feature that lets you try copy/pasted code, try some functions/algorithms before integrating them directly into a code or maybe just trying to find some <a href="http://draft.blogger.com/2007/09/are-you-php-5-guru.html">weird behavior</a> of PHP itself.</p><p>Like a real shell, the <i>PHP interactive shell</i> comes with:</p><ul><li>Tab completion</li><li>History</li><li>Integration of all the interesting shortcuts to edit the current line (PG+Up/Down, ALT+Backspace, ALT+Left/Right, CTRL+D,...)</li><li>etc.</li></ul><p>Next time you will:</p><ol><li>Create a PHP file into your DocumentRoot</li><li>Open it with your favorite editor</li><li>Fire up your browser to reach your newly created file</li><li>Show the source code of the generated page</li></ol><p>Maybe this day will you find command line easier :)</p><p>Want to view a demo of all this ? Sure:</p><object height="355" width="425"><param name="movie" value="http://www.youtube.com/v/yywsyrYurC0&rel=1" /><param name="wmode" value="transparent" /><embed src="http://www.youtube.com/v/yywsyrYurC0&rel=1" type="application/x-shockwave-flash" wmode="transparent" height="355" width="425"></embed></object>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com1tag:blogger.com,1999:blog-7449668645955818675.post-64993341555186352172007-09-29T19:16:00.000+02:002007-11-19T21:28:46.961+01:00Zend Certified Engineer, bis repetita<img style="float: right; margin: 0px 0px 10px 10px;" alt="Zend Certified Engineer" src="http://www.zend.com/images/training/zce_logo.gif" border="0" /><img style="float: right; margin: 0px 0px 10px 10px;" alt="Zend Certified Engineer" src="http://www.zend.com/images/training/php5_zce_logo.gif" border="0" />For the second time I successfully passed the <a href="http://www.zend.com/education/zend_php_certification">Zend Engineer Certification</a> :) First time was in June 2004 when I was contacted by Daniel Kushner, previous Zend's Director of Education. It covered PHP 4 and I was really proud to be the second Zend Certified Engineer in Europe.<br /><br />This last friday I passed the certification that covers PHP 5 too. Comparing both certifications I would say that the PHP 5 one covers more topics than PHP 4's. The global difficulty of both certifications is high as you will not be able to pass them if you don't have a <strong>strong</strong> PHP experience. The main difference is that you really have more time to complete the new exam. I remember not having time enough to reply to all questions the first time and now I finished the exam in less than one hour which let me 20 minutes to review all my answers and leaving the room 10 minutes before the end.Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com5tag:blogger.com,1999:blog-7449668645955818675.post-31552202803183530452007-09-29T18:14:00.000+02:002007-11-18T11:07:48.813+01:00aKademy 2008 will take place in Belgium<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://static.kdenews.org/jr/akademy-2008-launch.png"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 200px;" src="http://static.kdenews.org/jr/akademy-2008-launch.png" alt="" border="0" /></a>I am very excited about <a href="http://dot.kde.org/1191001763/">this announcement</a>! <a href="http://akademy2008.kde.org/">aKademy 2008</a>, the annual <a href="http://www.kde.org/">KDE</a> World summit where all the <a href="http://www.kdedevelopers.org/">contributors</a> and enthusiasts have a chance to meet and share ideas, will take place at the <a href="http://www.denayer.wenk.be/index_eng.htm">De Nayer Institute</a> in <a href="http://en.wikipedia.org/wiki/Sint-Katelijne-Waver">Sint-Katelijne-Waver</a> somewhere between Antwerp and Brussels.Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com0tag:blogger.com,1999:blog-7449668645955818675.post-23578796835629102982007-09-13T20:56:00.001+02:002007-10-03T18:07:43.680+02:00Building dynamic SQL queries an elegant wayBuilding dynamic SQL queries — which is very common to handle search forms — is most of the time made in programming languages by concatenating strings this <a href="http://en.wikipedia.org/wiki/SQL_injection">insecure way</a>:<br /><code><span style="color: #000000"><br /><span style="color: #0000BB"><?php<br /></span><span style="color: #007700">require </span><span style="color: #DD0000">'connect.php'</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$firstname </span><span style="color: #007700">= </span><span style="color: #DD0000">'Patrick'</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">$lastname </span><span style="color: #007700">= </span><span style="color: #DD0000">'Allaert'</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$query </span><span style="color: #007700">= </span><span style="color: #DD0000">'SELECT * FROM users WHERE 1'</span><span style="color: #007700">;<br /><br />if (!empty(</span><span style="color: #0000BB">$firstname</span><span style="color: #007700">)) {<br /> </span><span style="color: #0000BB">$query </span><span style="color: #007700">.= </span><span style="color: #DD0000">" AND firstname = '$firstname'"</span><span style="color: #007700">;<br />}<br /><br />if (!empty(</span><span style="color: #0000BB">$lastname</span><span style="color: #007700">)) {<br /> </span><span style="color: #0000BB">$query </span><span style="color: #007700">.= </span><span style="color: #DD0000">" AND lastname = '$lastname'"</span><span style="color: #007700">;<br />}<br /><br />foreach (</span><span style="color: #0000BB">$db</span><span style="color: #007700">-></span><span style="color: #0000BB">query</span><span style="color: #007700">(</span><span style="color: #0000BB">$query</span><span style="color: #007700">, </span><span style="color: #0000BB">PDO</span><span style="color: #007700">::</span><span style="color: #0000BB">FETCH_ASSOC</span><span style="color: #007700">) as </span><span style="color: #0000BB">$row</span><span style="color: #007700">) {<br /> </span><span style="color: #0000BB">print_r</span><span style="color: #007700">(</span><span style="color: #0000BB">$row</span><span style="color: #007700">);<br />}<br /></span><span style="color: #0000BB">?></span><br /></span><br /></code><br />Excluding the fact that this code is vulnerable to <a href="http://en.wikipedia.org/wiki/SQL_injection">SQL injection</a>, which is avoided using either proper escaping or <a href="http://www.php.net/manual/en/function.PDO-prepare.php">prepared statement</a>, we have to admit that appending " WHERE 1" as basic condition and prepending all conditions with " AND " looks more like a hat trick than a proper way of coding although we are used to see this.<br /><br />A proper but still insecure version of this script would be:<br /><code><span style="color: #000000"><br /><span style="color: #0000BB"><?php<br /></span><span style="color: #007700">require </span><span style="color: #DD0000">'connect.php'</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$firstname </span><span style="color: #007700">= </span><span style="color: #DD0000">'Patrick'</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">$lastname </span><span style="color: #007700">= </span><span style="color: #DD0000">'Allaert'</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$query </span><span style="color: #007700">= </span><span style="color: #DD0000">'SELECT * FROM users'</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$cond </span><span style="color: #007700">= array();<br /><br />if (!empty(</span><span style="color: #0000BB">$firstname</span><span style="color: #007700">)) {<br /> </span><span style="color: #0000BB">$cond</span><span style="color: #007700">[] = </span><span style="color: #DD0000">"firstname = '$firstname'"</span><span style="color: #007700">;<br />}<br /><br />if (!empty(</span><span style="color: #0000BB">$lastname</span><span style="color: #007700">)) {<br /> </span><span style="color: #0000BB">$cond</span><span style="color: #007700">[] = </span><span style="color: #DD0000">"lastname = '$lastname'"</span><span style="color: #007700">;<br />}<br /><br />if (</span><span style="color: #0000BB">count</span><span style="color: #007700">(</span><span style="color: #0000BB">$cond</span><span style="color: #007700">)) {<br /> </span><span style="color: #0000BB">$query </span><span style="color: #007700">.= </span><span style="color: #DD0000">' WHERE ' </span><span style="color: #007700">. </span><span style="color: #0000BB">implode</span><span style="color: #007700">(</span><span style="color: #DD0000">' AND '</span><span style="color: #007700">, </span><span style="color: #0000BB">$cond</span><span style="color: #007700">);<br />}<br /><br />foreach (</span><span style="color: #0000BB">$db</span><span style="color: #007700">-></span><span style="color: #0000BB">query</span><span style="color: #007700">(</span><span style="color: #0000BB">$query</span><span style="color: #007700">, </span><span style="color: #0000BB">PDO</span><span style="color: #007700">::</span><span style="color: #0000BB">FETCH_ASSOC</span><span style="color: #007700">) as </span><span style="color: #0000BB">$row</span><span style="color: #007700">) {<br /> </span><span style="color: #0000BB">print_r</span><span style="color: #007700">(</span><span style="color: #0000BB">$row</span><span style="color: #007700">);<br />}<br /></span><span style="color: #0000BB">?></span><br /></span><br /></code><br />In this last version, we made use of the <a href="http://www.php.net/manual/en/function.implode.php">implode function</a> to glue all the conditions together in the case at least one condition is defined. To combine this with security, the next step is to use prepared statement:<br /><code><span style="color: #000000"><br /><span style="color: #0000BB"><?php<br /></span><span style="color: #007700">require </span><span style="color: #DD0000">'connect.php'</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$firstname </span><span style="color: #007700">= </span><span style="color: #DD0000">'Patrick'</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">$lastname </span><span style="color: #007700">= </span><span style="color: #DD0000">'Allaert'</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$query </span><span style="color: #007700">= </span><span style="color: #DD0000">'SELECT * FROM users'</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$cond </span><span style="color: #007700">= array();<br /></span><span style="color: #0000BB">$params </span><span style="color: #007700">= array();<br /><br />if (!empty(</span><span style="color: #0000BB">$firstname</span><span style="color: #007700">)) {<br /> </span><span style="color: #0000BB">$cond</span><span style="color: #007700">[] = </span><span style="color: #DD0000">"firstname = ?"</span><span style="color: #007700">;<br /> </span><span style="color: #0000BB">$params</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$firstname</span><span style="color: #007700">;<br />}<br /><br />if (!empty(</span><span style="color: #0000BB">$lastname</span><span style="color: #007700">)) {<br /> </span><span style="color: #0000BB">$cond</span><span style="color: #007700">[] = </span><span style="color: #DD0000">"lastname = ?"</span><span style="color: #007700">;<br /> </span><span style="color: #0000BB">$params</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$lastname</span><span style="color: #007700">;<br />}<br /><br />if (</span><span style="color: #0000BB">count</span><span style="color: #007700">(</span><span style="color: #0000BB">$cond</span><span style="color: #007700">)) {<br /> </span><span style="color: #0000BB">$query </span><span style="color: #007700">.= </span><span style="color: #DD0000">' WHERE ' </span><span style="color: #007700">. </span><span style="color: #0000BB">implode</span><span style="color: #007700">(</span><span style="color: #DD0000">' AND '</span><span style="color: #007700">, </span><span style="color: #0000BB">$cond</span><span style="color: #007700">);<br />}<br /><br /></span><span style="color: #0000BB">$stmt </span><span style="color: #007700">= </span><span style="color: #0000BB">$db</span><span style="color: #007700">-></span><span style="color: #0000BB">prepare</span><span style="color: #007700">(</span><span style="color: #0000BB">$query</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$stmt</span><span style="color: #007700">-></span><span style="color: #0000BB">execute</span><span style="color: #007700">(</span><span style="color: #0000BB">$params</span><span style="color: #007700">);<br /><br />foreach (</span><span style="color: #0000BB">$stmt</span><span style="color: #007700">-></span><span style="color: #0000BB">fetchAll</span><span style="color: #007700">(</span><span style="color: #0000BB">PDO</span><span style="color: #007700">::</span><span style="color: #0000BB">FETCH_ASSOC</span><span style="color: #007700">) as </span><span style="color: #0000BB">$row</span><span style="color: #007700">) {<br /> </span><span style="color: #0000BB">print_r</span><span style="color: #007700">(</span><span style="color: #0000BB">$row</span><span style="color: #007700">);<br />}<br /></span><span style="color: #0000BB">?></span><br /></span><br /></code>Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com8tag:blogger.com,1999:blog-7449668645955818675.post-90761077739987035422007-09-10T23:00:00.000+02:002007-10-03T18:07:43.681+02:00Are you a PHP 5 guru?If you are able to answer the following questions, you are certainly one of them! Consider the following script:<br /><code><span style="color: rgb(0, 0, 187);"><?php<br /><a href="http://www.php.net/error-reporting" style="color: rgb(0, 0, 187);">error_reporting</a></span><span style="color: rgb(0, 119, 0);">(</span><span style="color: rgb(0, 0, 187);">E_ALL </span><span style="color: rgb(0, 119, 0);">| </span><span style="color: rgb(0, 0, 187);">E_STRICT</span><span style="color: rgb(0, 119, 0);">);<br /></span><span style="color: rgb(0, 0, 187);">$test</span><span style="color: rgb(0, 119, 0);">[<a href="http://www.php.net/isset" style="color: rgb(0, 119, 0);">isset</a>(</span><span style="color: rgb(0, 0, 187);">$test</span><span style="color: rgb(0, 119, 0);">)] = </span><span style="color: rgb(0, 0, 187);">$test</span><span style="color: rgb(0, 119, 0);">;<br /></span><a href="http://www.php.net/var_dump" style="color: rgb(0, 0, 187);">var_dump</a><span style="color: rgb(0, 119, 0);">(</span><span style="color: rgb(0, 0, 187);">$test</span><span style="color: rgb(0, 119, 0);">);<br /></span><span style="color: rgb(0, 0, 187);">?></span></code><br /><ol><li>How many notices are displayed?</li><li>What is the output?</li></ol>Ok, you replied with:<br /><ol><li>1 notice (Notice: Undefined variable: test in ...)</li><li><pre>array(1) {<br />[0]=><br />NULL<br />}</pre></li></ol>Congratulations! You just <span style="font-weight: bold;">failed</span> your <a href="http://www.zend.com/education/zend_php_certification">Zend Engineer Certification</a>.<br />Joke aside, correct answers were:<br /><ol><li>0 notice</li><li><pre>array(1) {<br />[0]=><br />array(1) {<br />[0]=><br />*RECURSION*<br />}<br />}</pre></li></ol>Well, in PHP 4 you would be in the right, but PHP 5 introduces a new <span style="font-style: italic;">feature</span> that let you construct a kind of recursive array... More interesting is that if you used an isset on the right part of the assignment it would return you <span style="font-style: italic;">false</span>!Patrick Allaerthttp://www.blogger.com/profile/08540817341697816823noreply@blogger.com1