tag:blogger.com,1999:blog-38256361509041363112024-03-05T08:49:36.265+01:00Symfony worldSymfony, PHP, MySQL & SVN - tips n'tricks for web developersTomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.comBlogger60125tag:blogger.com,1999:blog-3825636150904136311.post-31007565049101521682012-11-18T16:30:00.000+01:002013-11-09T11:07:13.629+01:00svn log user<p>
SVN log tool does not provide any option to filter output by the user. Using the <code>--username</code> option won't do the job, unfortunately.
</p>
<p>
But you may filter SVN changes commited by a chosen user using the following:
<pre><code data-language="shell">svn log | sed -n '/| username |/,/-----$/ p'
</code></pre>
Basing on that, you may create a bash script, called <code>svn-log-user</code> for example, with the following content:
<pre><code data-language="shell">#!/bin/bash
svn log | sed -n "/| $1 |/,/-----$/ p"</code></pre>
Then, put it inside the <code>/usr/local/bin</code> directory or anywhere that is accessible from the path, run:
<pre><code data-language="shell">echo $PATH</code></pre>
to check which directories are in the path. Then, call it with:
<pre><code data-language="shell">./svn-log-user username</code></pre>
</p>
Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com2tag:blogger.com,1999:blog-3825636150904136311.post-34750354120624498282012-10-14T19:54:00.000+02:002013-04-20T18:57:37.250+02:00Prestashop 1.2 performance bug - customer groups<h2>performance sufferings</h2>
<p>
If your prestashop 1.2 database is populated with few hundred customer records, your e-shop may be in real danger of very bad performance. The problem lies in lack of properly set MySQL indexes and primary keys. Within the time, your shop may be unusable, because MySQL server will harvest enormous piles of data, leaving the end-user to wait for the browser response half a minute or so. This is a serious threat which has been fixed in newer prestashop versions. But some people still run the 1.2 prestashop version since 2009/2010 was the time when prestashop was becoming very popular. This version is concerned, however, to be one of the worst versions of prestashop ever.
</p>
<h2>the problem</h2>
<p>
Group feature is used to define reductions, price displaying methods and some price-related stuff that refer to only a part of the shop. This may be: chosen customers (<code>ps_customer_group</code> table being used for that), chosen product categories (<code>ps_category_group</code>) and so on. Although, it is not commonly used by merchants. Fortunately not, because it makes performance extremely bad.
</p>
<p>
Below is an example of a bad query. I fetched it using <a href="http://dev.mysql.com/doc/refman/5.1/en/slow-query-log.html">MySQL slow logs</a>:
<div style="overflow-x:scroll;">
<pre style="width: 1700px;color:#d1d1d1;background:#000000;"><span style='color:#9999a9; '># Query_time: 20.056626 Lock_time: 0.000182 Rows_sent: 1 Rows_examined: 372383 </span>
<span style='color:#e66170; font-weight:bold; '>SELECT</span> p<span style='color:#d2cd86; '>.</span><span style='color:#b060b0; '>*</span><span style='color:#b060b0; '>,</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>description</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>description_short</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>link_rewrite</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>meta_description</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>meta_keywords</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>meta_title</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>name</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> p<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ean13</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span>i<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_image</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> il<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>legend</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> t<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>rate</span><span style='color:#02d045; '>`</span>
<span style='color:#e66170; font-weight:bold; '>FROM</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ps_product</span><span style='color:#02d045; '>`</span> p
<span style='color:#e66170; font-weight:bold; '>LEFT</span> <span style='color:#e66170; font-weight:bold; '>JOIN</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ps_product_lang</span><span style='color:#02d045; '>`</span> pl <span style='color:#e66170; font-weight:bold; '>ON</span> <span style='color:#d2cd86; '>(</span>p<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_product</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_product</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>AND</span> pl<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_lang</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> <span style='color:#008c00; '>3</span><span style='color:#d2cd86; '>)</span>
<span style='color:#e66170; font-weight:bold; '>LEFT</span> <span style='color:#e66170; font-weight:bold; '>JOIN</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ps_image</span><span style='color:#02d045; '>`</span> i <span style='color:#e66170; font-weight:bold; '>ON</span> <span style='color:#d2cd86; '>(</span>i<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_product</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> p<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_product</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>AND</span> i<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>cover</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> <span style='color:#008c00; '>1</span><span style='color:#d2cd86; '>)</span>
<span style='color:#e66170; font-weight:bold; '>LEFT</span> <span style='color:#e66170; font-weight:bold; '>JOIN</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ps_image_lang</span><span style='color:#02d045; '>`</span> il <span style='color:#e66170; font-weight:bold; '>ON</span> <span style='color:#d2cd86; '>(</span>i<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_image</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> il<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_image</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>AND</span> il<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_lang</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> <span style='color:#008c00; '>3</span><span style='color:#d2cd86; '>)</span>
<span style='color:#e66170; font-weight:bold; '>LEFT</span> <span style='color:#e66170; font-weight:bold; '>JOIN</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ps_tax</span><span style='color:#02d045; '>`</span> t <span style='color:#e66170; font-weight:bold; '>ON</span> t<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_tax</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> p<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_tax</span><span style='color:#02d045; '>`</span>
<span style='color:#e66170; font-weight:bold; '>LEFT</span> <span style='color:#e66170; font-weight:bold; '>JOIN</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ps_category_product</span><span style='color:#02d045; '>`</span> cp <span style='color:#e66170; font-weight:bold; '>ON</span> <span style='color:#d2cd86; '>(</span>cp<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_product</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> p<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_product</span><span style='color:#02d045; '>`</span><span style='color:#d2cd86; '>)</span>
<span style='color:#e66170; font-weight:bold; '>INNER</span> <span style='color:#e66170; font-weight:bold; '>JOIN</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ps_category_group</span><span style='color:#02d045; '>`</span> ctg <span style='color:#e66170; font-weight:bold; '>ON</span> <span style='color:#d2cd86; '>(</span>ctg<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_category</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> cp<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_category</span><span style='color:#02d045; '>`</span><span style='color:#d2cd86; '>)</span>
<span style='color:#e66170; font-weight:bold; '>INNER</span> <span style='color:#e66170; font-weight:bold; '>JOIN</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>ps_customer_group</span><span style='color:#02d045; '>`</span> cg <span style='color:#e66170; font-weight:bold; '>ON</span> <span style='color:#d2cd86; '>(</span>cg<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_group</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> ctg<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_group</span><span style='color:#02d045; '>`</span><span style='color:#d2cd86; '>)</span>
<span style='color:#e66170; font-weight:bold; '>WHERE</span> <span style='color:#d2cd86; '>(</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>reduction_price</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>></span> <span style='color:#008c00; '>0</span> <span style='color:#d2cd86; '>OR</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>reduction_percent</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>></span> <span style='color:#008c00; '>0</span><span style='color:#d2cd86; '>)</span>
<span style='color:#d2cd86; '>AND</span> <span style='color:#d2cd86; '>(</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>reduction_from</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>reduction_to</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>OR</span> <span style='color:#d2cd86; '>(</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>reduction_from</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '><=</span> <span style='color:#00c4c4; '>'2012-10-14'</span> <span style='color:#d2cd86; '>AND</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>reduction_to</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>>=</span> <span style='color:#00c4c4; '>'2012-10-14'</span><span style='color:#d2cd86; '>)</span><span style='color:#d2cd86; '>)</span>
<span style='color:#d2cd86; '>AND</span> p<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>active</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> <span style='color:#008c00; '>1</span>
<span style='color:#d2cd86; '>AND</span> <span style='color:#d2cd86; '>(</span>cg<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_customer</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> <span style='color:#008c00; '>223</span> <span style='color:#d2cd86; '>OR</span> ctg<span style='color:#d2cd86; '>.</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>id_group</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>=</span> <span style='color:#008c00; '>1</span><span style='color:#d2cd86; '>)</span>
<span style='color:#e66170; font-weight:bold; '>ORDER</span> <span style='color:#e66170; font-weight:bold; '>BY</span> <span style='color:#e66170; font-weight:bold; '>RAND</span><span style='color:#d2cd86; '>(</span><span style='color:#d2cd86; '>)</span> <span style='color:#e66170; font-weight:bold; '>LIMIT</span> <span style='color:#008c00; '>1</span><span style='color:#b060b0; '>;</span>
</pre>
</div>
<p>
Such query took me 20 seconds and in my database I've got around 500 orders, less than 300 customers and over 1700 products only. Now I call the MySQL <a href="http://dev.mysql.com/doc/refman/5.1/en/explain.html">EXPLAIN</a>:
</p>
<div style="overflow-x:scroll;">
<pre style='width: 1700px;color:#d1d1d1;background:#000000;'><span style='color:#9999a9; '>+----+-------------+-------+--------+-----------------------------------------+--------------------+---------+--------------------------------------+------+----------------------------------------------+</span>
<span style='color:#d2cd86; '>|</span> id <span style='color:#d2cd86; '>|</span> select_type <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>table</span> <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>type</span> <span style='color:#d2cd86; '>|</span> possible_keys <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>key</span> <span style='color:#d2cd86; '>|</span> key_len <span style='color:#d2cd86; '>|</span> ref <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>rows</span> <span style='color:#d2cd86; '>|</span> Extra <span style='color:#d2cd86; '>|</span>
<span style='color:#9999a9; '>+----+-------------+-------+--------+-----------------------------------------+--------------------+---------+--------------------------------------+------+----------------------------------------------+</span>
<span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> SIMPLE <span style='color:#d2cd86; '>|</span> p <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>ALL</span> <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>PRIMARY</span><span style='color:#b060b0; '>,</span>reduction_date <span style='color:#d2cd86; '>|</span> <span style='color:#d2cd86; '>NULL</span> <span style='color:#d2cd86; '>|</span> <span style='color:#d2cd86; '>NULL</span> <span style='color:#d2cd86; '>|</span> <span style='color:#d2cd86; '>NULL</span> <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1702</span> <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>Using</span> <span style='color:#e66170; font-weight:bold; '>where</span><span style='color:#b060b0; '>;</span> <span style='color:#e66170; font-weight:bold; '>Using</span> <span style='color:#e66170; font-weight:bold; '>temporary</span><span style='color:#b060b0; '>;</span> <span style='color:#e66170; font-weight:bold; '>Using</span> filesort <span style='color:#d2cd86; '>|</span>
<span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> SIMPLE <span style='color:#d2cd86; '>|</span> pl <span style='color:#d2cd86; '>|</span> eq_ref <span style='color:#d2cd86; '>|</span> product_lang_index<span style='color:#b060b0; '>,</span>id_lang <span style='color:#d2cd86; '>|</span> product_lang_index <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>8</span> <span style='color:#d2cd86; '>|</span> ad9bis_prestashop<span style='color:#d2cd86; '>.</span>p<span style='color:#d2cd86; '>.</span>id_product<span style='color:#b060b0; '>,</span>const <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> <span style='color:#d2cd86; '>|</span>
<span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> SIMPLE <span style='color:#d2cd86; '>|</span> cp <span style='color:#d2cd86; '>|</span> ref <span style='color:#d2cd86; '>|</span> category_product_index<span style='color:#b060b0; '>,</span>product_category <span style='color:#d2cd86; '>|</span> product_category <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>4</span> <span style='color:#d2cd86; '>|</span> ad9bis_prestashop<span style='color:#d2cd86; '>.</span>p<span style='color:#d2cd86; '>.</span>id_product <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>3</span> <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>Using</span> <span style='color:#e66170; font-weight:bold; '>where</span> <span style='color:#d2cd86; '>|</span>
<span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> SIMPLE <span style='color:#d2cd86; '>|</span> ctg <span style='color:#d2cd86; '>|</span> ref <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>PRIMARY</span><span style='color:#b060b0; '>,</span>group_category <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>PRIMARY</span> <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>4</span> <span style='color:#d2cd86; '>|</span> ad9bis_prestashop<span style='color:#d2cd86; '>.</span>cp<span style='color:#d2cd86; '>.</span>id_category <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>Using</span> <span style='color:#e66170; font-weight:bold; '>index</span> <span style='color:#d2cd86; '>|</span>
<span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> SIMPLE <span style='color:#d2cd86; '>|</span> i <span style='color:#d2cd86; '>|</span> ref <span style='color:#d2cd86; '>|</span> image_product <span style='color:#d2cd86; '>|</span> image_product <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>4</span> <span style='color:#d2cd86; '>|</span> ad9bis_prestashop<span style='color:#d2cd86; '>.</span>p<span style='color:#d2cd86; '>.</span>id_product <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>3</span> <span style='color:#d2cd86; '>|</span> <span style='color:#d2cd86; '>|</span>
<span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> SIMPLE <span style='color:#d2cd86; '>|</span> cg <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>index</span> <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>PRIMARY</span><span style='color:#b060b0; '>,</span>customer_login<span style='color:#b060b0; '>,</span>id_customer <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>PRIMARY</span> <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>8</span> <span style='color:#d2cd86; '>|</span> <span style='color:#d2cd86; '>NULL</span> <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>238</span> <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>Using</span> <span style='color:#e66170; font-weight:bold; '>where</span><span style='color:#b060b0; '>;</span> <span style='color:#e66170; font-weight:bold; '>Using</span> <span style='color:#e66170; font-weight:bold; '>index</span><span style='color:#b060b0; '>;</span> <span style='color:#e66170; font-weight:bold; '>Using</span> <span style='color:#e66170; font-weight:bold; '>join</span> buffer <span style='color:#d2cd86; '>|</span>
<span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> SIMPLE <span style='color:#d2cd86; '>|</span> il <span style='color:#d2cd86; '>|</span> eq_ref <span style='color:#d2cd86; '>|</span> image_lang_index <span style='color:#d2cd86; '>|</span> image_lang_index <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>8</span> <span style='color:#d2cd86; '>|</span> ad9bis_prestashop<span style='color:#d2cd86; '>.</span>i<span style='color:#d2cd86; '>.</span>id_image<span style='color:#b060b0; '>,</span>const <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> <span style='color:#d2cd86; '>|</span>
<span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> SIMPLE <span style='color:#d2cd86; '>|</span> t <span style='color:#d2cd86; '>|</span> eq_ref <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>PRIMARY</span> <span style='color:#d2cd86; '>|</span> <span style='color:#e66170; font-weight:bold; '>PRIMARY</span> <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>4</span> <span style='color:#d2cd86; '>|</span> ad9bis_prestashop<span style='color:#d2cd86; '>.</span>p<span style='color:#d2cd86; '>.</span>id_tax <span style='color:#d2cd86; '>|</span> <span style='color:#008c00; '>1</span> <span style='color:#d2cd86; '>|</span> <span style='color:#d2cd86; '>|</span>
<span style='color:#9999a9; '>+----+-------------+-------+--------+-----------------------------------------+--------------------+---------+--------------------------------------+------+----------------------------------------------+</span>
<span style='color:#008c00; '>8</span> <span style='color:#e66170; font-weight:bold; '>rows</span> <span style='color:#e66170; font-weight:bold; '>in</span> <span style='color:#e66170; font-weight:bold; '>set</span> <span style='color:#d2cd86; '>(</span><span style='color:#009f00; '>0.02</span> sec<span style='color:#d2cd86; '>)</span>
</pre>
</div>
<p>
As you can see, <code>ALL</code> type is used to seek <code>ps_product</code> records. Afterwards, <code>index</code> type is used to join <code>ps_customer_group</code> records. Such queries are not optimized at all (that's why all old prestashop applications shoud be upgraded to newer versions).
</p>
<h2>the clean solution</h2>
<p>
I spent some time trying to fix proper indexes on database tables. It's quite frustrating, because there are almost 150 tables and you should check most of them. Of course, the biggest problem is on the m:n relational tables (<code>ps_product_tag</code>, <code>ps_category_group</code>, etc.) but even the product table is missing a proper index then <code>reduction_from</code> and <code>reduction_to</code> column values are being compared (as you can see in the example above).
</p>
<p>
So, instead of the clean solution, I chose...
</p>
<h2>the brute-force solution</h2>
<p>
Finally, I decided to cut off (i.e. completely remove) the entire <code>customer group</code> functionality from PHP code level, because neither of two prestashop application owners I work with use the feature and this gave me 100% that prestashop will work as fast as possible. Take a look at the code:
</p>
<div style="overflow-x:scroll;">
<pre style='width:1000px;color:#d1d1d1;background:#000000;'>
<span style='color:#ffffff; background:#000000; '>$sql</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'...</span>
<span style='color:#00c4c4; background:#000000; '>LEFT JOIN `'</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '>_DB_PREFIX_</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>'manufacturer` as m ON (m.`id_manufacturer`= p.`id_manufacturer`)</span>
<span style='color:#00c4c4; background:#000000; '>LEFT JOIN `'</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '>_DB_PREFIX_</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>'category_product` cp ON (cp.`id_product` = p.`id_product`)</span>
<span style='color:#00c4c4; background:#000000; '>INNER JOIN `'</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '>_DB_PREFIX_</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)</span>
<span style='color:#00c4c4; background:#000000; '>'</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$cookie</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>id_customer</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#b060b0; background:#000000; '>?</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'INNER JOIN `'</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '>_DB_PREFIX_</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)'</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>''</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>'</span>
<span style='color:#00c4c4; background:#000000; '>WHERE ('</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$cookie</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>id_customer</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#b060b0; background:#000000; '>?</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'cg.`id_customer` = '</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#e66170; background:#000000; font-weight:bold; '>intval</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$cookie</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>id_customer</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>' OR'</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>''</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; background:#000000; '>AND m.`id_manufacturer` = '</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#e66170; background:#000000; font-weight:bold; '>intval</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$manufacturer</span><span style='color:#d2cd86; background:#000000; '>[</span><span style='color:#00c4c4; background:#000000; '>'id_manufacturer'</span><span style='color:#d2cd86; background:#000000; '>]</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>'</span>
<span style='color:#00c4c4; background:#000000; '>GROUP BY p.`id_product`'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
</div>
</p>
<p>
Look at the fragment using the cookie object:
</p>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#ffffff; background:#000000; '>$cookie</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>id_customer</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#b060b0; background:#000000; '>?</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'--sql wipe out code--'</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>''</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
<p>
PHP checks if user is logged in. If the user is not loggen in (anonymous), PHP won't know which group does this customer belong to, so it ommits the bad SQL clauses. And this makes sense - for anonymous users, prestashop 1.2 works like a charm.
</p>
<p>
You may save yourself a lot of pain by getting rid of those SQL clauses. Grep the classes directory for the cookie phrase:
<pre style="background: black;">
grep -Rn '$cookie->id_customer ?' .
</pre>
You'll have to modify just 6 classes:
<ul>
<li><code>Manufacturer.php</code></li>
<li><code>Product.php</code></li>
<li><code>ProductSale.php</code></li>
<li><code>Search.php</code></li>
<li><code>Supplier.php</code></li>
<li><code>Tag.php</code></li>
</ul>
Below I dump my git repository diff that applies those changes:
</p>
<div style="overflow-x:scroll">
<pre style='width:1600px;height:1000px;color:#d1d1d1;background:#000000;'>commit <span style='color:#00a800; '>32</span>f<span style='color:#00a800; '>6</span>a<span style='color:#00a800; '>9</span>facaf<span style='color:#00a800; '>77</span>ca<span style='color:#00a800; '>9</span>c<span style='color:#00a800; '>8326</span>baf<span style='color:#00a800; '>288187889</span>b<span style='color:#00a800; '>3</span>e<span style='color:#00a800; '>1</span>fc<span style='color:#00a800; '>4</span>
Author: Tomasz Ducin
Date: Sun Oct <span style='color:#00a800; '>14</span> <span style='color:#00a800; '>12</span>:<span style='color:#00a800; '>07</span>:<span style='color:#00a800; '>09</span> <span style='color:#00a800; '>2012</span> +<span style='color:#00a800; '>0200</span>
customer_group removed entirely from all sql queries
<span style='color:#008073; '>diff --git a/classes/Manufacturer.php b/classes/Manufacturer.php</span>
index <span style='color:#00a800; '>1</span>b<span style='color:#00a800; '>9</span>d<span style='color:#00a800; '>5</span>df..e<span style='color:#00a800; '>1</span>cabe<span style='color:#00a800; '>6</span> <span style='color:#00a800; '>100644</span>
<span style='color:#007997; '>--- a/classes/Manufacturer.php</span>
<span style='color:#007997; '>+++ b/classes/Manufacturer.php</span>
@@ -<span style='color:#00a800; '>176</span>,<span style='color:#00a800; '>8</span> +<span style='color:#00a800; '>176</span>,<span style='color:#00a800; '>8</span> @@ class Manufacturer extends ObjectModel
LEFT JOIN `'._DB_PREFIX_.'manufacturer` as m ON (m.`id_manufacturer`= p.`id_manufacturer`)
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#008080; '>- WHERE ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
<span style='color:#00c4c4; '>+ WHERE ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
AND m.`id_manufacturer` = '.intval($manufacturer['id_manufacturer']).'
GROUP BY p.`id_product`';
$result = Db::getInstance()->ExecuteS($sql);
@@ -<span style='color:#00a800; '>251</span>,<span style='color:#00a800; '>9</span> +<span style='color:#00a800; '>251</span>,<span style='color:#00a800; '>9</span> @@ class Manufacturer extends ObjectModel
FROM `'._DB_PREFIX_.'product` p
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE p.id_manufacturer = '.intval($id_manufacturer).'
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)'</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)'</span>
.($active ? ' AND p.`active` = <span style='color:#00a800; '>1</span>' : '')
.' GROUP BY p.`id_product`');
return intval(sizeof($result));
@@ -<span style='color:#00a800; '>270</span>,<span style='color:#00a800; '>9</span> +<span style='color:#00a800; '>270</span>,<span style='color:#00a800; '>9</span> @@ class Manufacturer extends ObjectModel
LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer`
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE p.`id_manufacturer` = '.intval($id_manufacturer).($active ? ' AND p.`active` = <span style='color:#00a800; '>1</span>' : '').'
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY p.`id_product`
ORDER BY '.(($orderBy == 'id_product') ? 'p.' : '').'`'.pSQL($orderBy).'` '.pSQL($orderWay).'
LIMIT '.((intval($p) - <span style='color:#00a800; '>1</span>) * intval($n)).','.intval($n);
<span style='color:#008073; '>diff --git a/classes/Product.php b/classes/Product.php</span>
index <span style='color:#00a800; '>3</span>f<span style='color:#00a800; '>54</span>c<span style='color:#00a800; '>90</span>..<span style='color:#00a800; '>0</span>ffba<span style='color:#00a800; '>15</span> <span style='color:#00a800; '>100644</span>
<span style='color:#007997; '>--- a/classes/Product.php</span>
<span style='color:#007997; '>+++ b/classes/Product.php</span>
@@ -<span style='color:#00a800; '>1006</span>,<span style='color:#00a800; '>10</span> +<span style='color:#00a800; '>1006</span>,<span style='color:#00a800; '>10</span> @@ class Product extends ObjectModel
LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE p.`active` = <span style='color:#00a800; '>1</span>
AND DATEDIFF(p.`date_add`, DATE_SUB(NOW(), INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : <span style='color:#00a800; '>20</span>).' DAY)) > <span style='color:#00a800; '>0</span>
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY p.`id_product`
ORDER BY '.(isset($orderByPrefix) ? pSQL($orderByPrefix).'.' : '').'`'.pSQL($orderBy).'` '.pSQL($orderWay).'
LIMIT '.intval($pageNumber * $nbProducts).', '.intval($nbProducts));
@@ -<span style='color:#00a800; '>1041</span>,<span style='color:#00a800; '>14</span> +<span style='color:#00a800; '>1041</span>,<span style='color:#00a800; '>14</span> @@ class Product extends ObjectModel
LEFT JOIN `'._DB_PREFIX_.'tax` t ON t.`id_tax` = p.`id_tax`
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE (`reduction_price` > <span style='color:#00a800; '>0</span> OR `reduction_percent` > <span style='color:#00a800; '>0</span>)
'.((!$beginning AND !$ending) ?
'AND (`reduction_from` = `reduction_to` OR (`reduction_from` <= \''.pSQL($currentDate).'\' AND `reduction_to` >= \''.pSQL($currentDate).'\'))'
:
($beginning ? 'AND `reduction_from` <= \''.pSQL($beginning).'\'' : '').($ending ? 'AND `reduction_to` >= \''.pSQL($ending).'\'' : '')).'
AND p.`active` = <span style='color:#00a800; '>1</span>
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
ORDER BY RAND()');
if ($row)
@@ -<span style='color:#00a800; '>1090</span>,<span style='color:#00a800; '>9</span> +<span style='color:#00a800; '>1090</span>,<span style='color:#00a800; '>9</span> @@ class Product extends ObjectModel
FROM `'._DB_PREFIX_.'product` p
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE (p.`reduction_price` > <span style='color:#00a800; '>0</span> OR p.`reduction_percent` > <span style='color:#00a800; '>0</span>)
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
AND p.`active` = <span style='color:#00a800; '>1</span>';
$result = Db::getInstance()->getRow($sql);
return intval($result['nb']);
@@ -<span style='color:#00a800; '>1108</span>,<span style='color:#00a800; '>14</span> +<span style='color:#00a800; '>1108</span>,<span style='color:#00a800; '>14</span> @@ class Product extends ObjectModel
LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE (`reduction_price` > <span style='color:#00a800; '>0</span> OR `reduction_percent` > <span style='color:#00a800; '>0</span>)
'.((!$beginning AND !$ending) ?
'AND (`reduction_from` = `reduction_to` OR (`reduction_from` <= \''.pSQL($currentDate).'\' AND `reduction_to` >= \''.pSQL($currentDate).'\'))'
:
($beginning ? 'AND `reduction_from` <= \''.pSQL($beginning).'\'' : '').($ending ? 'AND `reduction_to` >= \''.pSQL($ending).'\'' : '')).'
AND p.`active` = <span style='color:#00a800; '>1</span>
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY p.`id_product`
ORDER BY '.(isset($orderByPrefix) ? pSQL($orderByPrefix).'.' : '').'`'.pSQL($orderBy).'`'.' '.pSQL($orderWay).'
LIMIT '.intval($pageNumber * $nbProducts).', '.intval($nbProducts);
<span style='color:#008073; '>diff --git a/classes/ProductSale.php b/classes/ProductSale.php</span>
index a<span style='color:#00a800; '>55</span>ec<span style='color:#00a800; '>36</span>..<span style='color:#00a800; '>3016557</span> <span style='color:#00a800; '>100644</span>
<span style='color:#007997; '>--- a/classes/ProductSale.php</span>
<span style='color:#007997; '>+++ b/classes/ProductSale.php</span>
@@ -<span style='color:#00a800; '>71</span>,<span style='color:#00a800; '>9</span> +<span style='color:#00a800; '>71</span>,<span style='color:#00a800; '>9</span> @@ class ProductSale
LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = p.`id_tax`)
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE p.`active` = <span style='color:#00a800; '>1</span>
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY p.`id_product`
ORDER BY '.(isset($orderByPrefix) ? $orderByPrefix.'.' : '').'`'.pSQL($orderBy).'` '.pSQL($orderWay).'
LIMIT '.intval($pageNumber * $nbProducts).', '.intval($nbProducts));
@@ -<span style='color:#00a800; '>112</span>,<span style='color:#00a800; '>9</span> +<span style='color:#00a800; '>112</span>,<span style='color:#00a800; '>9</span> @@ class ProductSale
LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (cl.`id_category` = p.`id_category_default` AND cl.`id_lang` = '.intval($id_lang).')
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE p.`active` = <span style='color:#00a800; '>1</span>
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY p.`id_product`
ORDER BY sales DESC
LIMIT '.intval($pageNumber * $nbProducts).', '.intval($nbProducts));
<span style='color:#008073; '>diff --git a/classes/Search.php b/classes/Search.php</span>
index <span style='color:#00a800; '>6811</span>f<span style='color:#00a800; '>69</span>..<span style='color:#00a800; '>1</span>faaf<span style='color:#00a800; '>18</span> <span style='color:#00a800; '>100644</span>
<span style='color:#007997; '>--- a/classes/Search.php</span>
<span style='color:#007997; '>+++ b/classes/Search.php</span>
@@ -<span style='color:#00a800; '>180</span>,<span style='color:#00a800; '>10</span> +<span style='color:#00a800; '>180</span>,<span style='color:#00a800; '>10</span> @@ class Search
LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (p.`id_category_default` = cl.`id_category` AND cl.`id_lang` = '.intval($id_lang).')
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE '.implode(' AND ', $whereArray).'
AND p.active = <span style='color:#00a800; '>1</span>
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY p.`id_product`
ORDER BY position DESC
LIMIT <span style='color:#00a800; '>10</span>';
@@ -<span style='color:#00a800; '>192</span>,<span style='color:#00a800; '>7</span> +<span style='color:#00a800; '>192</span>,<span style='color:#00a800; '>7</span> @@ class Search
$queryResults = '
SELECT SQL_CALC_FOUND_ROWS p.*, pl.`description_short`, pl.`available_now`, pl.`available_later`, pl.`link_rewrite`, pl.`name`,
<span style='color:#008080; '>- t.`rate`, i.`id_image`, il.`legend`, m.`name` AS manufacturer_name '.($cookie->id_customer ? ', cg.`id_group`' : '').'</span>
<span style='color:#00c4c4; '>+ t.`rate`, i.`id_image`, il.`legend`, m.`name` AS manufacturer_name './*($cookie->id_customer ? ', cg.`id_group`' : '').*/'</span>
'.$score.'
FROM '._DB_PREFIX_.'product p
LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = '.intval($id_lang).')
@@ -<span style='color:#00a800; '>202</span>,<span style='color:#00a800; '>10</span> +<span style='color:#00a800; '>202</span>,<span style='color:#00a800; '>10</span> @@ class Search
LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE '.implode(' AND ', $whereArray).'
AND p.active = <span style='color:#00a800; '>1</span>
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY p.`id_product`
'.($orderBy ? 'ORDER BY '.$orderBy : '').($orderWay ? ' '.$orderWay : '').'
LIMIT '.intval(($pageNumber - <span style='color:#00a800; '>1</span>) * $pageSize).','.intval($pageSize);
<span style='color:#008073; '>diff --git a/classes/Supplier.php b/classes/Supplier.php</span>
index <span style='color:#00a800; '>0052572</span>..<span style='color:#00a800; '>904</span>d<span style='color:#00a800; '>15</span>d <span style='color:#00a800; '>100644</span>
<span style='color:#007997; '>--- a/classes/Supplier.php</span>
<span style='color:#007997; '>+++ b/classes/Supplier.php</span>
@@ -<span style='color:#00a800; '>109</span>,<span style='color:#00a800; '>8</span> +<span style='color:#00a800; '>109</span>,<span style='color:#00a800; '>8</span> @@ class Supplier extends ObjectModel
LEFT JOIN `'._DB_PREFIX_.'supplier` as m ON (m.`id_supplier`= p.`id_supplier`)
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#008080; '>- WHERE ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
<span style='color:#00c4c4; '>+ WHERE ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
AND m.`id_supplier` = '.intval($supplier['id_supplier']).'
GROUP BY p.`id_product`';
$result = Db::getInstance()->ExecuteS($sql);
@@ -<span style='color:#00a800; '>170</span>,<span style='color:#00a800; '>9</span> +<span style='color:#00a800; '>170</span>,<span style='color:#00a800; '>9</span> @@ class Supplier extends ObjectModel
FROM `'._DB_PREFIX_.'product` p
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE p.id_supplier = '.intval($id_supplier).'
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)'</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)'</span>
.($active ? ' AND p.`active` = <span style='color:#00a800; '>1</span>' : '')
.' GROUP BY p.`id_product`');
return intval(sizeof($result));
@@ -<span style='color:#00a800; '>189</span>,<span style='color:#00a800; '>9</span> +<span style='color:#00a800; '>189</span>,<span style='color:#00a800; '>9</span> @@ class Supplier extends ObjectModel
LEFT JOIN `'._DB_PREFIX_.'supplier` s ON s.`id_supplier` = p.`id_supplier`
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE p.`id_supplier` = '.intval($id_supplier).($active ? ' AND p.`active` = <span style='color:#00a800; '>1</span>' : '').'
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY p.`id_product`
ORDER BY '.(($orderBy == 'id_product') ? 'p.' : '').'`'.pSQL($orderBy).'` '.pSQL($orderWay).'
LIMIT '.((intval($p) - <span style='color:#00a800; '>1</span>) * intval($n)).','.intval($n);
<span style='color:#008073; '>diff --git a/classes/Tag.php b/classes/Tag.php</span>
index d<span style='color:#00a800; '>2</span>ebbeb..cf<span style='color:#00a800; '>272</span>ea <span style='color:#00a800; '>100644</span>
<span style='color:#007997; '>--- a/classes/Tag.php</span>
<span style='color:#007997; '>+++ b/classes/Tag.php</span>
@@ -<span style='color:#00a800; '>119</span>,<span style='color:#00a800; '>10</span> +<span style='color:#00a800; '>119</span>,<span style='color:#00a800; '>10</span> @@ class Tag extends ObjectModel
LEFT JOIN `'._DB_PREFIX_.'product` p ON p.id_product = pt.id_product
LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = p.`id_product`)
INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
<span style='color:#008080; '>- '.($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').'</span>
<span style='color:#00c4c4; '>+ './*($cookie->id_customer ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)' : '').*/'</span>
WHERE id_lang = '.intval($id_lang).'
AND p.active = <span style='color:#00a800; '>1</span>
<span style='color:#008080; '>- AND ('.($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').' ctg.`id_group` = 1)</span>
<span style='color:#00c4c4; '>+ AND ('./*($cookie->id_customer ? 'cg.`id_customer` = '.intval($cookie->id_customer).' OR' : '').*/' ctg.`id_group` = 1)</span>
GROUP BY t.id_tag
ORDER BY times DESC
LIMIT <span style='color:#00a800; '>0</span>, '.intval($nb));
</pre>
</div>
<p>
If you ever need to undo this change, just uncomment those lines and you're done (but think twice before you go back to customer group joins).
</p>
Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com4tag:blogger.com,1999:blog-3825636150904136311.post-86329493170244844632012-10-08T22:18:00.000+02:002013-04-20T18:57:47.806+02:00Prestashop Category Tree as a Nested Set<p>
<a href="http://en.wikipedia.org/wiki/Nested_set_model">Nested set</a> is one of the approaches to represent a tree in relational databases.
<h2>how it works</h2>
<p>
Take a look at the example pictures from wikipedia:
</p>
<div style="text-align: center;">
<img src="http://upload.wikimedia.org/wikipedia/commons/b/b5/Clothing-hierarchy-traversal-2.svg" />
</div>
<p>
This is just a tree with some numbers attached to node labels. Those numbers make a lot more sense if the tree is presented a nested sets (sets inside sets):
</p>
<div style="text-align: center;">
<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/NestedSetModel.svg/400px-NestedSetModel.svg.png" />
</div>
<p>
Note that reading all numbers from left to right is the ordered sequence of integer numbers. This is how the nested set are built and how the hierarchy is restored basing on those numbers.
</p>
<p>
Let's say, we've got a leaf node inside a tree. If this is just a normal tree (not a nested set), walking through all parents until reaching the root node will cost one subquery per each parent. But if we have a nested set, we can do this all with just one query by manipulating the left/right values of the sets - this is all what nested sets are here for.
</p>
<h2>parent of (a parent of (...)) a node</h2>
<p>
Walking from a specific node to the root node of the tree is used in the product display page. The breadcrumb section shows the path from the root node to the main category the product belongs to (<code>product.id_category_default</code> in the database). The <code>Product::getParentCategories()</code> method is called to find all parents of a chosen category:
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#e66170; font-weight:bold; '>WHERE</span> c<span style='color:#d2cd86; '>.</span>nleft <span style='color:#d2cd86; '><</span> <i>(int)$category['nleft']</i>
<span style='color:#d2cd86; '>AND</span> c<span style='color:#d2cd86; '>.</span>nright <span style='color:#d2cd86; '>></span> <i>(int)$category['nright']</i>
<span style='color:#e66170; font-weight:bold; '>ORDER</span> <span style='color:#e66170; font-weight:bold; '>BY</span> c<span style='color:#d2cd86; '>.</span>nleft
</pre>
You can imagine this as widening the left/right range. Take a look at the picture above: <i>Jackets</i> have the range of <code>6 : 7</code> (<code>7 - 6 = 1</code> which is the smallest possible subset = the leaf). The left value will be decreased (the smaller one) while the right value will be increased (the bigger one). The parent of <i>Jackets</i> is <i>Suits</i> with the range of <code>3 : 8</code> (<code>8 - 3 = 5</code>). <i>Men's</i> category, the parent of <i>Suits</i>, will be found in the same way - decreasing the left value and increasing the right value, <code>2:9</code>. If the left value equals 1, you're already at the root node.
</p>
<h2>children of a node</h2>
<p>
Finding all children of a specific node is done in the opposite way. You take all subsets of a given set. As everything is ordered, all nested subsets will have their left/right values inside the range of the chosen node:
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#e66170; font-weight:bold; '>WHERE</span> c<span style='color:#d2cd86; '>.</span>nleft <span style='color:#d2cd86; '>></span> <i>(int)$category['nleft']</i>
<span style='color:#d2cd86; '>AND</span> c<span style='color:#d2cd86; '>.</span>nright <span style='color:#d2cd86; '><</span> <i>(int)$category['nright']</i>
<span style='color:#e66170; font-weight:bold; '>ORDER</span> <span style='color:#e66170; font-weight:bold; '>BY</span> c<span style='color:#d2cd86; '>.</span>nleft
</pre>
In other words, we'll get all nodes that are situated between the left and the right border of the chosen node area. All nodes that are beyond this area are not the children of the chosen category.
</p>
<p>
Finding all children of a category tree node is used in the <code>Category::getAllChildren()</code> method.
</p>
<h2>nested sets in prestashop</h2>
<p>
Such approach was introduced in category structure in prestashop since version 1.4.0.5, released on December 22, 2010.
</p>
<p>
For anyone having older versions of the platform, prestashop provides special functionality which fills up all nested set data. It is implemented in <code>Category</code> class in <code>Category::regenerateEntireNtree()</code> method.
</p>
<h2>
upgrading
</h2>
<p>
Each new version of prestashop is provided with a small SQL script which enables to update the database structure to fit new functionalities - take a look at the <code>install/sql/upgrade</code> directory. It works just like doctrine migrations in <a href="http://www.symfony-project.org/doctrine/1_2/en/07-Migrations">symfony 1.x</a>. The <code>1.4.0.5.sql</code> script from the directory above includes the following lines:
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#e66170; font-weight:bold; '>ALTER</span> <span style='color:#e66170; font-weight:bold; '>TABLE</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>PREFIX_category</span><span style='color:#02d045; '>`</span> <span style='color:#e66170; font-weight:bold; '>ADD</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>nleft</span><span style='color:#02d045; '>`</span> <span style='color:#bb7977; '>INT</span> UNSIGNED <span style='color:#d2cd86; '>NOT</span> <span style='color:#d2cd86; '>NULL</span> DEFAULT <span style='color:#00c4c4; '>'0'</span> <span style='color:#e66170; font-weight:bold; '>AFTER</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>level_depth</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>;</span>
<span style='color:#e66170; font-weight:bold; '>ALTER</span> <span style='color:#e66170; font-weight:bold; '>TABLE</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>PREFIX_category</span><span style='color:#02d045; '>`</span> <span style='color:#e66170; font-weight:bold; '>ADD</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>nright</span><span style='color:#02d045; '>`</span> <span style='color:#bb7977; '>INT</span> UNSIGNED <span style='color:#d2cd86; '>NOT</span> <span style='color:#d2cd86; '>NULL</span> DEFAULT <span style='color:#00c4c4; '>'0'</span> <span style='color:#e66170; font-weight:bold; '>AFTER</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>nleft</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>;</span>
<span style='color:#e66170; font-weight:bold; '>ALTER</span> <span style='color:#e66170; font-weight:bold; '>TABLE</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>PREFIX_category</span><span style='color:#02d045; '>`</span> <span style='color:#e66170; font-weight:bold; '>ADD</span> <span style='color:#e66170; font-weight:bold; '>INDEX</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>nleftright</span><span style='color:#02d045; '>`</span> <span style='color:#d2cd86; '>(</span><span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>nleft</span><span style='color:#02d045; '>`</span><span style='color:#b060b0; '>,</span> <span style='color:#02d045; '>`</span><span style='color:#d1d1d1; background:#000000; '>nright</span><span style='color:#02d045; '>`</span><span style='color:#d2cd86; '>)</span><span style='color:#b060b0; '>;</span>
</pre>
Those lines create 2 columns which store the left/right values which will be used to traverse the tree from child nodes to their parents.
</p>
<h2>database performance</h2>
<p>
Selects are faster with such structure. But be aware that each modification you make on any category tree node will fire regenerating entire nested set values - prestashop doesn't check where the modified node is - it builds the nested set from scratch (<code>Category::regenerateEntireNtree()</code>). If you have a really big tree, inserting a new category node can take quite a lot of time.
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-63255465279161311182012-10-01T22:03:00.000+02:002012-10-08T20:30:01.776+02:00svn dump subrepo
Subversion tools enable you to dump only chosen parts of the original repository. This may be especially useful, when you want to separate a smaller project out from a bigger thing. <code><a href="http://svnbook.red-bean.com/en/1.7/svn.ref.svndumpfilter.html">svnadmin dump</a></code> will be used along with <code><a href="http://svnbook.red-bean.com/en/1.7/svn.ref.svndumpfilter.html">svndumpfilter</a></code> command.
<code>svndumpfilter</code> must be run using one of the following subcommands: <code><a href="http://svnbook.red bean.com/en/1.7/svn.ref.svndumpfilter.commands.c.include.html">include</a></code> or <code><a href="http://svnbook.red-bean.com/en/1.7/svn.ref.svndumpfilter.commands.c.exclude.html">exclude</a></code>. Depending on what is easier, you may specify which path prefixes (or directories) will be included into new subrepo dump - or which will be excluded.
<p>
For example, let's assume our original repository structure looks like the following:
<pre style='color:#d1d1d1;background:#000000;'>/abc
some content
/def
some content
/ghi
some content
/jkl
some content
</pre>
If we run:
<pre style='color:#d1d1d1;background:#000000;'>$ svndumpfilter include <span style='color:#70318a; '>/abc</span> <span style='color:#70318a; '>/def</span> <span style='color:#9999a9; '># pseudo command</span>
</pre>
our subrepo will consist of:
<pre style='color:#d1d1d1;background:#000000;'>
/abc
some content
/def
some content
</pre>
but if we run:
<pre style='color:#d1d1d1;background:#000000;'>$ svndumpfilter exclude <span style='color:#70318a; '>/abc</span> <span style='color:#9999a9; '># pseudo command</span>
</pre>
our subrepo will consist of:
<pre style='color:#d1d1d1;background:#000000;'>
/def
some content
/ghi
some content
/jkl
some content
</pre>
I wrote <i>pseudo command</i>, since you have to stream the original repository first, so that <code>svndumpfilter</code> can filter its content. Real commands would look like:
<pre style='color:#d1d1d1;background:#000000;'>$ svnadmin dump <span style='color:#70318a; '>/home/tomasz/svn/original_repo</span> <span style='color:#e34adc; '>|</span> svndumpfilter include <span style='color:#70318a; '>/abc</span> <span style='color:#70318a; '>/def</span> --drop-empty-revs --renumber-revs <span style='color:#e34adc; '>></span> subrepo<span style='color:#e66170; font-weight:bold; '>.</span>svndump
$ svnadmin dump <span style='color:#70318a; '>/home/tomasz/svn/original_repo</span> <span style='color:#e34adc; '>|</span> svndumpfilter exclude <span style='color:#70318a; '>/abc</span> --drop-empty-revs --renumber-revs <span style='color:#e34adc; '>></span> subrepo<span style='color:#e66170; font-weight:bold; '>.</span>svndump
</pre>
</p>
<p>
Now let's take a look at some <code>svndumpfilter</code> options:
</p>
<h3>--drop-empty-revs</h3>
<p>
If filtering causes any revision to be empty (i.e., causes no change to the repository), removes these revisions from the final dump file.
</p>
<h3>--renumber-revs</h3>
<p>
Renumbers revisions that remain after filtering.
</p>
<p>
Thanks to above options, the result repository dump will look as if it always consisted of the chosen parts (no empty commits).
</p>
Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-20621245681095550852012-09-26T23:21:00.001+02:002013-04-20T18:58:09.785+02:00PHP code security in prestashop<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8-hefHi06uTwB02MdxQeNkRWbgpGaz0U9SvWT-3RXlVptPcSWT80Zj9RSO-vI-pAb0bWgqwtUb3PGNugi9QvMnNZBscz4VtcxQa3Dld3lrWncIPZYFbR2V8VZsU8Bv_QocSPi1Dx2rlo/s1600/thief.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="267" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8-hefHi06uTwB02MdxQeNkRWbgpGaz0U9SvWT-3RXlVptPcSWT80Zj9RSO-vI-pAb0bWgqwtUb3PGNugi9QvMnNZBscz4VtcxQa3Dld3lrWncIPZYFbR2V8VZsU8Bv_QocSPi1Dx2rlo/s400/thief.jpg" /></a><span class="movie">"Make Your Home Unattractive to Thieves" available at <a href="http://www.doityourself.com/stry/preventingburglaries" style="color:gray;">doityourself.com</a></span></div>
<p>
Recently I had to improve mailing system of an existing <a href="http://www.prestashop.com/">prestashop</a> platform. It was based on <a href="http://www.prestashop.com/en/entwickler-versionen/changelog/1.2.2.0">1.2.2.0 version</a>, released on 2009-08-27. The task was to use a gmail account over SMTP to authorize e-mail sender. It seems quite easy thanks to the <a href="http://swiftmailer.org/">swift mailer</a>, which is included in prestashop package (prestashop 1.2.2.0 used swift 3.3.2). Unfortunately, the task turned out to be very painful since swift mailer was included in prestashop in a very bad way. Each time I tried to connect to the smtp service I was redirected to the project homepage with absolutely no errors, no logs and no any kind of information provided. But this was just the tip of the iceberg - the main problem was (and still is) elsewhere.
</p>
<h2>bubble index.php redirect</h2>
<p>
It's a security technique, adapted by prestashop, trying to block unwanted access to the file structure. The mechanism is based on a simple index.php file containing following code:
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Expires: Mon, 26 Jul 1997 05:00:00 GMT"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Last-Modified: "</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#e66170; background:#000000; font-weight:bold; '>gmdate</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"D, d M Y H:i:s"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>" GMT"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Cache-Control: no-store, no-cache, must-revalidate"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Cache-Control: post-check=0, pre-check=0"</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>false</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Pragma: no-cache"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Location: ../"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>exit</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
All directories inside the project, no matter where or how deep inside the file structure they are, contain this obligatory index.php file. The workflow is really simple - if you make a request (e.g. from a browser) to an existing directory (different from the root directory of the project) you'll step on the index.php that redirects your browser one directory up. There you'll step on another index.php file. You go up and up like a bubble in a water pool, until you get to the top level directory (could be document root), where the index.php controller file resides - it will generate the homepage. The browser will not display the entire redirecting route - it'll just load the homepage.
</p>
<h2>and my problem was</h2>
<p>
... that Swift Mailer stepped on index.php bubble each time a mail was going to be sent. And it was really frustrating and time-consuming to spot such bug.
</p>
<p>
If no authenticators are set before the SMTP session is started, Swift tries to handle it by himself. Swift iterates through all PHP files inside the <code>Swift/Authenticator</code> directory: loads them and tries one by one to authorize. Swift 3.3.2 authenticators directory contains: <code>LOGIN.php</code>, </code>PLAIN.php</code>, <code>CRAMMD5.php</code>, <code>PopB4Smtp.php</code> and... index.php bubble. The solution was to replace:
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>if</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>preg_match</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"</span><span style='color:#02d045; background:#000000; '>/</span><span style='color:#d2cd86; background:#000000; '>^</span><span style='color:#d2cd86; background:#000000; '>[</span><span style='color:#00c4c4; background:#000000; '>A</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#00c4c4; background:#000000; '>Za</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#00c4c4; background:#000000; '>z0</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#00c4c4; background:#000000; '>9-</span><span style='color:#d2cd86; background:#000000; '>]</span><span style='color:#d2cd86; background:#000000; '>+</span><span style='color:#008080; background:#000000; '>\\</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>php</span><span style='color:#008080; background:#000000; '>\$</span><span style='color:#02d045; background:#000000; '>/</span><span style='color:#00c4c4; background:#000000; '>"</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$file</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
with:
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>if</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>preg_match</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"</span><span style='color:#02d045; background:#000000; '>/</span><span style='color:#d2cd86; background:#000000; '>^</span><span style='color:#d2cd86; background:#000000; '>[</span><span style='color:#00c4c4; background:#000000; '>A</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#00c4c4; background:#000000; '>Za</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#00c4c4; background:#000000; '>z0</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#00c4c4; background:#000000; '>9-</span><span style='color:#d2cd86; background:#000000; '>]</span><span style='color:#d2cd86; background:#000000; '>+</span><span style='color:#008080; background:#000000; '>\\</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>php</span><span style='color:#008080; background:#000000; '>\$</span><span style='color:#02d045; background:#000000; '>/</span><span style='color:#00c4c4; background:#000000; '>"</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$file</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>&</span><span style='color:#d2cd86; background:#000000; '>&</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$file</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>!</span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'index.php'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
</p>
<h2>prestashop security</h2>
<p>
As I said before, problem with badly included Swift Mailer is just the tip of the iceberg. The main problem in my opinion is using the "<i>bubble redirect</i>" system...
</p>
<h2>is it secure?</h2>
<p>
Let's focus on the weaknesses that a potential attacker could use. First of all, all PHP and other files are <strong>still accessible from the browser</strong>. Moreover, all those <strong>file paths are very easy to track</strong>. It's not difficult for a PHP developer to find out that a shop is based on the prestashop engine (routing should be enough to determine this, especially that routing is not configurable). Just download a prestashop package to discover entire file structure, which is probably unmodified in most of the shops available online (prestashop's aim is to be used mainly by non-PHP-developers so original structure will probably be kept). If any major prestashop security bug will be tracked in future, all online shops based on older versions will be defenseless.
</p>
<p>
Another danger is the (almost) omnipresent <code>chmod -R 777</code>. Prestashop installer script forces the user to grant <strong>recursive writable permissions</strong> to most of top level directories of the project.
</p>
<h2>how about a better solution?</h2>
<p>
The following question arises: how about virtual hosts and setting the document root to the public directory of the project? Such approach is adopted in most PHP frameworks (e.g. <a href="http://www.jamesfairhurst.co.uk/posts/view/installing_cakephp">cakePHP</a> or all versions of symfony framework: <a href="http://www.symfony-project.org/getting-started/1_4/en/05-Web-Server-Configuration#chapter_05_sub_web_server_configuration">1</a> and <a href="http://www.chrisshennan.com/2012/02/23/installing-and-configuring-symfony2">2</a>). Hiding files (moving them outside the document root) disables direct access to the files, which dramatically reduces the risk of a successful attack. Another possibility is to use <code>.htaccess</code> files among project file structure.
</p>
<p>
The above approach is clearly defined in the <a href="http://php.net/manual/en/security.php#33627">PHP manual security chapter</a>. Ocrow wrote:
</p>
<p><i>
[...] The most robust way to guard against this possibility is to prevent your webserver from calling the library scripts directly, either by moving them out of the document root, or by putting them in a folder configured to refuse web server access. [...]
</i></p>
<p>
Note that it was submitted in 2003...
</p>
<h2>virtual hosts? how about shared hosting limitations?</h2>
<p>
Most shared hosting providers allow their customers access to the <code>/home/user</code> directory where they can put whatever they want and it won't be accessible directly from the web. Only <code>/home/user/public_html</code> (or a similar directory) is accessible. And it is feasible to design the installer script to copy or move the files outside the document root - PHP will need only the autoloader properly set. Or use <code>.htaccess</code> instead.
</p>
<h2>prestashop 1.5 still uses bubble redirect</h2>
<p>
The only thing that changed, concerning above topic, is a three-line comment inside the swift mailer included. If you look at <code>tools/swift/Swift/Authenticator/index.php</code>, you'll find:
</p>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Expires: Mon, 26 Jul 1997 05:00:00 GMT"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Last-Modified: "</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#e66170; background:#000000; font-weight:bold; '>gmdate</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"D, d M Y H:i:s"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>" GMT"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Cache-Control: no-store, no-cache, must-revalidate"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Cache-Control: post-check=0, pre-check=0"</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>false</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>header</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>"Pragma: no-cache"</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '></span>
<span style='color:#9999a9; background:#000000; '>// The header location has been commented because swift do a require() on each .php of this folder</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#9999a9; background:#000000; '>// (and this relocation make it do it recursively on each PrestaShop folder and so PrestaShop PHP file)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#9999a9; background:#000000; '>//header("Location: ../");</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>exit</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
<p>
Well, this is not a solution. It's just a problem workaround. I understand, that a PHP framework is a complex tool and is used by programmers who can adopt webserver features, chmod directories and so on - and e-commerce package should be easy enough to be installed by an average internet user. But "<i>bubble index.php redirect</i>" is not the best way to secure the application code.
</p>
<h2>it doesn't mean that prestashop is bad, though...</h2>
<p>
I want to make it clear: <strong>I do appreciate all the hard work the prestashop team has done already</strong> (really)! Prestashop is becoming more and more popular - it's a great success. And I'm a prestashop user/developer after all :). Anyway, I strongly encourage the developers team to discuss the security topic and the installer script.
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-23724308751142805622012-09-01T22:36:00.000+02:002013-04-20T18:46:16.299+02:00symfony cron task logging example<h2>cron task logging</h2>
<p>
Some time ago <a href="http://symfony-world.blogspot.com/2010/09/symfony-crons-and-cron-task-logging.html">I wrote</a> about a magnificent plugin managing cron task runtime, <a href="http://www.symfony-project.org/plugins/sfTaskLoggerPlugin">sfTaskLoggerPlugin</a> and I promised to provide some real world examples of the plugin's usage. The time has come, so let's go on!
</p>
<h2>archiving outdated products</h2>
<p>I'll start with an easy functionality which is executed each day using a cron task. <code>ProductManager</code> class is just a high-level model class, which is separated from ORM business logic, since it has nothing to do with Doctrine directly. The <code>archiveProducts</code> method fetches all products that meet some specific criteria (in this case - products that are not active no more). The first lines use the ORM-level layer to fetch Doctrine records. Then, all products are again checked against another criteria, inside a loop, and if any pass, they're permanently archived. The result of this method is an array of archived product IDs - this is very important data for the task logger. Take a look at the following code:
</p>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>class</span><span style='color:#ffffff; background:#000000; '> ProductManager</span>
<span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#e66170; background:#000000; font-weight:bold; '>static</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>public</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> archiveProducts</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$products</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> Doctrine</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>getTable</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'Product'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>findProductsQuery</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>execute</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>array</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>foreach</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$products</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>as</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$product</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$availability_history</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$product</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>getAvailabilityHistory</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>getLast</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#e66170; background:#000000; font-weight:bold; '>if</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$availability_history</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>shouldBeArchived</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>        </span><span style='color:#ffffff; background:#000000; '>$product</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>archive</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>        </span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#d2cd86; background:#000000; '>[</span><span style='color:#d2cd86; background:#000000; '>]</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$product</span><span style='color:#d2cd86; background:#000000; '>[</span><span style='color:#00c4c4; background:#000000; '>'id'</span><span style='color:#d2cd86; background:#000000; '>]</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>return</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
<p>The model layer is done, now it's time to prepare the controller layer - and this is the main thing here. Let's start with implementing the command line symfony task. We create a <code>lib/task/archiveProductsTask.class.php</code> file:<br />
</p>
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#f6c1d0; background:#000000; '><?php</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>require_once</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>dirname</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#007d45; background:#000000; '>__FILE__</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'/../../plugins/sfTaskLoggerPlugin/lib/task/sfBaseTaskLoggerTask.class.php'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>class</span><span style='color:#ffffff; background:#000000; '> archiveProductsTask </span><span style='color:#e66170; background:#000000; font-weight:bold; '>extends</span><span style='color:#ffffff; background:#000000; '> sfBaseTaskLoggerTask</span>
<span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
<p>We will put all necessary task stuff here. Note that our new custom task <code>archiveProductsTask</code> class extends the plugin <code>sfBaseTaskLoggerTask</code> class. And plugin <code>sfBaseTaskLoggerTask</code> class extends the base symfony <code>sfBaseTask</code> class (without <code>sfTaskLoggerPlugin</code>, a custom task would just directly extend <code>sfBaseTask</code>). Thanks to it, you may adapt the <code>sfBaseTaskLoggerTask</code> class to meet your needs, if you want some custom mechanisms to be available in all your custom tasks.</p>
<p>
We define the constants that would be used to state the return code of a task run:
</p>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>const</span><span style='color:#ffffff; background:#000000; '> ERROR_CODE_FAILURE </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#008c00; background:#000000; '>1</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>const</span><span style='color:#ffffff; background:#000000; '> ERROR_CODE_SUCCESS </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#008c00; background:#000000; '>1</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
<p>
Now let's define the symfony task standards - task name, namespace and descriptions, which are available in the command line:
</p>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>protected</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> configure</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>parent</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>configure</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>namespace</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'cron'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>name</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'archiveProducts'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>briefDescription</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'Mark products as archived'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>detailedDescription</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#02d045; background:#000000; '><<<</span><span style='color:#e34adc; background:#000000; '>EOF</span><span style='color:#00c4c4; background:#000000; '></span>
<span style='color:#00c4c4; background:#000000; '>The [cron:archiveProducts|INFO] task finds all products that were</span>
<span style='color:#00c4c4; background:#000000; '>for a given amount of time and sets their status to archival. Call it</span>
<span style='color:#00c4c4; background:#000000; '>with:</span>
<span style='color:#00c4c4; background:#000000; '> </span>
<span style='color:#00c4c4; background:#000000; '>  [php symfony cron:archiveProducts|INFO].</span>
<span style='color:#e34adc; background:#000000; '>EOF</span><span style='color:#00c4c4; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
<p>
Having this class defined, you may run:
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#e66170; font-weight:bold; '>.</span><span style='color:#70318a; '>/symfony</span>
</pre>
in your project root directory to find, that a new command <code>cron:archiveProducts</code> is now available. Simply, run:
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#e66170; font-weight:bold; '>.</span><span style='color:#70318a; '>/symfony</span> cron<span style='color:#d2cd86; '>:</span>archiveProducts
</pre>
</p>
<p>
And here comes the task logger part of the task implementation. Just to remind - each time the <code>cron:archiveProducts</code> is executed, a single <code>task_logger</code> record is inserted into the database. This record holds all important information about the task runtime, these are:
<ul>
<li>task name</li>
<li>task arguments</li>
<li>task options</li>
<li>records processed</li>
<li>records not processed</li>
<li>start time</li>
<li>end time</li>
<li>task is still running</li>
<li>task has successfuly finished</li>
<li>error code</li>
<li>comment</li>
</ul>
and some more. Of course, you may modify it and do whatever you want (add new or remove existing information) - these are just the most basic information each task execution should log somewhere.
</p>
<p>
Let's implement the comment generator - you may either generate a plain text comment or even HTML code (if you want lists or links to be nicely displayed). You need to consider it yourself, since big amounts of HTML, generated every day or even more frequently, will use lots of disk space in MySQL, so that your database grows bigger and bigger each day. Think about that ;).
</p>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>protected</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> generateComment</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>''</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$count</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>count</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>if</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$count</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#008c00; background:#000000; '>0</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'Products changed into archival<ul>'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#e66170; background:#000000; font-weight:bold; '>foreach</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>as</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$product</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>        </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'<li>'</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '>$product</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>'</li>'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'</ul>'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>task</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setCountProcessed</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$count</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>return</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
<p>
And finally, let's implement the rest of task logger logic. The most important function here is the <code>doProcess</code>. Everything is done inside the try-catch block. First, the model layer is called to do the data processing stuff. Once the data is processed, we may interpret the results. We generate the comments (basing on the result array), we set the error code and set the <code>is_ok</code> field if no exceptions were raised. If any problem was encountered, <code>is_ok</code> has to be set to false, using <code>setNOk()</code> method:
</p>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>protected</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> doProcess</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$arguments</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>array</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$options</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>array</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>try</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> ProductManager</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>archiveProducts</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>task</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setComments</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>generateComment</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>task</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setErrorCode</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>self</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>ERROR_CODE_SUCCESS</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setOk</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>catch</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>Exception </span><span style='color:#ffffff; background:#000000; '>$e</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>task</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setErrorCode</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>self</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>ERROR_CODE_FAILURE</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setNOk</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$e</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
</pre>
<p>Our new custom task class looks like this now:
</p>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>require_once</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>dirname</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#007d45; background:#000000; '>__FILE__</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'/../../plugins/sfTaskLoggerPlugin/lib/task/sfBaseTaskLoggerTask.class.php'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#e66170; background:#000000; font-weight:bold; '>class</span><span style='color:#ffffff; background:#000000; '> archiveProductsTask </span><span style='color:#e66170; background:#000000; font-weight:bold; '>extends</span><span style='color:#ffffff; background:#000000; '> sfBaseTaskLoggerTask</span>
<span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#e66170; background:#000000; font-weight:bold; '>const</span><span style='color:#ffffff; background:#000000; '> ERROR_CODE_FAILURE </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#008c00; background:#000000; '>1</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#e66170; background:#000000; font-weight:bold; '>const</span><span style='color:#ffffff; background:#000000; '> ERROR_CODE_SUCCESS </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#008c00; background:#000000; '>1</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#e66170; background:#000000; font-weight:bold; '>protected</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> configure</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>parent</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>configure</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>namespace</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'cron'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>name</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'archiveProducts'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>briefDescription</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'Mark products as archived'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>detailedDescription</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#02d045; background:#000000; '><<<</span><span style='color:#e34adc; background:#000000; '>EOF</span><span style='color:#00c4c4; background:#000000; '></span>
<span style='color:#00c4c4; background:#000000; '>The [cron:archiveProducts|INFO] task finds all products that were</span>
<span style='color:#00c4c4; background:#000000; '>for a given amount of time and sets their status to archival. Call it</span>
<span style='color:#00c4c4; background:#000000; '>with:</span>
<span style='color:#00c4c4; background:#000000; '> </span>
<span style='color:#00c4c4; background:#000000; '>  [php symfony cron:archiveProducts|INFO].</span>
<span style='color:#e34adc; background:#000000; '>EOF</span><span style='color:#00c4c4; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#e66170; background:#000000; font-weight:bold; '>protected</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> generateComment</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>''</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$count</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>count</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>if</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$count</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#008c00; background:#000000; '>0</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'Produkty zmienione na archiwalne<ul>'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#e66170; background:#000000; font-weight:bold; '>foreach</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>as</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$product</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>        </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'<li>'</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#ffffff; background:#000000; '>$product</span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#00c4c4; background:#000000; '>'</li>'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>.</span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'</ul>'</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>task</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setCountProcessed</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$count</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>return</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$comment</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '> </span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#e66170; background:#000000; font-weight:bold; '>protected</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> doProcess</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$arguments</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>array</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$options</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>array</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>try</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> ProductManager</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>archiveProducts</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>task</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setComments</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>generateComment</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$result</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>task</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setErrorCode</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>self</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>ERROR_CODE_SUCCESS</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setOk</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>catch</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>Exception </span><span style='color:#ffffff; background:#000000; '>$e</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>task</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setErrorCode</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>self</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>ERROR_CODE_FAILURE</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setNOk</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$e</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
</pre><br />
<h2>one example is enough. Think about the new possibilities</h2><p>I'm not going to give dozens of similar examples of code implementation, since the task logger plugin logic would be mostly the same.</p><p>If you have a big symfony 1.x application, I strognly encourage you to install the <a href="http://www.symfony-project.org/plugins/sfTaskLoggerPlugin">sfTaskLoggerPlugin</a> and redefine some of your task, so that they would be logged each time they're executed. After some time take a look at your task logs - you'll see how many information you can get:
<ul>
<li>analyse your task runtime performance</li>
<li>analyse how many times task have failed</li>
<li>analyse how many records are being processed</li>
<li>analyse what time the task processes the most data and what time there is no processed data</li>
<li>implement another mechanism to monitor all your task logs and alarm, when any task fails (check the <code>is_running</code> against <code>is_ok</code> task_logger record fields)</li>
</ul>
and so on. Give it a try! ;)
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-32905608532985429492012-08-27T22:30:00.000+02:002013-04-20T18:48:53.835+02:00symfony event example<h2>Symfony events</h2><br />
<a href="http://symfonylab.pl/548/symfony-event-przyklad-plugin">see this article in Polish (symfony event na przykładzie sfDoctrineGuardLoginHistoryPlugin)</a><br />
<br />
<p>In a nutshell, events are the mechanisms which connect implemented functionalities with kinda' label (just a chosen name), e.g. "xyz". During the script execution there goes a message: "attention, everyone! xyz!" - and right after that, all functionalities linked to this label are executed. All this is automatic, but you have to define (in a configuration file of the whole project or of a plugin) which events are linked with which functionalities.<br />
</p><br />
<p>For example, a customer submitting an order in an E-commerce application forces the following things to happen:<br />
<ul><li>saving all order data in the database</li>
<li>sending an E-mail containing all important order data (including payment, shipment, etc.) to the customer</li>
<li>assigning the customer to a chosen customer group, depending on the type of assortment he has just bought</li>
<li>creating a reminder for the customer service in order to persuade the customer to buy something again - the reminder becomes active for a specific employee e.g. one month after the first order has been submitted</li>
</ul>Thanks to this example, we can see how many different mechanisms can be triggered by performing an action. Now the question is - hot to implement it?<br />
<ol><li>of course, we can create elegant methods (in OOP style) and call them each time when dealing with a <i>customer that submits an order</i></li>
<li>or we can use our custom events to handle this: first we define our events, we link them to corresponding methods in appropriate classes - and finally, in each place where <i>the customer submits the order</i>, we register the event (what will trigger the rest)</li>
</ol></p><br />
<p>In my opinion, the biggest advantage of the second solution is the <strong>convenience and flexibility</strong>. After we've implemented our custom events and methods, triggering the entire mechanism in a new place in the code is just one line ("attention, everyone! xyz!"). Moreover, we gain the <strong>clearness of the code</strong>, seen from the top level of the entire project (especially when executing one action has to trigger many other actions to be executed - and this happens very often in big projects). The first solution - which is just a chain of method calls, one after another - can make the businness logic very sophisticated and difficult to understand at a glance. And it will surely make the business logic harder to improve. And finally, each event is some kind of a <strong>gate that leads you to anything else in the project</strong>. It's just like placing a door somewhere in the wall - you can enter the room very fast and easily, otherwise you would need to ruin the wall to get in.<br />
</p><br />
<h2>events in sfDoctrineGuardLoginHistoryPlugin</h2><br />
<p>Symfony has lots of built-in events (<a href="http://www.symfony-project.org/reference/1_4/en/15-Events#chapter_15_events">symfony 1.4 docs</a>). One of them, <code>user.change_authentication</code> - has been used in sfDoctrineGuardLoginHistoryPlugin. The event is connected to a custom method in the <a href="http://trac.symfony-project.org/browser/plugins/sfDoctrineGuardLoginHistoryPlugin/trunk/config/sfDoctrineGuardLoginHistoryPluginConfiguration.class.php">config/sfDoctrineGuardLoginHistoryPluginConfiguration</a> configuration file:<br />
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>public</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> initialize</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$</span><span style='color:#e66170; background:#000000; font-weight:bold; '>this</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>dispatcher</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>connect</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'user.change_authentication'</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>array</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'UserLoginHistoryTable'</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'writeLoginHistory'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
</pre><br />
According to the docs, an event is notified (it takes place) <i>whenever the user authentication status changes</i>. This event is connected with only one method, <a href="http://trac.symfony-project.org/browser/plugins/sfDoctrineGuardLoginHistoryPlugin/trunk/lib/model/doctrine/PluginUserLoginHistoryTable.class.php">UserLoginHistoryTable</a>::writeLoginHistory(), which saves login/logout entry in the database for any user:<br />
<br />
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; background:#000000; font-weight:bold; '>static</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>public</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> writeLoginHistory</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>sfEvent </span><span style='color:#ffffff; background:#000000; '>$event</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$sessionUser</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$event</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>getSubject</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$params</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$event</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>getParameters</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>if</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#e66170; background:#000000; font-weight:bold; '>true</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$params</span><span style='color:#d2cd86; background:#000000; '>[</span><span style='color:#00c4c4; background:#000000; '>'authenticated'</span><span style='color:#d2cd86; background:#000000; '>]</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$userId</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$sessionUser</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>getGuardUser</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>id</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$sessionUser</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>setAttribute</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'user_id'</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$userId</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'sfDoctrineGuardLoginHistoryPlugin'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#e66170; background:#000000; font-weight:bold; '>self</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>createHistoryEntry</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'login'</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$userId</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#e66170; background:#000000; font-weight:bold; '>else</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#ffffff; background:#000000; '>$userId</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$sessionUser</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#ffffff; background:#000000; '>getAttributeHolder</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#e66170; background:#000000; font-weight:bold; '>remove</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'user_id'</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>null</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#00c4c4; background:#000000; '>'sfDoctrineGuardLoginHistoryPlugin'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>      </span><span style='color:#e66170; background:#000000; font-weight:bold; '>self</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '>createHistoryEntry</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'logout'</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$userId</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#e66170; background:#000000; font-weight:bold; '>protected</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>static</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>function</span><span style='color:#ffffff; background:#000000; '> createHistoryEntry</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#ffffff; background:#000000; '>$state</span><span style='color:#d2cd86; background:#000000; '>,</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$userId</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>{</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$history</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>new</span><span style='color:#ffffff; background:#000000; '> UserLoginHistory</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$history</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>state</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$state</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$history</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>user_id</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#ffffff; background:#000000; '>$userId</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$history</span><span style='color:#d2cd86; background:#000000; '>-></span><span style='color:#ffffff; background:#000000; '>ip</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#d2cd86; background:#000000; '>=</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>getenv</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'HTTP_X_FORWARDED_FOR'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#b060b0; background:#000000; '>?</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>getenv</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'HTTP_X_FORWARDED_FOR'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#b060b0; background:#000000; '>:</span><span style='color:#ffffff; background:#000000; '> </span><span style='color:#e66170; background:#000000; font-weight:bold; '>getenv</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#00c4c4; background:#000000; '>'REMOTE_ADDR'</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>    </span><span style='color:#ffffff; background:#000000; '>$history</span><span style='color:#d2cd86; background:#000000; '>-</span><span style='color:#d2cd86; background:#000000; '>></span><span style='color:#e66170; background:#000000; font-weight:bold; '>save</span><span style='color:#d2cd86; background:#000000; '>(</span><span style='color:#d2cd86; background:#000000; '>)</span><span style='color:#b060b0; background:#000000; '>;</span><span style='color:#ffffff; background:#000000; '></span>
<span style='color:#ffffff; background:#000000; '>  </span><span style='color:#b060b0; background:#000000; '>}</span><span style='color:#ffffff; background:#000000; '></span>
</pre></p><br />
<p>As a result, we don't have to dig deep into the sfDoctrineGuardPlugin and modify the code to call the method above inside login/logout mechanisms. In fact, we don't have to know anything about . The only thing we need to know is that the <code>user.change_authentication</code> event is notified during user login/logout and we connect our method with this event - and everything works fine. Easy, fast and effective!<br />
</p><br />
<p>Predefined symfony events are some kind of an interface we can use very easily, as we can see in the plugin above (symfony provides us with many doors we can open with no need to destroy the walls to get in). It's a good idea to create custom events in bigger projects and connect them to the mechanisms. Then, enabling/disabling a chosen functionality is just a matter of adding/removing on line of code.<br />
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-83596994372483640062012-06-09T15:19:00.000+02:002013-04-20T20:47:48.934+02:00Command line SVN editor<p>
For those of you who still use Subversion as the software versioning system, setting <code>SVN_EDITOR</code> option will be important sooner or later.
</p>
<p>
SVN properties such as <a href="http://svnbook.red-bean.com/en/1.0/ch07s03.html"><code>svn:externals</code></a> or <a href="http://svnbook.red-bean.com/en/1.1/ch07s02.html"><code>svn:ignore</code></a> need to be set manually by the developer. To do this, run <a href="http://svnbook.red-bean.com/en/1.1/re20.html"><code>svn propedit</code></a> or <a href="http://svnbook.red-bean.com/en/1.0/re23.html"><code>svn propset</code></a> command (IDEs such as NetBeans have built-in SVN support which does the same stuff). If you're working in the command line, run one of the following commands:
</p>
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#904050; '>export</span> SVN_EDITOR<span style='color:#d2cd86; '>=</span>vim
</pre>
or
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#904050; '>export</span> SVN_EDITOR<span style='color:#d2cd86; '>=</span>nano
</pre>
or
<pre style='color:#d1d1d1;background:#000000;'><span style='color:#904050; '>export</span> SVN_EDITOR<span style='color:#d2cd86; '>=</span>mcedit <i>(or whatever)</i>
</pre>
<p>
This sets the SVN editor for the current user session. If you want to store this option permanently (to be set whenever you log in to your machine), create/edit the <code>~/.bash_profile</code> file and put the same content there.
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-46724875490603980122012-02-19T21:07:00.000+01:002013-04-20T19:02:26.670+02:00(symfony) multiple select default value<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqOe0UYNVha9DZlLMMpzdBUWASZiiyP3TIWOE-luBw9eAamqqmxw5r-b8s18jMlRz-LHXaHRKFlayGiPxxkHHr5z9mvV6oAUeA8aQJPkyt_kgFMTt7WRklvV0s8M8eqLrb7yVd20KHusY/s1600/default_multiple_form.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqOe0UYNVha9DZlLMMpzdBUWASZiiyP3TIWOE-luBw9eAamqqmxw5r-b8s18jMlRz-LHXaHRKFlayGiPxxkHHr5z9mvV6oAUeA8aQJPkyt_kgFMTt7WRklvV0s8M8eqLrb7yVd20KHusY/s576/default_multiple_form.png" /></a><span class="movie">Scene from "Pulp Fiction" by Quentin Tarantino (1994)</span></div>
<p>
Symfony framework provides an easy way of defining default form values (you can find <a href="http://symfony-world.blogspot.com/2011/11/symfony-basics-form-default-values.html">related post here</a>). But you may find problems to define default values of a multiple select (m:n relation) widget.
</p>
<h2>example</h2>
<p>
We are given two models:
<ul>
<li><i>Offer</i> - representing a business offer which is available online</li>
<li><i>Localization</i> - representing geographical region</li>
</ul>
Two models above share a typical m:n relation, represented by <i>OfferLocalization</i> model, which means that a specific Offer is available in chosen regions.
</p>
<p>
We want the offer form to display default values for the localizations_list widget (the OfferLocalization m:n relation). Let's say, we want Localizations 3 and 5 (by id) to be the default ones, just like in the picture below (showing regions in Poland):
</p>
<img src="http://img692.imageshack.us/img692/2562/defaultiw.png" />
<p>
Unfortunately, the following code will not work:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setDefault</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'localizations_list'</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(204, 102, 204);">3</span><span style="color: rgb(51, 153, 51);">,</span><span style="color: rgb(204, 102, 204);">5</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre>
in the <i>configure()</i> method since the <i>BaseOfferForm</i> class has the <i>public function updateDefaultsFromObject()</i> method implemented (generated by Doctrine):
<pre class="php" style="font-family: monospace;"> <span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> updateDefaultsFromObject<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
parent<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">updateDefaultsFromObject</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/isset"><span style="color: rgb(153, 0, 0);">isset</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">widgetSchema</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'localizations_list'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setDefault</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'localizations_list'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">object</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">Localizations</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getPrimaryKeys</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
</p>
<h2>solution</h2>
<p>
You can simply override this method in the OfferForm class:
<pre class="php" style="font-family: monospace;"> <span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> updateDefaultsFromObject<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
parent<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">updateDefaultsFromObject</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">isNew</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setDefault</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'localizations_list'</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(204, 102, 204);">3</span><span style="color: rgb(51, 153, 51);">,</span><span style="color: rgb(204, 102, 204);">5</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
and you're done!
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-47739327326323399892012-02-13T00:17:00.003+01:002013-04-20T18:44:39.600+02:00excellent modelling tool: cacoo<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyoOyKjeVeckZRloNoyqnlYTEtS6Y0B6HECWdxkUn-yhQCiNSY02JsiR4N1yEkJhEEV4D_aSz8cgh3Q4H_I99DQTGHx4ODUvDxxFtaHRIbtvVdOvEbE8JvTuVb6EeDMdlpwBJx5jJt3zc/s1600/diagrams.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyoOyKjeVeckZRloNoyqnlYTEtS6Y0B6HECWdxkUn-yhQCiNSY02JsiR4N1yEkJhEEV4D_aSz8cgh3Q4H_I99DQTGHx4ODUvDxxFtaHRIbtvVdOvEbE8JvTuVb6EeDMdlpwBJx5jJt3zc/s576/diagrams.png" /></a><span class="movie">Scene from the epic "Braveheart" by Mel Gibson (1995)</span></div>
<p>
This time I want to present an excellent visual tool I've started using recently to improve my projects' documentation. It's the <a href"http://cacoo.com">Cacoo</a> online diagram editor, which is a professional flash tool.
</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgemqYkl8ChnxHIghaoMOzeL5gLqZe4pSEvTdwluIlNwbByd1IQqw9DOnryy_H2Aih5o6IB1uvP2Q3NduzBZReLNTVH4dcuPap_PbhxW23vkWs_hCXACRoCLvrDoC-QTT8TdX0z3hLTzkU/s1600/cacoo.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="89" width="265" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgemqYkl8ChnxHIghaoMOzeL5gLqZe4pSEvTdwluIlNwbByd1IQqw9DOnryy_H2Aih5o6IB1uvP2Q3NduzBZReLNTVH4dcuPap_PbhxW23vkWs_hCXACRoCLvrDoC-QTT8TdX0z3hLTzkU/s400/cacoo.png" /></a></div>
<p>
The basic concept is very easy: <b>you create graphic diagrams</b>. You may structure them in folders and sheets (don't worry, each country should have its own translation already - I'm using Polish version):
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFUgxOjXjohBfUAWVeyFxR6fCh4O80BE5X4-RZ29cTYAId7XuvWXSheuVGOoAg7fU7N6FawfsCcEw87armNjCYPxX4-FnYe-fO8ir8LngfLWhYWVkBGVUmIq5Z6dhZZaRWCuqm-Y3T8ME/s1600/diagram2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="320" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFUgxOjXjohBfUAWVeyFxR6fCh4O80BE5X4-RZ29cTYAId7XuvWXSheuVGOoAg7fU7N6FawfsCcEw87armNjCYPxX4-FnYe-fO8ir8LngfLWhYWVkBGVUmIq5Z6dhZZaRWCuqm-Y3T8ME/s400/diagram2.png" /></a></div>
There's a history of all changes done on each diagram:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2jtwcxlVBd_dfvbqjbPCwWCjBBaUP2W8_J_7JRkh1Ux0kime74tKmZ7ZcGtKgnPMe8r8182sccXt50q5J67ITtAz5a0F3MRN8jpZfuDl7qQMDzoihW3yhpLJQJxlB-ljNcqcSvTqckig/s1600/diagram1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="250" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2jtwcxlVBd_dfvbqjbPCwWCjBBaUP2W8_J_7JRkh1Ux0kime74tKmZ7ZcGtKgnPMe8r8182sccXt50q5J67ITtAz5a0F3MRN8jpZfuDl7qQMDzoihW3yhpLJQJxlB-ljNcqcSvTqckig/s400/diagram1.png" /></a></div>
Creating a diagram usually starts with creating your own, custom objects which represent something. You may use standard, predefined objects or import any image from the internet to combine new objects:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8mvTX1YjxSzf1CiZBKDmdmUaPJ4AFAJHlsQObBvOSnBlan93yW_Hx5Ew6kV6tFswzspaF2hIa8Ud-HdqieLDtrmEBrghJQa3a64VgE3oe8_JR7c8mWUABHFr9rxhDrjuAEHafJTRYbxo/s1600/diagrams3.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="160" width="236" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8mvTX1YjxSzf1CiZBKDmdmUaPJ4AFAJHlsQObBvOSnBlan93yW_Hx5Ew6kV6tFswzspaF2hIa8Ud-HdqieLDtrmEBrghJQa3a64VgE3oe8_JR7c8mWUABHFr9rxhDrjuAEHafJTRYbxo/s400/diagrams3.png" /></a></div>
You may resize or change the angle of an object:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqamrU4TkQ2vYjH7X10GFgyPCk5lpoDGksHJlBv3Jvhny_P2jFYqd3e2llKL_rW7G1npN6eSY9XgCODzPo-RBVldJf6BcJUjcXgi8dtYN_h2_rTtv8McwUT7XZfurYkLUAj6s_3S0By-o/s1600/diagram4.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="256" width="313" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqamrU4TkQ2vYjH7X10GFgyPCk5lpoDGksHJlBv3Jvhny_P2jFYqd3e2llKL_rW7G1npN6eSY9XgCODzPo-RBVldJf6BcJUjcXgi8dtYN_h2_rTtv8McwUT7XZfurYkLUAj6s_3S0By-o/s400/diagram4.png" /></a></div>
There are lots of predefined stencils to USE, including entire UML:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_u8WyV7Vn7RuPgDwKCXMiMsqMlYH55p5AcTC8A5CyHURzS5rf2fcyNY39Xwvf2OjAMKh9xSJptWJIiFhCBUgP8ihOuWQIzGXVUEF-esre9TfzIef5ljT8QzI6aLPW2VbVBO5MtV18pZQ/s1600/diagrams5.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="400" width="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_u8WyV7Vn7RuPgDwKCXMiMsqMlYH55p5AcTC8A5CyHURzS5rf2fcyNY39Xwvf2OjAMKh9xSJptWJIiFhCBUgP8ihOuWQIzGXVUEF-esre9TfzIef5ljT8QzI6aLPW2VbVBO5MtV18pZQ/s400/diagrams5.png" /></a></div>
The interface is very intuitive - it took me only an hour to learn how to make the most out of cacoo.
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEij6nFZKF6LHT5Y0Ax_Ktc2aRljG02A3AesgocU4bie_pAP-VIw2jb7yXIU6ByfkcFyTKw3xoeAo4CLrt471NFro79jstvVICacT6hUV9agJ6UFxpSkNDB2kZ3fyAPSL3OLq1krOrrsa8o/s1600/diagrams6.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="246" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEij6nFZKF6LHT5Y0Ax_Ktc2aRljG02A3AesgocU4bie_pAP-VIw2jb7yXIU6ByfkcFyTKw3xoeAo4CLrt471NFro79jstvVICacT6hUV9agJ6UFxpSkNDB2kZ3fyAPSL3OLq1krOrrsa8o/s400/diagrams6.png" /></a></div>
There are dozens of easy tools you can use to make really good quality diagrams:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNJ6b85XclmB1ObH3_Ez09E2Oc17vDSU1pbCXfKIgoSe2MXCo8FJgT8XpMKJHG34rHlIR4GZu33BoA7XPr7Oyxz02-VcBhMuUFIRbxIbVKu8_drDCgxXpphakycTkt_vvw5DSDs9nCtcU/s1600/diagrams8.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="250" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNJ6b85XclmB1ObH3_Ez09E2Oc17vDSU1pbCXfKIgoSe2MXCo8FJgT8XpMKJHG34rHlIR4GZu33BoA7XPr7Oyxz02-VcBhMuUFIRbxIbVKu8_drDCgxXpphakycTkt_vvw5DSDs9nCtcU/s400/diagrams8.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
I won't try to list al those tools, I hope the screenshots I made speak for themselves (especially I like the drag n'drop stuff and moving objects across the diagram).
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWrKZBMz1aG7mIAP2-6KPowu7Qy3hGo1w5cjZf6iU6iqVPsuA05F7hhznuIhV9zR2tCQPTwD9cCby6ili23foCmNTft5cp5wY2GJ605dsAPi0ItpsKjOqiMJY9_Z_vBvi23W4A8oOoCe0/s1600/diagrams9.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="357" width="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWrKZBMz1aG7mIAP2-6KPowu7Qy3hGo1w5cjZf6iU6iqVPsuA05F7hhznuIhV9zR2tCQPTwD9cCby6ili23foCmNTft5cp5wY2GJ605dsAPi0ItpsKjOqiMJY9_Z_vBvi23W4A8oOoCe0/s400/diagrams9.png" /></a></div>
And the most comfortable and user-friendy thing is easy diagram update. If your IT project has changed and your documentatin is not up to date anymore - just log in, make a tiny change in the diagram and dump it again, replace the old diagram in the docs and you're done!
</p>
<h2>predefined objects to use</h2>
<p>
Just get a predefined stencil - just like a nice looking professor below:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoOvsowFz2C3Z3GY4lgImQTyezGiL3xnbVWOB0QgiUup_BwVK57qmM2Teo4oIKwxcqGtmoN14vchmNrQilsMKUDO5QnawtEB0trP9NUPOwMV332MM2FAgahjcxdG-CQ80oTQqwg42xyoA/s1600/grand1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="152" width="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoOvsowFz2C3Z3GY4lgImQTyezGiL3xnbVWOB0QgiUup_BwVK57qmM2Teo4oIKwxcqGtmoN14vchmNrQilsMKUDO5QnawtEB0trP9NUPOwMV332MM2FAgahjcxdG-CQ80oTQqwg42xyoA/s400/grand1.png" /></a></div>
This stencil is a group of smaller objects. You can choose some objects to be grouped to make moving easier. You can even resize a whole group of objects, including font size changes! And groups can consist of groups...
</p>
<p>
Anyway, a group member can still be edited separately:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ16_p9ppcI_BS19TePqtE1RoRpATN5NkAhlsuskd3NW2pZWEY1zBfT27QCuXnxGfBbtipDZDX65okPASMRHkL_MY2Hcr2ZrSndAayE-3WOz6QDh2cut1LF9CwFFYPFGybs13JcGcwDaI/s1600/grand2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="153" width="290" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ16_p9ppcI_BS19TePqtE1RoRpATN5NkAhlsuskd3NW2pZWEY1zBfT27QCuXnxGfBbtipDZDX65okPASMRHkL_MY2Hcr2ZrSndAayE-3WOz6QDh2cut1LF9CwFFYPFGybs13JcGcwDaI/s400/grand2.png" /></a></div>
There are really lots and lots of possibilities there. You can do whatever you can think of.
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgczi0iGJODLt2mGwpPvU7EoJ90T9nlxP0Q7Pg2Ci-EMesq3ntEL596LHbm9K7nW9VGMajoEwBQ-3D3GJZ80eIy24zfdg0hfwBAU_yRbsWWHeaxnEZ0En31JDsVUxYSyQmqpeMtNi3CR8w/s1600/grand3.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="176" width="275" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgczi0iGJODLt2mGwpPvU7EoJ90T9nlxP0Q7Pg2Ci-EMesq3ntEL596LHbm9K7nW9VGMajoEwBQ-3D3GJZ80eIy24zfdg0hfwBAU_yRbsWWHeaxnEZ0En31JDsVUxYSyQmqpeMtNi3CR8w/s400/grand3.png" /></a></div>
Finally, you can export your work to few file formats (I use ksnapshot - PNG file is enough for me).
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI0X0d21NG6yGFC95rKFRDAoJhA1QZoCJA2uyiCfcVQ9sMz2jDmDI9dzd_r_Qy3SwixwVf7ojXtYOnk59rj-boiq139quFdVEC9FGQV4BQTmus2y5qvH9hyJvjWQ-DL-dyc2CgovtG5lo/s1600/diagrams10.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI0X0d21NG6yGFC95rKFRDAoJhA1QZoCJA2uyiCfcVQ9sMz2jDmDI9dzd_r_Qy3SwixwVf7ojXtYOnk59rj-boiq139quFdVEC9FGQV4BQTmus2y5qvH9hyJvjWQ-DL-dyc2CgovtG5lo/s500/diagrams10.png" /></a></div>
</p>
<h2>try the demo version</h2>
<p>
The <i>free plan</i> gives you the possibility to hold 25 diagrams and it's enough to create useful diagrams, documenting your projects. I did - and I'm satisfied. Just take a look:
</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipzidUGwiALttKYqKh0YibUm1dA78YVfczwZVzZZ3NeXbQ5g-_RfVfnM_tLAfdN9cAFObifhA2azePr0ysOSwsc8_nx7sH94Mn6Xw1HWggfLR1XEZXz1UYSvy4opcSh-st70rD401fz5k/s1600/example1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="332" width="155" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipzidUGwiALttKYqKh0YibUm1dA78YVfczwZVzZZ3NeXbQ5g-_RfVfnM_tLAfdN9cAFObifhA2azePr0ysOSwsc8_nx7sH94Mn6Xw1HWggfLR1XEZXz1UYSvy4opcSh-st70rD401fz5k/s400/example1.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4A1rM18v0-LM358JUSq4Ug7rUO_j0Q3h3emSnKlxf1mjfqXe4lwFKGK2Vi9AVqHvs3lezHv5ATdb32oGsMYlQK6xR_UHQEQiHHRKKZXZ4QirnvYSoUapl-Zc6lE-HhIKGfWzu-JX7Vh0/s1600/example2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="210" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4A1rM18v0-LM358JUSq4Ug7rUO_j0Q3h3emSnKlxf1mjfqXe4lwFKGK2Vi9AVqHvs3lezHv5ATdb32oGsMYlQK6xR_UHQEQiHHRKKZXZ4QirnvYSoUapl-Zc6lE-HhIKGfWzu-JX7Vh0/s400/example2.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmqmj-yYA_r_QTZRGKfYYx60tleizhNe3xpXgRxRhQIbAxQUlTpVKN6cITmwf3J4m1i9OjqESgRuhT4p80ZGS-JrCGZqa_SYkdst7VLJqGfGeHmNYWFBwHbNG-dDZnJdtc-fqZUJ2TlHM/s1600/example3.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="308" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmqmj-yYA_r_QTZRGKfYYx60tleizhNe3xpXgRxRhQIbAxQUlTpVKN6cITmwf3J4m1i9OjqESgRuhT4p80ZGS-JrCGZqa_SYkdst7VLJqGfGeHmNYWFBwHbNG-dDZnJdtc-fqZUJ2TlHM/s400/example3.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrs6Ak-2vhTLFHsVyJufwWc35S8R4jNYx80kjTYc_pHFuCTMn3cYrDh7zi7KmyM8oOju7AWzCMAkYXT3tX5cpzVC2mWp6O01D99nWy_2aDHQKSmE_HZRN7Hcf7J46XbOMfZVJOxANgHQw/s1600/example4.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="236" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrs6Ak-2vhTLFHsVyJufwWc35S8R4jNYx80kjTYc_pHFuCTMn3cYrDh7zi7KmyM8oOju7AWzCMAkYXT3tX5cpzVC2mWp6O01D99nWy_2aDHQKSmE_HZRN7Hcf7J46XbOMfZVJOxANgHQw/s400/example4.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxn9RTi9HeFWuPziFB29ONUc24j7tlUCKH08qTaDS8vyc6B-E4kNkgAo0FQstpSEECI54s6780nINAE-BFEc7eARJisQUDkK3EqzNP5KSkkbVsxQ7SZ0453375MGMowkp6F36zfSgtCts/s1600/example5.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="232" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxn9RTi9HeFWuPziFB29ONUc24j7tlUCKH08qTaDS8vyc6B-E4kNkgAo0FQstpSEECI54s6780nINAE-BFEc7eARJisQUDkK3EqzNP5KSkkbVsxQ7SZ0453375MGMowkp6F36zfSgtCts/s400/example5.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggUQtYH1aDzZx-8LrygYDq1qMJcY6MuojKg0z7uJ3c_7Dv6UfBKBeabXhj3aQ110CWPgKxld7XRdM8eATUjdh_pMBcVgHUpraO-UarwPkshue-vtD6TspK3dAQJbmS0LEnnykcXgDGGbM/s1600/example6.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="262" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggUQtYH1aDzZx-8LrygYDq1qMJcY6MuojKg0z7uJ3c_7Dv6UfBKBeabXhj3aQ110CWPgKxld7XRdM8eATUjdh_pMBcVgHUpraO-UarwPkshue-vtD6TspK3dAQJbmS0LEnnykcXgDGGbM/s400/example6.png" /></a></div>
<h2>they didn't found me - I found them :)</h2>
<p>
Just to make it clear - <b>I was not contacted by the <a href="http://cacoo.com">Cacoo</a> team</b> and I was not asked if I could write a nice review (things like this happen - some time ago I was asked to review a pro-symfony IDE) ;-). So there was no bribe. One day I was just looking for a free UML-designing software (desktop app or online), because I was finding StarUML less and less satisfying. I found Cacoo and I didn't need to search any longer.
</p>
<p>
<a href="http://cacoo.com">Try it</a>.
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com2tag:blogger.com,1999:blog-3825636150904136311.post-71545062593719634132012-02-05T18:48:00.000+01:002012-02-10T18:16:31.293+01:00svn blame: who made the bug?<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYhImcnwVrpIB72_FR2c2IiZjMrsiPnB9ILVGJ4xvw_ul1yUfi1jnN9VLshCXMQVJGvqEDvhDH2fN-NVLrWyfCTv5U5lYuOezcPK3gajiUyIe9xZtbLJvwM8r5_WKp4qre4YjJ2-2Br-E/s1600/svn_blame.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYhImcnwVrpIB72_FR2c2IiZjMrsiPnB9ILVGJ4xvw_ul1yUfi1jnN9VLshCXMQVJGvqEDvhDH2fN-NVLrWyfCTv5U5lYuOezcPK3gajiUyIe9xZtbLJvwM8r5_WKp4qre4YjJ2-2Br-E/s576/svn_blame.png" /></a><span class="movie">Scene from "12 Angry Men" by Sidney Lumet (1957)</span></div>
<p>
In bigger projects it happens quite often that an unknown team member has made a modification that caused a lot of trouble. And no one wants to confess... It is quite uncomfortable to analyse distinct revisions and changes of a file to find who caused the problem, as it is time consuming. This is where <a href="http://www.linxit.de/svnbook/en/1.0/re02.html">svn blame</a> is the perfect tool - you execute it upon a chosen target to display revision number, team member and content line by line. You can easily find who is the author of a bug and when he committed it. It's intereting, that despite being very useful, this tool is not very known.
</p>
<h2>blame me if you can find me quickly!</h2>
<p>
The usage is very simple:
<pre>
tducin@workshop:/var/www/admin/config/doctrine$ svn blame schema.yml
</pre>
and you have the entire file analysed:
<pre>
367 dennis connection: master
362 tducin
362 tducin options:
362 tducin charset: utf8
362 tducin collate: utf8_general_ci
362 tducin
3755 george Status:
3755 george comment: "Statusy"
491 dennis actAs:
1980 dennis SoftDelete: ~
1793 harold Timestampable: ~
1793 harold Signable: ~
492 tducin columns:
492 tducin name:
3755 george type: string(64)
492 tducin notnull: true
2628 tducin comment: "nazwa"
3755 george notify_mail:
3755 george type: boolean
3755 george notnull: true
3755 george default: false
3755 george comment: "klient ma być powiadamiany mailowo"
1541 dennis stock_mode:
1541 dennis type: enum
1541 dennis values: [ reserved, sent, cancelled ]
1541 dennis default: reserved
2628 dennis comment: "tryb magazynowy: reserved, sent, cancelled"
3755 george force_reminder:
892 tducin type: boolean
878 tducin notnull: true
3755 george comment: "Wymuszenie dodania wpisu w kalendarzu"
3755 george block_update:
3755 george type: boolean
3755 george notnull: true
3755 george comment: "Blokada zmiany statusu"
4078 harold indexes:
4078 harold softdelete_idx:
4078 harold fields: [ deleted_at ]
</pre>
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com2tag:blogger.com,1999:blog-3825636150904136311.post-50923772800995496332012-01-29T17:06:00.000+01:002013-04-20T18:48:38.904+02:00symfony range filter<p>
In symfony/doctrine, only the <a href="http://www.doctrine-project.org/projects/orm/1.2/docs/manual/behaviors/en#core-behaviors:timestampable">Timestampable behavior</a> provides ranged filters (a combination of <strong>from</strong> and <strong>to</strong> values that are queried just like <a href="http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#operator_between">MySQL BETWEEN statement</a>). But how about ranged values for other data types?
</p>
<p>
I thought it'd be both cool and very useful, so I set off to search the web, but found no solution. Hence, I've made one myself. We will create three new classes and modify two more files to make it work in the basic version.
</p>
<h2>new widget/validator classes</h2>
<p>
The first class to be added is the form widget, <strong>lib/widget/sfWidgetFormInputRange.class.php</strong>:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">class</span> sfWidgetFormInputRange <span style="color: rgb(0, 0, 0); font-weight: bold;">extends</span> sfWidgetForm
<span style="color: rgb(0, 153, 0);">{</span>
protected <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> configure<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$options</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$attributes</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addRequiredOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addRequiredOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'to_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'template'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'from %from_value% to %to_value%'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Renders the widget.
*
* @param string $name The element name
* @param string $value The value displayed in this widget
* @param array $attributes An array of HTML attributes to be merged with the default HTML attributes
* @param array $errors An array of errors for the field
*
* @return string An HTML tag string
*
* @see sfWidgetForm
*/</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> render<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$name</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$value</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 153, 0); font-weight: bold;">null</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$attributes</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$errors</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$value</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array_merge"><span style="color: rgb(153, 0, 0);">array_merge</span></a><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">''</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'to'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">''</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/is_array"><span style="color: rgb(153, 0, 0);">is_array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">)</span> ? <span style="color: rgb(0, 0, 136);">$value</span> <span style="color: rgb(51, 153, 51);">:</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">return</span> <a href="http://www.php.net/strtr"><span style="color: rgb(153, 0, 0);">strtr</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">translate</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'template'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'%from_value%'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">render</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$name</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">'[from]'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'from'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'%to_value%'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'to_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">render</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$name</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">'[to]'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'to'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Gets the stylesheet paths associated with the widget.
*
* @return array An array of stylesheet paths
*/</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> getStylesheets<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(177, 177, 0);">return</span> <a href="http://www.php.net/array_unique"><span style="color: rgb(153, 0, 0);">array_unique</span></a><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array_merge"><span style="color: rgb(153, 0, 0);">array_merge</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getStylesheets</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'to_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getStylesheets</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Gets the JavaScript paths associated with the widget.
*
* @return array An array of JavaScript paths
*/</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> getJavaScripts<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(177, 177, 0);">return</span> <a href="http://www.php.net/array_unique"><span style="color: rgb(153, 0, 0);">array_unique</span></a><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array_merge"><span style="color: rgb(153, 0, 0);">array_merge</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getJavaScripts</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'to_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getJavaScripts</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
The second one is the form filter widget, <strong>lib/widget/sfWidgetFormFilterInputRange.class.php</strong>:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">class</span> sfWidgetFormFilterInputRange <span style="color: rgb(0, 0, 0); font-weight: bold;">extends</span> sfWidgetFormInputRange
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Configures the current widget.
*
* Available options:
*
* * with_empty: Whether to add the empty checkbox (true by default)
* * empty_label: The label to use when using an empty checkbox
* * template: The template used for from value and to value
* Available placeholders: %from_value%, %to_value%
* * filter_template: The template to use to render the widget
* Available placeholders: %value_range%, %empty_checkbox%, %empty_label%
*
* @param array $options An array of options
* @param array $attributes An array of default HTML attributes
*
* @see sfWidgetForm
*/</span>
protected <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> configure<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$options</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$attributes</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
parent<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">configure</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$options</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$attributes</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'with_empty'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 153, 0); font-weight: bold;">true</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'empty_label'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'is empty'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'template'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'from %from_value%<br />to %to_value%'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'filter_template'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'%value_range%<br />%empty_checkbox% %empty_label%'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Renders the widget.
*
* @param string $name The element name
* @param string $value The value displayed in this widget
* @param array $attributes An array of HTML attributes to be merged with the default HTML attributes
* @param array $errors An array of errors for the field
*
* @return string An HTML tag string
*
* @see sfWidgetForm
*/</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> render<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$name</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$value</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 153, 0); font-weight: bold;">null</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$attributes</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$errors</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$values</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array_merge"><span style="color: rgb(153, 0, 0);">array_merge</span></a><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'is_empty'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">''</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/is_array"><span style="color: rgb(153, 0, 0);">is_array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">)</span> ? <span style="color: rgb(0, 0, 136);">$value</span> <span style="color: rgb(51, 153, 51);">:</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">return</span> <a href="http://www.php.net/strtr"><span style="color: rgb(153, 0, 0);">strtr</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'filter_template'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'%value_range%'</span> <span style="color: rgb(51, 153, 51);">=></span> parent<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">render</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$name</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$attributes</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$errors</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'%empty_checkbox%'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'with_empty'</span><span style="color: rgb(0, 153, 0);">)</span> ? <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">renderTag</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'input'</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'type'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">'checkbox'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'name'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$name</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">'[is_empty]'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'checked'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$values</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'is_empty'</span><span style="color: rgb(0, 153, 0);">]</span> ? <span style="color: rgb(0, 0, 255);">'checked'</span> <span style="color: rgb(51, 153, 51);">:</span> <span style="color: rgb(0, 0, 255);">''</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(51, 153, 51);">:</span> <span style="color: rgb(0, 0, 255);">''</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'%empty_label%'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'with_empty'</span><span style="color: rgb(0, 153, 0);">)</span> ? <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">renderContentTag</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'label'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">translate</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'empty_label'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'for'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">generateId</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$name</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">'[is_empty]'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(51, 153, 51);">:</span> <span style="color: rgb(0, 0, 255);">''</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
Finally, the last class is the widget validator, <strong>lib/validator/sfValidatorInputRange.class.php</strong>:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">class</span> sfValidatorInputRange <span style="color: rgb(0, 0, 0); font-weight: bold;">extends</span> sfValidatorBase
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Configures the current validator.
*
* Available options:
*
* * from_value: The from value validator (required)
* * to_value: The to value validator (required)
* * from_field: The name of the "from" value field (optional, default: from)
* * to_field: The name of the "to" value field (optional, default: to)
*
* @param array $options An array of options
* @param array $messages An array of error messages
*
* @see sfValidatorBase
*/</span>
protected <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> configure<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$options</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$messages</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setMessage</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'invalid'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'The begin value must be before the end value.'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addRequiredOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addRequiredOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'to_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from_field'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'from'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">addOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'to_field'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'to'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(153, 0, 153); font-style: italic;">/**
* @see sfValidatorBase
*/</span>
protected <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> doClean<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$fromField</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from_field'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$toField</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'to_field'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$fromField</span><span style="color: rgb(0, 153, 0);">]</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'from_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">clean</span><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/isset"><span style="color: rgb(153, 0, 0);">isset</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$fromField</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span> ? <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'text'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$fromField</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(51, 153, 51);">:</span> <span style="color: rgb(0, 153, 0); font-weight: bold;">null</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$toField</span><span style="color: rgb(0, 153, 0);">]</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getOption</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'to_value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">clean</span><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/isset"><span style="color: rgb(153, 0, 0);">isset</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$toField</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span> ? <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'text'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$toField</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(51, 153, 51);">:</span> <span style="color: rgb(0, 153, 0); font-weight: bold;">null</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$fromField</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'text'</span><span style="color: rgb(0, 153, 0);">]</span> <span style="color: rgb(51, 153, 51);">&&</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$toField</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'text'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$v</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorSchemaCompare<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$fromField</span><span style="color: rgb(51, 153, 51);">,</span> sfValidatorSchemaCompare<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">LESS_THAN_EQUAL</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$toField</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'throw_global_error'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">true</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'invalid'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'invalid'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$v</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">clean</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$fromField</span><span style="color: rgb(0, 153, 0);">]</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$fromField</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'text'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$toField</span><span style="color: rgb(0, 153, 0);">]</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$toField</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'text'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">return</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
<h2>accustoming solution to a example project</h2>
For the purposes of this article, let's say we have an Offer model. We will add the offer.recommendations integer value filter in the offer admin module. The <strong>lib/filter/doctrine/OfferFormFilter.class.php</strong> shall be improved as below:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">class</span> OfferFormFilter <span style="color: rgb(0, 0, 0); font-weight: bold;">extends</span> BaseOfferFormFilter
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> configure<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
parent<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">configure</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setWidget</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'recommendations_range'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormFilterInputRange<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'from_value'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormInput<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'to_value'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormInput<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'with_empty'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">false</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setValidator</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'recommendations_range'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorInputRange<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">false</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'from_value'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorSchemaFilter<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'text'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorNumber<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">false</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'to_value'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorSchemaFilter<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'text'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorNumber<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">false</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> addRecommendationsRangeColumnQuery<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$query</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$field</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$rootAlias</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$query</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getRootAlias</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/isset"><span style="color: rgb(153, 0, 0);">isset</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'from'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(51, 153, 51);">&&</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'from'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 0, 136);">$query</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">andWhere</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$rootAlias</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">".recommendations >= ?"</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'from'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/isset"><span style="color: rgb(153, 0, 0);">isset</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'to'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(51, 153, 51);">&&</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'to'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 0, 136);">$query</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">andWhere</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$rootAlias</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">".recommendations <= ?"</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'to'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
Finally, add the filter inside the <strong>generator.yml</strong> file of the offer module:
<pre class="yaml" style="font-family: monospace;"> config:
fields:
recommendations_range:
label: Liczba poleceń
filter:
display: [ ..., recommendations_range]</pre>
</p>
<h2>result</h2>
<p>
Now you can use ranged filters anywhere in your project. Moreover, you can modify this mechanism to accustom it to your project needs.
</p>
<img src="http://img11.imageshack.us/img11/9954/filterr.png" />Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com5tag:blogger.com,1999:blog-3825636150904136311.post-75837187285889046572012-01-23T10:28:00.000+01:002013-04-20T19:03:53.147+02:00symfony forward through LAN<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDkF5EXLe5WlFP3VqMSV2mhiuNJsZWfobiEoojZelN7EXGXNT0nliV4sDzBYYOtz4lkab-qDc4ak22gClVtcx3bmVtDSAQPRT8o9s-BHcBHGi7PLbzGGipAS2t2USpXG5IqDR5DPAQu0Y/s1600/forward.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDkF5EXLe5WlFP3VqMSV2mhiuNJsZWfobiEoojZelN7EXGXNT0nliV4sDzBYYOtz4lkab-qDc4ak22gClVtcx3bmVtDSAQPRT8o9s-BHcBHGi7PLbzGGipAS2t2USpXG5IqDR5DPAQu0Y/s576/forward.png" /></a><span class="movie">Scene from "Disney's Adventures of the Gummi Bears" by Jymn Magon & Art Vitello (introduced in 1985)</span></div>
<p>
Ever thought of creating an action that would run different code depending on whether the client machine is inside or outside the LAN? It's really easy yet useful. I used it few times in one of my backend apps so far. For people outside the LAN, both links are hidden (use comparison below) and the redirected links are inaccessible. Thanks to it, company employees working from outside the office may not see inaccessible links.
Below is the auxiliary method:
<pre class="php" style="font-family: monospace;"> <span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Checks if the client computer is inside the LAN with a
* specific address. If so, redirects to a local address.
*
* @param String $ip - IP of the webpage to be forwarded to
* @param String $port - port of the webpage to be forwarded to
*/</span>
protected <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> forwardThroughLan<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$ip</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$port</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$_SERVER</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'REMOTE_ADDR'</span><span style="color: rgb(0, 153, 0);">]</span> <span style="color: rgb(51, 153, 51);">==</span> sfConfig<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">get</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'path_server_ip'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">redirect</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'http://'</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 136);">$ip</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">':'</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 136);">$port</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">else</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setTemplate</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'forwardThroughLanError'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
and here is direct usage:
<pre class="php" style="font-family: monospace;"> <span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Executes forward TRAC through LAN action. Forwards to TRAC webpage
* inside LAN in the office building.
*
* @param sfWebRequest $request
*/</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> executeForwardTracThroughLan<span style="color: rgb(0, 153, 0);">(</span>sfWebRequest <span style="color: rgb(0, 0, 136);">$request</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">forwardThroughLan</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'192.168.1.99'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'8080'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-43380163183013723332012-01-16T19:08:00.000+01:002012-02-10T18:17:11.316+01:00SVN repository auto update<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi67xlsr7ECuRwuG1wtQmlvQKp7iPzOC9XBnneC_iyjlT_fHGecMpxRcwIyWwVPQxJsqgsUiJEXipyaN8X7ojhOoni1jh6rw29tG9SWAozhtmeFZ5MskRlyX9hj568e0paMdyeHk4vSh9o/s1600/svn_auto_update.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi67xlsr7ECuRwuG1wtQmlvQKp7iPzOC9XBnneC_iyjlT_fHGecMpxRcwIyWwVPQxJsqgsUiJEXipyaN8X7ojhOoni1jh6rw29tG9SWAozhtmeFZ5MskRlyX9hj568e0paMdyeHk4vSh9o/s576/svn_auto_update.png" /></a><span class="movie">Scene from "Brazil" by Terry Gilliam (1985)</span></div>
<p>
I found an <a href="http://tech.cynarski.pl/2009/06/03/svn-hooks-i-autoupdatey/">article (in Polish)</a> describing a subversion hook that will automatically update a server working copy. This seems perfect - after commiting a new revision, all changes are available online (entire development team can test the most up to date version, your boss may see the newest results, etc).
</p>
<p>
Anyway, I don't see any reason to use C here, so I've made my own version updating the repository using shell command:
<pre class="sh" style="font-family: monospace;">svn up /home/website/domains/dev.website.com/website
--no-auth-cache --username user --password pass</pre>
The --no-auth-cache option is important here - authorization data is not going to be remembered here. Other two options define the authorization data.
</p>
<h2>troubleshooting</h2>
<p>
You may have problems with file permissions, if the user which holds the repository is different than the user which hold the working copy (this was in my case). The problem I got was permission denied for .../.svn/lock file, which is a temporary file, existing only when the revision is being checked/committed. Oops... There is no one good solution for this - it depends on the server configuration. It can be adding one user to the group of another, changing the owner of the entire working copy files (no matter in which /home subdirectory it is, as long as web server can handle requests, etc).
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-60316319457188002622012-01-15T23:40:00.000+01:002012-02-10T18:17:46.530+01:00force to log message SVN python<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3c9mtje8bAmefOyTaBVenROPIF6KgOaTOkipgNfFBWLtA1cGJ_cF1hy_JyJxYmK-WmA0tMi97G3E3fyArzJdCDWPvMDGsxJXiVOnSOTmNkjJd14iTqGrFjsCF5YORh-4SM3Xna0yC3kU/s1600/svn_force_log.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3c9mtje8bAmefOyTaBVenROPIF6KgOaTOkipgNfFBWLtA1cGJ_cF1hy_JyJxYmK-WmA0tMi97G3E3fyArzJdCDWPvMDGsxJXiVOnSOTmNkjJd14iTqGrFjsCF5YORh-4SM3Xna0yC3kU/s576/svn_force_log.png" /></a><span class="movie">Scene from "It Happened One Night" by Frank Capra (1934)</span></div>
<p>
Another blog post about Subversion. Few days ago someone asked me if I know how to block commiting a subversion revision that has no log message defined. I didn't, but I thought it's a good idea, because it could help to track bugs, changes, etc. There's no such configuration, but it can be achieved using <a href="http://svnbook.red-bean.com/en/1.0/svn-book.html#svn-ch-5-sect-2.1">SVN hooks</a>.
</p>
<h2>tried to do this in PHP...</h2>
<p>
There are many resources in the web giving examples to do this using a windows batch file - but I'm a linux user. At first, I wanted to do this in PHP. Although my final solution is not PHP, maybe someone will find following code snippets useful.
</p>
<p>
So I started searching through many pages on the web to find PHP shell scripts that could block committing a revision with no log message. The first step is, of course, to create an executable <strong>pre-commit</strong> file in the <strong>hooks</strong> directory of the repository. Then make the shell use the PHP parser to execute our script. Next, define path to svnlook binary and execute it:
<pre class="php" style="font-family: monospace;">#!/usr/local/bin/php -q
<span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span>
<span style="color: rgb(0, 0, 136);">$SVNLOOK</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 255);">'/usr/bin/svnlook'</span><span style="color: rgb(51, 153, 51);">;</span>
<a href="http://www.php.net/exec"><span style="color: rgb(153, 0, 0);">exec</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">"<span style="color: rgb(0, 102, 153); font-weight: bold;">$SVNLOOK</span> log -t <span style="color: rgb(0, 102, 153); font-weight: bold;">{$argv[2]}</span> <span style="color: rgb(0, 102, 153); font-weight: bold;">{$argv[1]}</span>"</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$output</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$result</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre>
We have the output and the result of the command available. So we may just check if the message (which revision a user is just trying to commit) is empty (I guess there is no other way than to use <code><a href="http://www.linxit.de/svnbook/en/1.0/re51.html">svnlook</a></code>). If the log message is empty, the revision shall be stopped - and this happens, when exit status code is different than 0 (of course, a message explaining the cause is appreciated):
<pre class="php" style="font-family: monospace;"><span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/empty"><span style="color: rgb(153, 0, 0);">empty</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$result</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<a href="http://www.php.net/fwrite"><span style="color: rgb(153, 0, 0);">fwrite</span></a><span style="color: rgb(0, 153, 0);">(</span>STDERR<span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">"Commit blocked, need to pass the log message."</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<a href="http://www.php.net/exit"><span style="color: rgb(153, 0, 0);">exit</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(204, 102, 204);">1</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(177, 177, 0);">else</span>
<a href="http://www.php.net/exit"><span style="color: rgb(153, 0, 0);">exit</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(204, 102, 204);">0</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre>
This all should work, but 99,9% of all servers will have the exec function disabled by default (in both CLI and web server PHP configuration; along with other system functions):
<pre class="sh" style="font-family: monospace;">disable_functions = exec,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,proc_open,dl,popen,show_source</pre>
Even if you manage to remove those functions from the configuration, you lower the server security, which may risk dangerous <a href="http://en.wikipedia.org/wiki/Code_injection">script injection</a>. So I forgot about using PHP...
</p>
<h2>...and finally moved to python</h2>
<p>
Finally, I managed to succeed using python:
<pre class="python" style="font-family: monospace;"><span style="color: rgb(128, 128, 128); font-style: italic;">#!/usr/bin/python</span>
<span style="color: rgb(255, 119, 0); font-weight: bold;">import</span> <span style="color: rgb(220, 20, 60);">sys</span>, <span style="color: rgb(220, 20, 60);">os</span>, <span style="color: rgb(220, 20, 60);">string</span>
SVNLOOK=<span style="color: rgb(72, 61, 139);">'/usr/bin/svnlook'</span>
<span style="color: rgb(255, 119, 0); font-weight: bold;">def</span> main<span style="color: black;">(</span>repos, txn<span style="color: black;">)</span>:
log_cmd = <span style="color: rgb(72, 61, 139);">'%s log -t "%s" "%s"'</span> <span style="color: rgb(102, 204, 102);">%</span> <span style="color: black;">(</span>SVNLOOK, txn, repos<span style="color: black;">)</span>
log_msg = <span style="color: rgb(220, 20, 60);">os</span>.<span style="color: black;">popen</span><span style="color: black;">(</span>log_cmd, <span style="color: rgb(72, 61, 139);">'r'</span><span style="color: black;">)</span>.<span style="color: rgb(220, 20, 60);">readline</span><span style="color: black;">(</span><span style="color: black;">)</span>.<span style="color: black;">rstrip</span><span style="color: black;">(</span><span style="color: rgb(72, 61, 139);">'n'</span><span style="color: black;">)</span>
<span style="color: rgb(255, 119, 0); font-weight: bold;">if</span> <span style="color: rgb(0, 128, 0);">len</span><span style="color: black;">(</span>log_msg<span style="color: black;">)</span> <span style="color: rgb(102, 204, 102);"><</span> <span style="color: rgb(255, 69, 0);">10</span>:
<span style="color: rgb(220, 20, 60);">sys</span>.<span style="color: black;">stderr</span>.<span style="color: black;">write</span> <span style="color: black;">(</span><span style="color: rgb(72, 61, 139);">"<span style="color: rgb(0, 0, 153); font-weight: bold;">\n</span>> Revision comment must be defined<span style="color: rgb(0, 0, 153); font-weight: bold;">\n</span>"</span><span style="color: black;">)</span>
<span style="color: rgb(220, 20, 60);">sys</span>.<span style="color: black;">exit</span><span style="color: black;">(</span><span style="color: rgb(255, 69, 0);">1</span><span style="color: black;">)</span>
<span style="color: rgb(255, 119, 0); font-weight: bold;">else</span>:
<span style="color: rgb(220, 20, 60);">sys</span>.<span style="color: black;">exit</span><span style="color: black;">(</span><span style="color: rgb(255, 69, 0);">0</span><span style="color: black;">)</span>
<span style="color: rgb(255, 119, 0); font-weight: bold;">if</span> __name__ == <span style="color: rgb(72, 61, 139);">'__main__'</span>:
<span style="color: rgb(255, 119, 0); font-weight: bold;">if</span> <span style="color: rgb(0, 128, 0);">len</span><span style="color: black;">(</span><span style="color: rgb(220, 20, 60);">sys</span>.<span style="color: black;">argv</span><span style="color: black;">)</span> <span style="color: rgb(102, 204, 102);"><</span> <span style="color: rgb(255, 69, 0);">3</span>:
<span style="color: rgb(220, 20, 60);">sys</span>.<span style="color: black;">stderr</span>.<span style="color: black;">write</span><span style="color: black;">(</span><span style="color: rgb(72, 61, 139);">"<span style="color: rgb(0, 0, 153); font-weight: bold;">\n</span>> Usage: %s REPOS TXN<span style="color: rgb(0, 0, 153); font-weight: bold;">\n</span>"</span> <span style="color: rgb(102, 204, 102);">%</span> <span style="color: black;">(</span><span style="color: rgb(220, 20, 60);">sys</span>.<span style="color: black;">argv</span><span style="color: black;">[</span><span style="color: rgb(255, 69, 0);">0</span><span style="color: black;">]</span><span style="color: black;">)</span><span style="color: black;">)</span>
<span style="color: rgb(255, 119, 0); font-weight: bold;">else</span>:
main<span style="color: black;">(</span><span style="color: rgb(220, 20, 60);">sys</span>.<span style="color: black;">argv</span><span style="color: black;">[</span><span style="color: rgb(255, 69, 0);">1</span><span style="color: black;">]</span>, <span style="color: rgb(220, 20, 60);">sys</span>.<span style="color: black;">argv</span><span style="color: black;">[</span><span style="color: rgb(255, 69, 0);">2</span><span style="color: black;">]</span><span style="color: black;">)</span></pre>
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com1tag:blogger.com,1999:blog-3825636150904136311.post-33142150224354527072011-12-15T23:07:00.000+01:002012-02-10T18:18:56.361+01:00svnsync tutorial<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOqwot0heTujJWYD2ezDKjLlO_XtelBWecStIZqYvM0YUL5ELs7y8jZNO82h-K3zrkL460qVW7RdTx4n0_JjzFJsgh58TYSa5AH3LahlJwMhdH6JQAtJeTc_0d0VQqpfSV_2-ymqQLoZM/s1600/svn_sync.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOqwot0heTujJWYD2ezDKjLlO_XtelBWecStIZqYvM0YUL5ELs7y8jZNO82h-K3zrkL460qVW7RdTx4n0_JjzFJsgh58TYSa5AH3LahlJwMhdH6JQAtJeTc_0d0VQqpfSV_2-ymqQLoZM/s576/svn_sync.png" /></a>
<span class="movie">Scene from magnificent "One Flew Over the Cuckoo's Nest" By Miloš Forman</span></div>
<p>
I was forced to use this tool when I wanted to move my old project repository into a new server (until then, its SVN repository was hosted on a free SVN hosting). The hosting provider forced me to pay for the svnadmin dump, so I searched for an alternative solution and <code>svnsync</code> did the job.
</p>
<p>
In comparison to <code>svnadmin dump</code>/<code>load</code>, <a href="http://svnbook.red-bean.com/en/1.4/svn.ref.svnsync.html"><code>svnsync</code></a> is migrating subversion repository with no svnadmin access. The migration can be done when you have a working copy (checkout) of the source repository and a clean (<code>svnadmin create</code>) version of the target repository. The repository synchronization is just reading all revisions one by one and copying them into the target repository, unchanged.
</p>
<p>
First, take a look at <a href="http://www.svnsite.com/content/page/svnsync">this article</a>. Here you can find that a hook needs to be created in the target repository: <i>hooks/pre-revprop-change</i> (removing <i>.tmpl</i> and granting executable permissions for the script). What is not mentioned in the article above is that you should probably put
<pre style='color:#000000;'><span style='color:#696969; '>#!</span><span style='color:#007997; '>/bin/sh</span><span style='color:#bb7977; font-weight:bold; '> </span>
<span style='color:#bb7977; font-weight:bold; '>exit</span> <span style='color:#008c00; '>0</span><span style='color:#800080; '>;</span>
</pre>
as the <i>hooks/pre-revprop-change</i> script content, to avoid the error, as written in <a href="http://blog.elijaa.org/index.php?post/2010/09/23/revprop-change-blocked-by-pre-revprop-change-hook-%28exit-code-255%29-with-no-output-error">another article</a>.
</p>
<p>
Now, initialize the synchronization (example call below):
</p>
<pre style='color:#000000;'>svnsync init <span style='color:#808030; '>[</span><span style='color:#0000e6; '>target repository</span><span style='color:#808030; '>]</span> <span style='color:#808030; '>[</span><span style='color:#0000e6; '>source repository</span><span style='color:#808030; '>]</span>
svnsync init file<span style='color:#808030; '>:</span><span style='color:#70018a; '>/</span><span style='color:#70018a; '>/</span><span style='color:#70018a; '>/home/user/svn/test-repo</span> svn<span style='color:#808030; '>:</span><span style='color:#70018a; '>/</span><span style='color:#70018a; '>/free-hosting/that-forced-me-to-pay/for-the-svn-dump</span>
</pre>
<p>
and copy all revisions, while being in the working copy of the source repository (example call below again):
</p>
<pre style='color:#000000;'>svnsync --non-interactive sync <span style='color:#808030; '>[</span><span style='color:#0000e6; '>target repository</span><span style='color:#808030; '>]</span>
svnsync --non-interactive sync file<span style='color:#808030; '>:</span><span style='color:#70018a; '>/</span><span style='color:#70018a; '>/</span><span style='color:#70018a; '>/home/user/svn/test-repo</span>
</pre>
<p>
Be patient and wait until it's finished. Afterwards, remove the useless hooks/pre-revprop-change script - and you're done!
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-35982637235682787812011-11-08T20:58:00.000+01:002013-04-20T19:04:02.425+02:00extending doctrine admin module: filtered sum<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0WyQSEoixIJyVb5x_oIqqTLtEz4B214_jhCGlbSE3NbjjBFeoIfYJaSIn554KaKW6YAkiUozIAg1H-YyKslqoXyJYdKJeKi5YsBwWo3vcsPuNgCAA7-9ZHn0tGhrAe8B9kqwnYRTSKsA/s1600/tiffany.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0WyQSEoixIJyVb5x_oIqqTLtEz4B214_jhCGlbSE3NbjjBFeoIfYJaSIn554KaKW6YAkiUozIAg1H-YyKslqoXyJYdKJeKi5YsBwWo3vcsPuNgCAA7-9ZHn0tGhrAe8B9kqwnYRTSKsA/s576/tiffany.png" /></a><span class="movie">Scene from "Breakfast at Tiffany's" by Blake Edwards (1961)</span></div>
<p>
Another post for symfony beginners. I'll show how to display a sum (or any other function) of all elements filtered in an admin module. <u>All elements</u> - meaning the ones displayed on the current page (list pagination) and all the rest which is not currently visible. This is going to be really easy.
</p>
<h2>action</h2>
<p>
Let's start with calculations. We need to get the sum of all filtered elements and pass it to the <a href="http://www.symfony-project.org/gentle-introduction/1_4/en/07-Inside-the-View-Layer">View</a>. As this article is not about <a href="http://www.symfony-project.org/gentle-introduction/1_4/en/02-Exploring-Symfony-s-Code#chapter_02_the_mvc_pattern">MVC design pattern</a>, I'll just override <i>executeIndex</i> action and put the calculations code inside (to make it as easy as possible, though <u>calculations should be done in model, not controller</u>).
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">class</span> xxxActions <span style="color: rgb(0, 0, 0); font-weight: bold;">extends</span> autoXxxActions
<span style="color: rgb(0, 163, 0);">{</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> executeIndex<span style="color: rgb(0, 163, 0);">(</span>sfWebRequest <span style="color: rgb(0, 0, 136);">$request</span><span style="color: rgb(0, 163, 0);">)</span>
<span style="color: rgb(0, 163, 0);">{</span>
parent<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 80, 0);">executeIndex</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 136);">$request</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$query</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 80, 0);">buildQuery</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 80, 0);">copy</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$root_alias</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$query</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 80, 0);">getRootAlias</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$total_data</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$query</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 80, 0);">limit</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(204, 102, 204);">0</span><span style="color: rgb(0, 163, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 80, 0);">select</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 255);">"SUM(<span style="color: rgb(0, 102, 153); font-weight: bold;">{$root_alias}</span>.cash_total) AS sum"</span><span style="color: rgb(0, 163, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 80, 0);">fetchArray</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 80, 0);">total_count</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$total_data</span><span style="color: rgb(0, 163, 0);">[</span><span style="color: rgb(204, 102, 204);">0</span><span style="color: rgb(0, 163, 0);">]</span><span style="color: rgb(0, 163, 0);">[</span><span style="color: rgb(0, 0, 255);">'sum'</span><span style="color: rgb(0, 163, 0);">]</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 163, 0);">}</span>
<span style="color: rgb(0, 163, 0);">}</span></pre>
As you can see, we've got a <i>Xxx</i> model which holds the <i>cash_total: decimal</i> column, representing a sum of money. The <i>$this->buildQuery()->clone()</i> part does all the magic - we have the query with all filters set by the user in the interface. We will only tell doctrine to calculate the sum of all filtered elements for us (<i>$total_count</i> variable will be available in the <i>indexSuccess.php</i> template). The <i>->limit(0)</i> clears the SQL limit clause, of course.
</p>
<h2>templates</h2>
<p>
This part is boring, actually. We need to override two more files: <a href="http://www.symfony-project.org/gentle-introduction/1_4/en/14-Admin-Generator#chapter_14_sub_a_look_at_the_generated_code">fetch them from cache</a> and put them in the module/template directory. These files are: <i>indexSuccess.php</i> in which the line
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> include_partial<span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 255);">'event/list'</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 255);">'pager'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$pager</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'sort'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$sort</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'helper'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$helper</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(0, 163, 0);">)</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span></pre>
should be replaced with:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> include_partial<span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 255);">'event/list'</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 255);">'pager'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$pager</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'sort'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$sort</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'helper'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$helper</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'total_count'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$total_count</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(0, 163, 0);">)</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span></pre>
and the <i>_list.php</i> partial which should have few lines added:
<pre class="html4strict" style="font-family: monospace;"><span style="color: rgb(0, 163, 0);"><<a href="http://december.com/html/4/element/tfoot.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tfoot</span></a>></span>
<span style="color: rgb(0, 163, 0);"><<a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span><span style="color: rgb(128, 128, 128); font-style: italic;"><!-- added code starts here --></span>
<span style="color: rgb(0, 163, 0);"><<a href="http://december.com/html/4/element/th.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">th</span></a> <span style="color: rgb(0, 0, 102);">colspan</span><span style="color: rgb(102, 204, 102);">=</span><span style="color: rgb(255, 0, 0);">"6"</span>></span>
w sumie: <span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> <span style="color: rgb(177, 177, 0);">echo</span> Tools<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 80, 0);">priceFormat</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 136);">$total_count</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 163, 0); font-weight: bold;">true</span><span style="color: rgb(0, 163, 0);">)</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span>
<span style="color: rgb(0, 163, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/th.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">th</span></a>></span>
<span style="color: rgb(0, 163, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span><span style="color: rgb(128, 128, 128); font-style: italic;"><!-- added code ends here --></span>
<span style="color: rgb(0, 163, 0);"><<a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span>
<span style="color: rgb(0, 163, 0);"><<a href="http://december.com/html/4/element/th.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">th</span></a> <span style="color: rgb(0, 0, 102);">colspan</span><span style="color: rgb(102, 204, 102);">=</span><span style="color: rgb(255, 0, 0);">"6"</span>></span>
<span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> <span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 136);">$pager</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 80, 0);">haveToPaginate</span><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(51, 153, 51);">:</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span>
<span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> include_partial<span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 255);">'event/pagination'</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 163, 0);">(</span><span style="color: rgb(0, 0, 255);">'pager'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 136);">$pager</span><span style="color: rgb(0, 163, 0);">)</span><span style="color: rgb(0, 163, 0);">)</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span>
<span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> <span style="color: rgb(177, 177, 0);">endif</span><span style="color: rgb(51, 153, 51);">;</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span></pre>
As you can see, it's trivial, yet useful.
</p>
<p>
The sum of all filtered elements is visible in the footer of the doctrine admin module list table, as you can see below, but you can put it anywhere you want (as long as it's in the index action templates):
<br />
<img src="http://img267.imageshack.us/img267/4484/sumg.png" />
</p>
<h2>other functions</h2>
<p>
Of course, you can use other aggregate functions, such as average, minumum or maximum element - just take a look at the <a href="http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html">MySQL documentation</a>. You may also create <a href="http://dev.mysql.com/doc/refman/5.1/en/adding-functions.html">your own functions</a>.
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com3tag:blogger.com,1999:blog-3825636150904136311.post-46475333548590752032011-11-06T19:10:00.000+01:002013-04-20T19:02:18.153+02:00symfony basics: form default values<p>
This is some stuff for symfony beginners, who still want to learn symfony 1.4. You may set default form values for all kind of <a href="http://www.symfony-project.org/forms/1_4/en/">forms</a> (including doctrine forms). Set one default value at a time:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">class</span> XxxForm <span style="color: rgb(0, 0, 0); font-weight: bold;">extends</span> BaseXxxForm
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> configure<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setDefault</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'field'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'value'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
or set a whole array of them:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> configure<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setDefaults</span><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'field_1'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">'value_1'</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'field_2'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">'value_2'</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(102, 102, 102); font-style: italic;">// ...</span>
<span style="color: rgb(0, 0, 255);">'field_x'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">'value_x'</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
</p>
<h2>default values for new objects</h2>
<p>
Sometimes you want to set the default form values just before the object is created, because it'd be easier for the aplication user to fill in some data. For example, the owner/author of a blog post may be set default to the current logged in user - or the date of an event may be set to now - and so on. This can be achieved with the <strong>isNew</strong> method of the doctrine form class (<i>lib/form/doctrine/XxxForm.class.php</i>):
<pre class="php" style="font-family: monospace;"><span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">isNew</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setDefault</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'created_at'<sup>1</sup></span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/date"><span style="color: rgb(153, 0, 0);">date</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'Y-m-d 00:00:00'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setDefault</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'created_by'<sup>2</sup></span><span style="color: rgb(51, 153, 51);">,</span> sfContext<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">getInstance</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getUser</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)<sup>3</sup></span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getId</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
<ul>
<li>note <sup>1</sup>: <a href="http://www.doctrine-project.org/documentation/manual/1_1/pl/behaviors:core-behaviors:timestampable">Timestampable Doctrine behavior</a> used in this example,</li>
<li>note <sup>2</sup>: <a href="http://www.symfony-project.org/plugins/sfDoctrineActAsSignablePlugin">Signable Doctrine behavior</a> used in this example,</li>
<li>note <sup>3</sup>: check <a href="http://prendreuncafe.com/blog/post/2010/02/17/User-Dependant-Forms-with-Symfony">this blog post</a> to avoid using <i>sfContext::getInstance()</i>.</li>
</ul>
</p>
<h2>moreover</h2>
<p>
You may implement whatever complex conditions you want your doctrine form to follow. Look at some of examples below:
<ul>
<li><u>current time</u> - <a href="http://php.net/manual/en/function.time.php">php time function</a></li>
<li><u>language/localization</u> (default country when registering a new user) - use <a href="http://www.w3.org/International/questions/qa-accept-lang-locales">Accept-Language HTTP</a></li>
<li><u>default settings set for a registered user</u> - fetch individual user settings from database (doctrine query)</li>
<li><u>last used item</u> (category/product/etc.) - a user inserts or updates a large amount of data, when he choses a specific item (category/product) it can be saved in its session (<i>$user->set/getAttribute()</i>) - when another record is processed, last used item is used as default (which, again, lowers time needed for the user to work)</li>
</ul>
<strong>A well designed interface includes lots of form default values</strong>, so that users don't have to waste their time on picking up the same values over and over again.
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com2tag:blogger.com,1999:blog-3825636150904136311.post-72741463000464009692011-10-31T23:09:00.000+01:002013-04-20T19:04:14.251+02:00symfony dynamic max_per_page<h1>max_per_page</h1>
<p>
In this post I'll show a very easy and a really useful thing. It is dynamic <i>max_per_page</i> value of the list pager. Such feature gives you the possibility <strong>to change the number of elements displayed in a list</strong> just by <strong>one click</strong>. It can be used both in the frontend and backend (the pager is the same) - I'll use the doctrine generated admin module (and the interface will be placed inside the filters box):
<img src="http://img577.imageshack.us/img577/3299/filters.png" alt="interface allowing backend user to change the max_per_page value in the 'filters' box" />
</p>
<h2>templates</h2>
<p>
First, let's make it visible. Add the following entry to application config/app.yml file:
<pre>
all:
const:
max_per_page: [ 10, 25, 50 ]
</pre>
Now we may refer to <i>app_const_max_per_page</i> config value which holds few standard <i>max_per_page</i> values to be used (this can be used in many different admin modules). Let's say we've got an admin module for our custom <i>MyModel</i> model. Now, override the cached <strong>_filters.php</strong> template: fetch it from <i>cache/admin/dev/modules/autoMyModel/templates</i> and put it in <i>apps/APP/modules/my_model/templates</i>. Take a look at the following part of the code:
<pre class="html4strict" style="font-family: monospace;"> <span style="color: rgb(0, 153, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/td.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">td</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/tfoot.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tfoot</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<a href="http://december.com/html/4/element/tbody.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tbody</span></a>></span>
<span style="color: rgb(128, 128, 128); font-style: italic;"><!-- insert here --></span>
<span style="color: rgb(0, 153, 0);"><<a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<a href="http://december.com/html/4/element/td.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">td</span></a> <span style="color: rgb(0, 0, 102);">colspan</span><span style="color: rgb(102, 204, 102);">=</span><span style="color: rgb(255, 0, 0);">"2"</span>></span></pre>
and insert few lines of code (replace the comment) to get the following:
<pre class="html4strict" style="font-family: monospace;">
<span style="color: rgb(0, 153, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/td.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">td</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/tfoot.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tfoot</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<a href="http://december.com/html/4/element/tbody.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tbody</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<a href="http://december.com/html/4/element/td.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">td</span></a> <span style="color: rgb(0, 0, 102);">colspan</span><span style="color: rgb(102, 204, 102);">=</span><span style="color: rgb(255, 0, 0);">"2"</span>></span>
set maximum elements per page:
<span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> <span style="color: rgb(177, 177, 0);">foreach</span> <span style="color: rgb(0, 153, 0);">(</span>sfConfig<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">get</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'app_const_max_per_page'</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(177, 177, 0);">as</span> <span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">:</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span>
<span class="max_per_page_selector"><span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> <span style="color: rgb(177, 177, 0);">echo</span> link_to<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'my_model/setMaxPerPage?max='</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 136);">$value</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span></span>
<span style="color: rgb(0, 0, 0); font-weight: bold;"><?php</span> <span style="color: rgb(177, 177, 0);">endforeach</span><span style="color: rgb(51, 153, 51);">;</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">?></span>
<span style="color: rgb(0, 153, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/td.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">td</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<span style="color: rgb(102, 204, 102);">/</span><a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<a href="http://december.com/html/4/element/tr.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">tr</span></a>></span>
<span style="color: rgb(0, 153, 0);"><<a href="http://december.com/html/4/element/td.html"><span style="color: rgb(0, 0, 0); font-weight: bold;">td</span></a> <span style="color: rgb(0, 0, 102);">colspan</span><span style="color: rgb(102, 204, 102);">=</span><span style="color: rgb(255, 0, 0);">"2"</span>></span>
</pre>
</p>
<h2>controller/action</h2>
<p>
The interface to change <i>max_per_page</i> is ready, so we have to improve the controller now. Let's add an action which stores the number of elements to display per page in user session (symofny has nice <i>set(get)Attributes</i> methods). So here it comes:
<pre class="php" style="font-family: monospace;"> <span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Sets my_model list's max per page config value, using user session
* attribute.
*
* @param sfWebRequest $request
*/</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> executeSetMaxPerPage<span style="color: rgb(0, 153, 0);">(</span>sfWebRequest <span style="color: rgb(0, 0, 136);">$request</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getUser</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setAttribute</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'my_model.max_per_page'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$max</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 136);">$request</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getParameter</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'max'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getUser</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setFlash</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'notice'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'max_per_page has been set to: '</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 136);">$max</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">redirect</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'@my_model'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre></p>
<h2>configuration generator</h2>
<p>
And finally, tell the pager to look for the custom value each time the list is going to be rendered. We need to override the method in configuration generator of the admin module:
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">class</span> my_modelGeneratorConfiguration <span style="color: rgb(0, 0, 0); font-weight: bold;">extends</span> BaseMy_modelGeneratorConfiguration
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(153, 0, 153); font-style: italic;">/**
* Returns max_per_page config value for my_model module. If it's not
* defined manually by the user, default value is returned.
*
* @return Integer
*/</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> getPagerMaxPerPage<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$max</span> <span style="color: rgb(51, 153, 51);">=</span> sfContext<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">getInstance</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getUser</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getAttribute</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'my_model.max_per_page'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(177, 177, 0);">return</span> <span style="color: rgb(0, 0, 136);">$max</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">else</span>
<span style="color: rgb(177, 177, 0);">return</span> parent<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">getPagerMaxPerPage</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>
It's all as easy as it could be. The controller searches the servers for the current user session data and returns either the custom data (if found) or the default value (which is taken from the <i>generator.yml</i> file).
</p>
<p>
Unfortunately, the <i>sfContext::getInstance()</i> is used here (this causes a lot of problems when <i>the default context</i> problem occurs). After a quick look I didn't find the better way to access the user from the configuration generator (but if you know how to - let me know ;).
</p>
(I'm wondering why it's not built in into symfony).Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-62521925824001738852011-08-16T07:56:00.000+02:002013-04-20T19:04:26.170+02:00doctrine act as signable plugin - new releases<p>After few months, new versions 1.2.2 and 1.2.3 of <a href="http://www.symfony-project.org/plugins/sfDoctrineActAsSignablePlugin">sfDoctrineActAsSignablePlugin</a> have been released. In short, the plugin provides a <b>Signable</b> behavior which automatically stores information on who has created or updated a given object.<br />
</p><br />
<h2>what's new</h2><p>A fellow symfony developer, <b>Daniel Möllenbeck</b>, suggested that there are some options missing in the behavior configuration. Until version 1.2.1, the <i>onDelete</i> option was hardcoded as <i>CASCADE</i>, which - as Daniel emphasised - may cause problems when a given user is supposed to be deleted (physically from the database, not <a href="http://symfony-world.blogspot.com/2010/10/doctrine-softdelete-behavior-usage.html">softDeleted</a>).<br />
</p><br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">ALTER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> customer
<span style="color: rgb(153, 51, 51); font-weight: bold;">ADD</span> CONSTRAINT customer_created_by_sf_guard_user_id
<span style="color: rgb(153, 51, 51); font-weight: bold;">FOREIGN</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">KEY</span> <span style="color: rgb(102, 204, 102);">(</span>created_by<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">REFERENCES</span> sf_guard_user<span style="color: rgb(102, 204, 102);">(</span>id<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">ON</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">DELETE</span> CASCADE;</pre><br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">ALTER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> customer
<span style="color: rgb(153, 51, 51); font-weight: bold;">ADD</span> CONSTRAINT customer_updated_by_sf_guard_user_id
<span style="color: rgb(153, 51, 51); font-weight: bold;">FOREIGN</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">KEY</span> <span style="color: rgb(102, 204, 102);">(</span>updated_by<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">REFERENCES</span> sf_guard_user<span style="color: rgb(102, 204, 102);">(</span>id<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">ON</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">DELETE</span> CASCADE;</pre><br />
<p>Let's imagine user enters customers over and over, then leaves the company. Now the admin deletes the user - probably the hard way (directly on the database, maybe the user is auto-created from somewhere...) --> The constraint will trigger the deletion of all customers created by that user. I have some doubt anybody would be happy about that.<br />
<br />
Having the constraint like that does the trick:<br />
<br />
a) allow NULL values in created_by/updated_by columns:<br />
<br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">ALTER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> customer
<span style="color: rgb(153, 51, 51); font-weight: bold;">ADD</span> CONSTRAINT customer_created_by_sf_guard_user_id
<span style="color: rgb(153, 51, 51); font-weight: bold;">FOREIGN</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">KEY</span> <span style="color: rgb(102, 204, 102);">(</span>created_by<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">REFERENCES</span> sf_guard_user<span style="color: rgb(102, 204, 102);">(</span>id<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">ON</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">DELETE</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">SET</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">NULL</span>;</pre><br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">ALTER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> customer
<span style="color: rgb(153, 51, 51); font-weight: bold;">ADD</span> CONSTRAINT customer_updated_by_sf_guard_user_id
<span style="color: rgb(153, 51, 51); font-weight: bold;">FOREIGN</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">KEY</span> <span style="color: rgb(102, 204, 102);">(</span>updated_by<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">REFERENCES</span> sf_guard_user<span style="color: rgb(102, 204, 102);">(</span>id<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">ON</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">DELETE</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">SET</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">NULL</span>;</pre><br />
b) forbid the deletion of the user:<br />
<br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">ALTER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> customer
<span style="color: rgb(153, 51, 51); font-weight: bold;">ADD</span> CONSTRAINT customer_created_by_sf_guard_user_id
<span style="color: rgb(153, 51, 51); font-weight: bold;">FOREIGN</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">KEY</span> <span style="color: rgb(102, 204, 102);">(</span>created_by<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">REFERENCES</span> sf_guard_user<span style="color: rgb(102, 204, 102);">(</span>id<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">ON</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">DELETE</span> RESTRICT;</pre><br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">ALTER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> customer
<span style="color: rgb(153, 51, 51); font-weight: bold;">ADD</span> CONSTRAINT customer_updated_by_sf_guard_user_id
<span style="color: rgb(153, 51, 51); font-weight: bold;">FOREIGN</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">KEY</span> <span style="color: rgb(102, 204, 102);">(</span>updated_by<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">REFERENCES</span> sf_guard_user<span style="color: rgb(102, 204, 102);">(</span>id<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">ON</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">DELETE</span> RESTRICT;</pre><br />
c) do nothing<br />
<br />
<h2>moreover</h2><br />
<p>Another fellow symfony developer, Christoph Berg, helped me to trace the bug with fixtures on created_by/updated_by values. Now, the bug is fixed and all fixture data works perfectly.<br />
</p><br />
<h2>the community</h2><br />
<p>Taking the opportunity, I'd like to thank Daniel, Christoph and all other symfony developers who share their opinions on my work - they help to find bugs and suggest some good ideas on how to improve the code. Thanks to you, the plugin gets better and better all the time. Thanks, guys!<br />
</p><br />
<p>Please, feel free to share your opinion and comment on the plugin!<br />
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com6tag:blogger.com,1999:blog-3825636150904136311.post-50874907747890785602011-08-15T18:11:00.000+02:002013-04-20T19:07:36.298+02:00symfony custom configuration files<h1>dynamic and cross-application configuration</h1><p>If your project gets very big and it has several applications, you may want to create cross-application configuration - store it in one place and let all applications use it (no matter how big it is). Or simply if your configuration is becoming really big and you want to arrange it somehow, custom config files is exactly what you are looking for!<br />
</p><br />
<h1>how to do that</h1><p>That's only three easy steps. First, let's create a custom configuration YAML file (<strong>config/something.yml</strong>, in the project main directory, not the app config directory) and put some data there:<br />
<pre>prod:
test: ok
dev:
test: ko
all:
foo: bar
john: doe
</pre></p><br />
<p>The second step is to create/update a config handler YAML file (<strong>config/config_handlers.yml</strong>) with following example content:<br />
<pre>config/something.yml:
class: sfDefineEnvironmentConfigHandler
param:
prefix: something_
</pre></p><br />
<p>The last step is to register the configuration file in all applications you want (after this step, the configuration will be available for an application). Use the application configuration class (e.g. <strong>apps/frontend/config/frontendConfiguration.class.php</strong>):<br />
<br />
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> configure<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(51, 153, 51);">....</span>
<span style="color: rgb(177, 177, 0);">require_once</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">getConfigCache</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">checkConfig</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'config/something.yml'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>Now all config options defined in the new file are available in the application with the <strong>_something</strong> prefix. The best way to check if the new file is available for the application is to run it in the <i>dev</i> mode in the browser and check the <i>web debug toolbar</i>, <i>config</i> icon, <i>settings</i> section. All configuration available for the application is displayed there. If you followed all three steps above, new file configuration should be available.<br />
<img src="http://img846.imageshack.us/img846/5497/fsettings.png" /><br />
</p><br />
<h1><a href="http://www.symfony-project.org/plugins/sfAdminDashPlugin">sfAdminDashPlugin trick</a></h1><p>This plugin provides an easy-to use menu divided into sections, which is configurable in YAML files. If you want to have different menus for different applications, you just have to put all configuration in the application config (by default, it's stored in <a href="http://trac.symfony-project.org/browser/plugins/sfAdminDashPlugin/trunk/config/app.yml">the plugin config</a>). One way is to put all menu config in the <i>app.yml</i> file, but, as mentioned before, it can make the app.yml file grow to an enormous size, so a better solution is to create a <i>menu.yml</i> file, which is loaded by the config handler. Everything is done the same way as before:<br />
<ul><li>create <strong>apps/APP/config/menu.yml</strong> file and define sfAdminDash menu there</li>
<li>create or update (if exists) the config handler file (<strong>apps/APP/config/config_handlers.yml</strong>)</li>
<li>finally, update the APP configuration class (<strong>apps/APP/config/APPConfiguration.class.php</strong>) by registering new config file</li>
</ul></p><br />
<p>I hope some of you will find this flexible configuration useful :).<br />
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-30960635311571952532011-06-26T22:54:00.000+02:002013-04-20T18:49:59.191+02:00Doctrine Event Listeners vs symfony fixtures<h2>introduction</h2><br />
<p>This is just an easy tip on how to use <a href="http://www.symfony-project.org/gentle-introduction/1_4/en/16-Application-Management-Tools#chapter_16_populating_a_database">symfony fixtures</a> along with <a href="http://www.doctrine-project.org/projects/orm/1.2/docs/manual/event-listeners/en">doctrine event listeners</a> in a symfony project.<br />
</p><br />
<h2>event listeners</h2><br />
<p><strong>Doctrine Event Listeners</strong> are the methods you define to trigger an activity always after/before a Doctrine Record is created, updated, deleted, etc. These can be: <i>postInsert</i>, <i>preInsert</i>, <i>postDelete</i>, <i>preDelete</i> and so on. They can be very useful to keep the code clean and logical. A good example of usage is uploading an image and creating many different files with different sizes, representing the same image (custom thumbnails). This could be done with overriding <i>ImageForm</i> upload mechanisms, but <i>postInsert</i> seems more clean and easier to maintain if uploading is done using more than one symfony form class.<br />
</p><br />
<h2>fixtures</h2><br />
<p><strong>Fixtures</strong> are a built-in symfony feature which enables you to easily populate the database with some data. It is usually test data, but for some projects, some initial data can be held in fixtures (e.g. initial configuration values). They should be used in all projects, since it's a lot easier to spot any bug using data that pretends to be real.<br />
</p><br />
<h2>the problem</h2><br />
<p>The problem occurs when you combine those two features. Imagine you've got two models in your projects, A and B, related 1:1 (e.g. <cite>sf_guard_user</cite> with <cite>sf_guard_user_profile</cite> from <a href="http://www.symfony-project.org/plugins/sfDoctrineGuardPlugin">sfDoctrineGuardPlugin</a>). For each A record there has to exist the B record. This means, when the mother record is created (suppose A is the master model), the B record needs to be immediately created and related to A. A Doctrine Event Listener is what should be used - a A::postInsert() that creates the B record.<br />
</p><br />
<p>So far, so good - but we still want to use symfony fixtures. Let's say, we want to define 10 sample A records along 10 sample B records. So we write a fixture file with 10 A and 10 B fixtures. Next, we fire the<br />
<pre>./symfony doctrine:build --all --and-load
</pre>command and what do we find? B records are doubled! And this can be quite difficult to spot!<br />
</p><br />
<p>Each time when a A fixture is saved, the postInsert is automatically executed, therefore new B record is created. So instead of creating records in the following order: A,A,A,...(10),B,B,B,...(10), we get A,B,A,B,A,B,...(10),B,B,B,...(10), having 10 A records and 20 B records. And this is not what we wanted. Fortunately, this can be quite easily fixed.<br />
</p><br />
<h2>the solution</h2><br />
<p>Just add one line to the Doctrine Event Listener:<br />
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> postInsert<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$event</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'cli'</span> <span style="color: rgb(51, 153, 51);">!=</span> <a href="http://www.php.net/php_sapi_name"><span style="color: rgb(153, 0, 0);">php_sapi_name</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(102, 102, 102); font-style: italic;">// some code here</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>We're basing on the fact, that fixtures are loaded from a <strong>task</strong>, which uses <strong>command line interface (cli)</strong>. Now, no fixtures use <strong>Doctrine Event Listeners</strong>.<br />
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com5tag:blogger.com,1999:blog-3825636150904136311.post-87972536788202554172011-05-08T14:54:00.000+02:002013-04-20T19:04:54.392+02:00data migration with symfony & doctrine<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhddZhvssZqc_Hq7okTYybO1JR-6T3LXUeMU6OjAEbKZymAmKqehtTVLzCm-mzk1FALLBwOEKemK5IikIntROUAD6p3MJ4fYahTX_Supm2TCB3tCldf49ZIGOf1ILSXT6vTeD2BWwhp11o/s1600/data_migration.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhddZhvssZqc_Hq7okTYybO1JR-6T3LXUeMU6OjAEbKZymAmKqehtTVLzCm-mzk1FALLBwOEKemK5IikIntROUAD6p3MJ4fYahTX_Supm2TCB3tCldf49ZIGOf1ILSXT6vTeD2BWwhp11o/s576/data_migration.png" /></a><span class="movie">Scene from "Apocalypse Now" by Francis Ford Coppola (1979)</span></div>
<h2>Diving into 'outsourcing applications with symfony': migrating data</h2><p>This post was inspired by <a href="http://www.fizyk.net.pl">Fizyk</a>'s <a href="http://symfony-world.blogspot.com/2011/01/outsourcing-applications-with-symfony.html?showComment=1295252059895#c8357194602331033272">comment</a> on the <a href="http://symfony-world.blogspot.com/2011/01/outsourcing-applications-with-symfony.html">outsourcing applications with symfony</a> article. Fizyk suggested that it would be a good idea to wrap all data migration functionalities into a symfony task(s). Basically, migrating data between different versions of the same project is the topic I want to focus on in this article.</p><br />
<h2>new version of a project, written from scratch</h2><p>In the previous article, we've been discussing developing a new version of an old project from scratch (e.g. because the old one was so inflexible that it was unable to be extended). <u>The first thing we should do is to design new database structure that will hold all data that the present project holds</u> (I assume that the database structure will have to be different, because if it was good, no one would redevelop entire project from scratch). Depending on your luck, it can be an easy task or a nightmare [<i>I've already had redeveloped 2 projects with databases that were not even in the <a href="http://en.wikipedia.org/wiki/First_normal_form">first normal form</a>... yes, there are projects like this. Check this out: one of these projects were made by a really bad developer who never used any type of version control system. He used no development environment (only production) and no localhost machines to test new features before they are added. He made all modifications on the production server, even if it took 2 weeks until they were finished. And there was only FTP access on the server... Oh, yes, there ae projects like that... and you may be asked to improve such projects :)</i>].<br />
</p><br />
<h2>data migration</h2><p>Now when we have two parallel database structures, we need to copy all data from the old one to the new one. Depending on the data (its size, structure, relations, constraints, etc.), I use one of the following:<br />
<ul><li>pure Doctrine (Doctrine in - Doctrine out)</li>
<li>mixed (Doctrine in - SQL out)</li>
<li>pure SQL (SQL query)</li>
</ul>For each data structure, I choose one of the above migration types very carefully (detailed descriptions below). If the project is big (and I assume this is the case), the development phase takes a lot of time. It's very important that you can run entire migration job:<br />
<ul><li><b>as fast as possible</b>: this enables you to run the migration <u>very often</u>, because it doesn't take too much time. And this gives you the possibility to <u>improve</u> the scripts, <u>test them carefully</u> (each mistake made during the migration will be painful when new project is started in production environment). And, finally, you can download the production (old project) database and migrate it to the new structure to compare the frontend of both projects - this helps to develop the business logic,</li>
<li><b>with wrapped interface</b>: click once - make yourself a cup of tea - come back in 10 minutes - click again - go out with your dog - come back in 15 minutes - the migration is done. Or otherwise: imagine you have a project with 50 tables and you have to run 50 actions (e.g. by a click), each one for each table - or call 50 symfony tasks, not to mention that you need to call them in appropriate order (because data is related in RDBMS) and you have to watch out not to break the order. Of course, there has to be 50 small jobs, deep inside the migration system, but your interface should do the work for you automatically (check an example below).</li>
<li><b>avoid repeating the same manual modifications</b>. This is difficult to discuss in general - I'll give an example. I've had an <i>action</i> table which stored users activity. The old project database had a record which stated that <i>user X</i> has replied to <i>topic Y</i> with <i>post Z</i>. And neither <i>topic Y</i> nor <i>post Z</i> existed in the database (again, if the project is so bad that it has to be redeveloped from scratch, such bad mistakes are very common). Of course, the migration system failed each time when symfony tried to insert a record that was related to a non-existent record. <u>What to do</u>?<br />
<ul><li><b>bad solution</b>: remove this <i>action</i> record from you old database local copy (you download the old project database from the production server, load it to your SQL localhost machine and everythng is 100% safe)</li>
<li><b>good solution</b>: remove this record from production database, because it already points some stupid and non-existing stuff.</li>
</ul><u>Why to do so</u>? Because if such mistakes were made in the old project (data inconsistency), there can be quite a lot of such paniful <i>action</i> table, which will make a single migration job really long. And after you have done the whole migration 2 or 3 times, it'll be enough or dumb work and you won't want to do it any more. Again, this is only a simple example. Each situation will probably need a different solution</li>
</ul>There is one more important thing: <b>no matter how complicated your migration job is, always create a detailed list of steps to make, describing what is being done at the moment, why such order and what is already migrated/what is still waiting to be migrated</b>. With such list, performing data migration is really easy and unstressful :)<br />
</p><br />
<p>Here you have an example of a migration job order from a project I did recently (all .sql files are '<i>pure SQL</i>' migrations):<br />
<a href="http://img863.imageshack.us/img863/3977/migrationjoborder.png"><img src="http://img863.imageshack.us/img863/3977/migrationjoborder.th.png"></a><br />
where the action <i>http://polonia/backend_dev.php/migrator/migrateAll</i> calls the following (these are the only '<i>pure Doctrine</i>' migrations):<br />
<pre class="php" style="font-family: monospace;">MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateConfig</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateForbiddenPhrases</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateQuotes</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateArticles</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateReadingCategories</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateReadingTextbooks</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateCountries</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateRegions</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
MigrationManager<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">migrateSubpages</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre></p><br />
<h2>pure Doctrine migration</h2><p>This is the only code example in the <a href="http://symfony-world.blogspot.com/2011/01/outsourcing-applications-with-symfony.html">outsourcing applications with symfony</a> article. And this is probably the only migration type that would be wrapped in a symfony task, as <a href="http://www.fizyk.net.pl">Fizyk</a> suggested. This is really easy - just fetch data from the old system and save it in the new system.<br />
<ul><li>used for <b>small amounts of data</b></li>
</ul>Below is another example of this data migration technique - private messages sent between users of a social website:<br />
<pre class="php" style="font-family: monospace;">static <span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> migrateMessages<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$wiadomosci</span> <span style="color: rgb(51, 153, 51);">=</span> Doctrine_Query<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">create</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">from</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'Wiadomosci w'</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">orderBy</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'w.wiad_id'</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">fetchArray</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">foreach</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$wiadomosci</span> <span style="color: rgb(177, 177, 0);">as</span> <span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$message</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> Message<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setId</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'wiad_id'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setTitle</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'temat'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setText</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'tresc'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setFromId</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'wiad_od'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setToId</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'wiad_do'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setDisplayed</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'przeczytane'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setProvoke</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'zaczep'</span><span style="color: rgb(0, 153, 0);">]</span> <span style="color: rgb(51, 153, 51);">==</span> <span style="color: rgb(0, 0, 255);">'t'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setCreatedAt</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'kolumna_data'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setUpdatedAt</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$row</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'kolumna_data'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$message</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">save</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(0, 153, 0);">}</span></pre></p><br />
<h2>pure SQL migration</h2><p>You may ask, why native SQL INSERT queries are constructed to be executed immediately, instead of using Doctrine? The answer is obvious: <b>time difference</b>. Doctrine is nice, but in fact, it is really slow. The problem with <i>pure Doctrine migration</i> is that it takes a lot of time to load all data with PHP/Doctrine and then to insert it into new structure. The table from the example above held over 50'000 records. At my local machine, it took several minutes to finish the job. It's too much (<b>data migration should be as fast as possible</b>). The solution to this problem is simply to create a sql script that will do the job thousand times faster:<br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">INSERT</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">INTO</span> NEW_DB<span style="color: rgb(102, 204, 102);">.</span>message <span style="color: rgb(102, 204, 102);">(</span>
id<span style="color: rgb(102, 204, 102);">,</span>
title<span style="color: rgb(102, 204, 102);">,</span>
text<span style="color: rgb(102, 204, 102);">,</span>
from_id<span style="color: rgb(102, 204, 102);">,</span>
to_id<span style="color: rgb(102, 204, 102);">,</span>
displayed<span style="color: rgb(102, 204, 102);">,</span>
provoke<span style="color: rgb(102, 204, 102);">,</span>
created_at<span style="color: rgb(102, 204, 102);">,</span>
updated_at
<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">SELECT</span>
wiad_id<span style="color: rgb(102, 204, 102);">,</span>
temat<span style="color: rgb(102, 204, 102);">,</span>
tresc<span style="color: rgb(102, 204, 102);">,</span>
wiad_od<span style="color: rgb(102, 204, 102);">,</span>
wiad_do<span style="color: rgb(102, 204, 102);">,</span>
przeczytane<span style="color: rgb(102, 204, 102);">,</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">IF</span><span style="color: rgb(102, 204, 102);">(</span>zaczep <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(255, 0, 0);">'t'</span><span style="color: rgb(102, 204, 102);">,</span> <span style="color: rgb(204, 102, 204);">1</span><span style="color: rgb(102, 204, 102);">,</span> <span style="color: rgb(204, 102, 204);">0</span><span style="color: rgb(102, 204, 102);">)</span><span style="color: rgb(102, 204, 102);">,</span>
kolumna_data<span style="color: rgb(102, 204, 102);">,</span>
kolumna_data
<span style="color: rgb(153, 51, 51); font-weight: bold;">FROM</span> OLD_DB<span style="color: rgb(102, 204, 102);">.</span>wiadomosci
<span style="color: rgb(153, 51, 51); font-weight: bold;">ORDER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">BY</span> wiad_id</pre>Sometimes the case may be even worse. The <i>action</i> table, mentioned before, held over 400'000 records and the PHP/apache configuration would need really big limits (and the migration would take several hours to finish). The following is another sql script, migrating action data:<br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">INSERT</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">INTO</span> NEW_DB<span style="color: rgb(102, 204, 102);">.</span>action <span style="color: rgb(102, 204, 102);">(</span>
created_at<span style="color: rgb(102, 204, 102);">,</span>
created_by<span style="color: rgb(102, 204, 102);">,</span>
type_id_external<span style="color: rgb(102, 204, 102);">,</span>
target_profile_id<span style="color: rgb(102, 204, 102);">,</span>
forum_post_id<span style="color: rgb(102, 204, 102);">,</span>
forum_topic_id
<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">SELECT</span> kolumna_data<span style="color: rgb(102, 204, 102);">,</span> u_id<span style="color: rgb(102, 204, 102);">,</span>
CASE akcja
WHEN <span style="color: rgb(255, 0, 0);">"profil"</span> THEN <span style="color: rgb(204, 102, 204);">1</span>
WHEN <span style="color: rgb(255, 0, 0);">"logowanie"</span> THEN <span style="color: rgb(204, 102, 204);">2</span>
WHEN <span style="color: rgb(255, 0, 0);">"wylogowanie"</span> THEN <span style="color: rgb(204, 102, 204);">3</span>
WHEN <span style="color: rgb(255, 0, 0);">"pm"</span> THEN <span style="color: rgb(204, 102, 204);">4</span>
WHEN <span style="color: rgb(255, 0, 0);">"zaczep"</span> THEN <span style="color: rgb(204, 102, 204);">5</span>
WHEN <span style="color: rgb(255, 0, 0);">"post"</span> THEN <span style="color: rgb(204, 102, 204);">6</span>
WHEN <span style="color: rgb(255, 0, 0);">"watek"</span> THEN <span style="color: rgb(204, 102, 204);">7</span>
END<span style="color: rgb(102, 204, 102);">,</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">IF</span> <span style="color: rgb(102, 204, 102);">(</span>akcja <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(255, 0, 0);">'profil'</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">OR</span> akcja <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(255, 0, 0);">'pm'</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">OR</span> akcja <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(255, 0, 0);">'zaczep'</span><span style="color: rgb(102, 204, 102);">,</span> akcja_id<span style="color: rgb(102, 204, 102);">,</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">NULL</span><span style="color: rgb(102, 204, 102);">)</span><span style="color: rgb(102, 204, 102);">,</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">IF</span> <span style="color: rgb(102, 204, 102);">(</span>akcja <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(255, 0, 0);">'post'</span><span style="color: rgb(102, 204, 102);">,</span> post_id<span style="color: rgb(102, 204, 102);">,</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">NULL</span><span style="color: rgb(102, 204, 102);">)</span><span style="color: rgb(102, 204, 102);">,</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">IF</span> <span style="color: rgb(102, 204, 102);">(</span>akcja <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(255, 0, 0);">'watek'</span><span style="color: rgb(102, 204, 102);">,</span> akcja_id<span style="color: rgb(102, 204, 102);">,</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">NULL</span><span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">FROM</span> OLD_DB<span style="color: rgb(102, 204, 102);">.</span>akcje <span style="color: rgb(153, 51, 51); font-weight: bold;">ORDER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">BY</span> kolumna_data <span style="color: rgb(153, 51, 51); font-weight: bold;">ASC</span></pre>Pure SQL migration may be a little bit more complicated (processing data in PHP is easier) - but you'll surely save a lot of time.<br />
<ul><li>used for <b>huge amounts of data</b>, as this can be the only possibility to migrate it</li>
</ul></p><br />
<h2>mixed migration: Doctrine IN / SQL OUT</h2><p>Sometimes it can be impossible to create a magnificent "<b>INSERT INTO SELECT FROM</b>" query. PHP, although being as slow as a dead snail (in comparison to SQL database management systems), still gives more possibilities just because being an imperative programming language. The idea of this migration type is to fetch the old system data using Doctrine, generate the SQL code and execute it. You may execute such generated script in any way you like - db console, tools like phpmyadmin or even forcing Doctrine to execute raw SQL. If someone prefers, this can be also done as a symfony task - but if the project is really big, you don't benefit from wrapping such mechanism into a symfony task.<br />
</p><p>To fully understand the idea of this data migration type, take a look at the example below:<br />
<pre class="php" style="font-family: monospace;">static <span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> getInexistentProfileIds<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$u_ids</span> <span style="color: rgb(51, 153, 51);">=</span> Doctrine_Query<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">create</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">from</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'Uzytkownicy u'</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">select</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'u.u_id'</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">orderBy</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'u.u_id'</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">fetchArray</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$ids</span> <span style="color: rgb(51, 153, 51);">=</span> <a href="http://www.php.net/range"><span style="color: rgb(153, 0, 0);">range</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(204, 102, 204);">1</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$u_ids</span><span style="color: rgb(0, 153, 0);">[</span><a href="http://www.php.net/count"><span style="color: rgb(153, 0, 0);">count</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$u_ids</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">-</span><span style="color: rgb(204, 102, 204);">1</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'u_id'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">foreach</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$u_ids</span> <span style="color: rgb(177, 177, 0);">as</span> <span style="color: rgb(0, 0, 136);">$u_id</span><span style="color: rgb(0, 153, 0);">)</span>
<a href="http://www.php.net/unset"><span style="color: rgb(153, 0, 0);">unset</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$ids</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 136);">$u_id</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'u_id'</span><span style="color: rgb(0, 153, 0);">]</span> <span style="color: rgb(51, 153, 51);">-</span> <span style="color: rgb(204, 102, 204);">1</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">return</span> <span style="color: rgb(0, 0, 136);">$ids</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre><pre class="php" style="font-family: monospace;">static <span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> generateSqlToRebuildUsers<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$ids</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">self</span><span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">getInexistentProfileIds</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$sql</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 255);">'INSERT INTO profile (`id`) VALUES ('</span><span style="color: rgb(51, 153, 51);">.</span><a href="http://www.php.net/implode"><span style="color: rgb(153, 0, 0);">implode</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'), ('</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$ids</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">')'</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">echo</span> <span style="color: rgb(0, 0, 136);">$sql</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">echo</span> <span style="color: rgb(0, 0, 255);">'<hr />'</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$sql</span> <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 0, 255);">'INSERT INTO sf_guard_user (`id`, `email_address`, `username`,`is_active`) VALUES ('</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">foreach</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$ids</span> <span style="color: rgb(177, 177, 0);">as</span> <span style="color: rgb(0, 0, 136);">$id</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 0, 136);">$sql</span> <span style="color: rgb(51, 153, 51);">.=</span> <span style="color: rgb(0, 0, 136);">$id</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">',"NULL-'</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 136);">$id</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">'","NULL-'</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 136);">$id</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">'", 0), ('</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">echo</span> <span style="color: rgb(0, 0, 136);">$sql</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre>The reason for doing mixed migration in this case is data inconsistency in the old project. Users were creating forum topics and posts. And when a user was deleted, posts and topics still referred to it. Migrating forum data returned dozens of errors, all of them was failing constraint - assigning topic/post to a non-existent user. The solution was to create <i>dead user records</i> (with deleted_at set), just for the SQL not to break the constraint. The first method fetches all IDs of the users that has been deleted, the second part generates the SQL code creating those dead records into the database. <i>This is only an example, of course, and the example above may be found controversial :), but, believe me, migrating an entire system (which was really, really badly developed) is a difficult task itself, the time you have is limited and sometimes you are forced to use somehow brutal solutions.</i> Anyway, it's up to you how you want to grab the generated SQL code. For me it was the easiest way to display it in the browser, copy-paste to phpmyadmin and execute. But you can generate a sql file, wrap it into a symfony task or do whatever you think of. The main idea is just to make the data migration faster.<br />
</p><br />
<h2>and that's it</h2><p>I hope that some of my hints will be useful if you ever need to migrate data between different versions of the same project. Comments are welcome :)<br />
</p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0Central Europe51.242897904529244 22.53450356176756546.412902904529247 13.394686061767565 56.07289290452924 31.674321061767564tag:blogger.com,1999:blog-3825636150904136311.post-291488096818052282011-04-18T01:11:00.000+02:002013-04-20T19:04:43.075+02:00faster queries: indexing tables<p>When designing complex web applications, you have to pay attention to the project performance to make the framework handle your request as fast as possible. This involves optimising client side (clean CSS, clean HTML, fast Javascript, etc.) and server side (caching templates and queries, usage of the database and many others). We will concentrate on the database here. In short, the database should have such structure that all information fetched to handle any request should be accessible very fast. This short article will show you few facts and tricks about symfony projects performance.<br />
</p><br />
<h1>built-in foreign key management</h1><br />
<p>One of the brilliant features in symfony is creating indexes for foreign keys by default. This saves a lot of time for the developers and, surely, leverages the overall performance of all symfony applications. Below is an example schema:<br />
</p><br />
<pre>Book:
actAs:
Timestampable: ~
SoftDelete: ~
columns:
category_id:
type: integer
notnull: true
comment: "kategoria książek"
title:
type: string(255)
notnull: true
comment: "tytuł"
author:
type: string(255)
comment: "autor"
description:
type: string
comment: "opis"
relations:
Category:
class: BookCategory
local: category_id
foreign: id
foreignAlias: Books
BookCategory:
actAs:
Timestampable: ~
SoftDelete: ~
columns:
name:
type: string(255)
notnull: true
comment: "nazwa"
</pre><br />
<p>Such schema will generate the following SQL code. Note that the <strong>book.category_id</strong> column is indexed (faster queries) and constrainted (no data inconsistency) at the same time, <strong>automatically</strong>:<br />
</p><br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">CREATE</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> book_category <span style="color: rgb(102, 204, 102);">(</span>id BIGINT <span style="color: rgb(153, 51, 51); font-weight: bold;">AUTO_INCREMENT</span> <span style="color: rgb(102, 204, 102);">...</span>;
<span style="color: rgb(153, 51, 51); font-weight: bold;">CREATE</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> book <span style="color: rgb(102, 204, 102);">(</span>id BIGINT <span style="color: rgb(153, 51, 51); font-weight: bold;">AUTO_INCREMENT</span> <span style="color: rgb(102, 204, 102);">...</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">INDEX</span> category_id_idx <span style="color: rgb(102, 204, 102);">(</span>category_id<span style="color: rgb(102, 204, 102);">)</span> <span style="color: rgb(102, 204, 102);">...</span>;
<span style="color: rgb(153, 51, 51); font-weight: bold;">ALTER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> book <span style="color: rgb(153, 51, 51); font-weight: bold;">ADD</span> CONSTRAINT book_category_id_book_category_id
<span style="color: rgb(153, 51, 51); font-weight: bold;">FOREIGN</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">KEY</span> <span style="color: rgb(102, 204, 102);">(</span>category_id<span style="color: rgb(102, 204, 102);">)</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">REFERENCES</span> book_category<span style="color: rgb(102, 204, 102);">(</span>id<span style="color: rgb(102, 204, 102);">)</span>;</pre><br />
<p>Obviously, you may create your custom indexes (and this will be discussed later). This <a href="http://www.symfony-project.org/doctrine/1_2/en/04-Schema-Files#chapter_04_indexes">section from <i>The symfony and Doctrine book</i></a> covers the topic of custom indexes.</p><br />
<h1>optimising MySQL queries</h1><br />
<p>Before you do anything to speed up your queries execution, you need to know what queries you have in your system. The obvious thing is to look at the powerful <strong>web debug toolbar</strong>. It's a great tool, but it won't tell you what to do when executing your queries takes too long. But it can point out which queries are definitely poorly written (they are highlighted with orange color then). Then it's time for you to solve the problem. Often, it may happen that you need to join tables (more about this is written in "<a href="http://symfony-world.blogspot.com/2011/02/less-doctrine-queries-in-symfony-admin.html">less doctrine queries</a>" article).<br />
</p><br />
<p>If the number of queries cannot be limited, probably you may need to add custom indexes on your tables. An index is an additional structure, bound to a table, that speeds up selecting the appropriate results (there are lots of good tutorials on this topic, such as <a href="http://www.tizag.com/mysqlTutorial/mysql-index.php">the tizag tutorials</a>). The database server, when executing any query, looks for the best structure that can be used to serve the results as fast as possible. We can ask our database server to analyse given any query to tell us how is it going to be executed. And the best tool to visualise this is the <strong>EXPLAIN</strong> statement (<a href="http://www.dbtuna.com/article.php?id=14">short reference here</a>). We will optimise a heavy query executed on the homepage of a social website, using <strong>explain</strong> and <strong>adding custom index</strong>.<br />
</p><br />
<h1>example - social website homepage problem</h1><br />
<p>The manager of the social website wants the developers to emphasise the users who are the most active ones. For example, he wants to display last logged users at the homepage. The developers figured out that they need to create an <i>action</i> table that will store actions performed by users. <i>Action</i> and <i>profile</i> tables are related to each other - a simple <strong>JOIN</strong> will be used each time when the homepage action is executed: last logged x profiles are fetched from the database and displayed then.<br />
</p><br />
<p>The website has been set off. Many users have registered and the action table is growing bigger and bigger every day. After few months, it has over 300'000 records. The manager is very happy that his project is becoming popular, but he noticed that the homepage is being served few seconds slower than it was in the beginning. The developers tell him that they didn't run high performance tests and they have to spend some time on optimisation. The manager is not pleased that it was not considered before.<br />
</p><br />
<h1>NOTE: always use test data when focusing on project performance</h1><p>Symfony has a built-in fixture mechanism which allows you to easily generate lots of different data (see the <a href="http://www.symfony-project.org/jobeet/1_4/Doctrine/en/03#chapter_03_the_initial_data">jobeet tutorial</a>). This is essential when you want to make sure that your project will manage with big popularity. Anyway, if you decide to generate really big amount of data, do NOT use any ORM. It consumes too much memory and generating fixtures takes a lot of your precious time. I'd suggest to generate raw SQL INSERT queries instead - they'll be a lot faster.<br />
</p><br />
<p>Okay, let's move on. Once you have got lots of data (either real or test), execute each action - one after another - and check its performance. First thing you should look at is the mentioned <strong>web debug toolbar</strong> in the top right corner of the screen when running <strong>dev application environment</strong>. You should be worried, when you see something like the following:<br />
</p><br />
<img src="http://img251.imageshack.us/img251/2759/toolbart.png" /><br />
<br />
<p>There is a big problem: at least one of the queries is unoptimal (orange color) and as a consequence, executing this action takes too much time (imagine, almost 5 secs per one execution is really long and it doesn't matter that I'm using my personal computer for testing). Left click on the query icon (the one to the right):<br />
</p><br />
<a href="http://img163.imageshack.us/img163/4959/badquery.png"><br />
<img src="http://img163.imageshack.us/img163/4959/badquery.th.png" /><br />
</a><br />
<br />
<p>One query takes almost 4 seconds to be executed. This surely causes a serious performance problem! Don't panic, just let your database server analyse the query for you:</p><br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">EXPLAIN</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">SELECT</span> a<span style="color: rgb(102, 204, 102);">.</span>id <span style="color: rgb(153, 51, 51); font-weight: bold;">AS</span> a__id<span style="color: rgb(102, 204, 102);">,</span> a<span style="color: rgb(102, 204, 102);">.</span>created_by <span style="color: rgb(153, 51, 51); font-weight: bold;">AS</span> a__created_by
<span style="color: rgb(153, 51, 51); font-weight: bold;">FROM</span> action a
<span style="color: rgb(153, 51, 51); font-weight: bold;">LEFT</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">JOIN</span> profile p <span style="color: rgb(153, 51, 51); font-weight: bold;">ON</span>
<span style="color: rgb(102, 204, 102);">(</span>a<span style="color: rgb(102, 204, 102);">.</span>created_by <span style="color: rgb(102, 204, 102);">=</span> p<span style="color: rgb(102, 204, 102);">.</span>id <span style="color: rgb(153, 51, 51); font-weight: bold;">AND</span> p<span style="color: rgb(102, 204, 102);">.</span>deleted_at <span style="color: rgb(153, 51, 51); font-weight: bold;">IS</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">NULL</span> <span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">WHERE</span> <span style="color: rgb(102, 204, 102);">(</span>
a<span style="color: rgb(102, 204, 102);">.</span>type_id_external <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(255, 0, 0);">'2'</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">AND</span> p<span style="color: rgb(102, 204, 102);">.</span>avatar_id <span style="color: rgb(153, 51, 51); font-weight: bold;">IS</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">NOT</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">NULL</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">AND</span> p<span style="color: rgb(102, 204, 102);">.</span>mode <span style="color: rgb(102, 204, 102);">=</span><span style="color: rgb(204, 102, 204);">4</span>
<span style="color: rgb(102, 204, 102);">)</span>
<span style="color: rgb(153, 51, 51); font-weight: bold;">ORDER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">BY</span> a<span style="color: rgb(102, 204, 102);">.</span>created_at <span style="color: rgb(153, 51, 51); font-weight: bold;">DESC</span>;</pre><br />
<a href="http://img215.imageshack.us/img215/6981/explainbad.png"><br />
<img src="http://img215.imageshack.us/img215/6981/explainbad.th.png" /><br />
</a><br />
<br />
<p>Here we can see, that the query has to check at least 1690 p (profile) table rows. And each profile record stores a lot of text data, describing each website user. All this makes the query take such long time to execute. If we want to speed it up, we just have to read carefully the query and concentrate on all columns used (and the order of their appearance). The solution is to find the best index (this topic may be quite complex and independent on the framework you use - ask google about indexing database tables/optimising databases - and read those articles carefully).<br />
</p><br />
<p>In this case, the developers executed the following line in MySQL:<br />
<pre class="sql" style="font-family: monospace;"><span style="color: rgb(153, 51, 51); font-weight: bold;">ALTER</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">TABLE</span> profile <span style="color: rgb(153, 51, 51); font-weight: bold;">ADD</span> <span style="color: rgb(153, 51, 51); font-weight: bold;">INDEX</span> deleted_at_idx <span style="color: rgb(102, 204, 102);">(</span>deleted_at<span style="color: rgb(102, 204, 102);">)</span>;</pre>which created an index on the deleted_at datetime column. Thanks to this index, the <i>EXPLAIN</i> command shows that only 10 different rows in the <i>profile</i> table have to be analysed for the query to be executed. And this is a great success - the execution time went down to <b>0,01 second</b>. Imagine the 4 seconds difference for each homepage display. This is the benefit from optimising project databases.<br />
</p><br />
<p>By the way, I cannot understand why the <i>deleted_at</i> column in the <a href="http://www.doctrine-project.org/documentation/manual/1_2/en/behaviors:core-behaviors:softdelete">SoftDelete</a> behavior is not indexed by default, especially when you can turn on automatic not-null-check (sql callback):<br />
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 136);">$manager</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setAttribute</span><span style="color: rgb(0, 153, 0);">(</span>Doctrine_Core<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">ATTR_USE_DQL_CALLBACKS</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 153, 0); font-weight: bold;">true</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre>Provided this line is present in the project configuration, each query that fetches a <i>SoftDelete</i> model will automatically add the "WHERE deleted_at IS NOT NULL" part. It's obvious that such column has to be indexed - the index can be complex though - and the deleted_at can be the last column in this index - but, anyway, default index on deleted_at is a good idea! As you can see, <b>you have to pay attention to what queries are executed inside your projects</b>!<br />
<p><br />
<h1>Note: database server different versions use different indexes</h1><p>Different database server versions may use totally different indexes to execute the same queries on the same database structure! Make sure you run your performance test in the (future) production environment. Otherwise, you may find your application execute unoptimised queries on the production server even if you spent a lot of time optimising it in your local dev machine.<br />
</p><br />
<p>In the example above, it turned out that the production server has a different db server than the developer's local machine. The developer didn't check it - he was not aware of the differences and their negative impact on project's performance. The index that has been built is useless in the production environment (so it should be deleted, because inserting each row is slowed down by this index). Moreover, it happedened, that the new index the developers needed to speed up the query should be build on the <i>action</i> table... <b>pay attention to database server versions you work on</b>!<br />
</p><br />
<h1>how many indexes to create</h1><p>Table indexes are really helpful and they speed up database performance. The more complex your application is, the more queries may be executed. In other words, the bigger your application is, the more queries it may need to provide good performance. But look out - do not create too much indexes and <strong>never create any index when you are not sure that it is used somewhere</strong>. Why? It's very simple - each index is an additional structure which uses some space and needs some time to be created and managed. When a record is inserted, updated or removed, each index has to be updated accordingly to the change made, which surely consumes time. If you create too many indexes, you may simply slow down your database. For example, each user login inserts new action record - then an action table with 10 indexes will be slower than an action table with only 3 indexes).<br />
</p><br />
<h1>further reading: <a href="http://hackmysql.com/case4">this</a>, <a href="http://stackoverflow.com/questions/3049283/mysql-indexes-what-are-the-best-practises">this</a> and <a href="http://www.mysqlfaqs.net/mysql-faqs/Indexes/What-are-best-practices-to-pick-columns-to-index">this</a></h1>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com0tag:blogger.com,1999:blog-3825636150904136311.post-31124168154246174402011-03-29T20:34:00.000+02:002013-04-20T19:05:31.103+02:00symfony validate database content with sfValidatorCallback<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIbu04TH9_6DccOb6BXjxeBJE3Xf_ejOm_gE4UxmgkijVxwEoievVt59rrrPmHlP_-bmJthOEtwmYByx4AJanBi8cvLZ67xeiqARRSVxr0Qir4UR5643benn1XyCu3NbmwtMg24YtDIdA/s1600/validator_callback.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIbu04TH9_6DccOb6BXjxeBJE3Xf_ejOm_gE4UxmgkijVxwEoievVt59rrrPmHlP_-bmJthOEtwmYByx4AJanBi8cvLZ67xeiqARRSVxr0Qir4UR5643benn1XyCu3NbmwtMg24YtDIdA/s576/validator_callback.png" /></a><span class="movie">Scene from "The three Musketeers" by Stephen Herek (1993)</span></div>
<p>In my last project, I needed to create a custom validation feature that would check if there are some specific records in database tables. You can read a lot about symfony validators <a href="http://www.symfony-project.org/forms/1_4/en/B-Validators">at the official documentation</a>, but I didn't found an exact solution to my problem there.</p><br />
<p>There are some <b>customers</b> with their <i>names</i> and their <i>security codes</i> and there are some <b>offers</b> that are bound to customers. There is a offer submission form in the frontend application, where the customer's name and security code have to be given. I found none of the simple validators useful. In the <a href="http://www.symfony-project.org/plugins/sfDoctrineGuardPlugin">sfDoctrineGuardPlugin</a> there is a sfGuardValidatorUser which is used to validate passwords in the signin form:</p><br />
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setWidgets</span><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'username'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormInputText<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'password'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormInputPassword<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'type'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">'password'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'remember'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormInputCheckbox<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setValidators</span><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'username'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorString<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'password'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorString<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'remember'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorBoolean<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">validatorSchema</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setPostValidator</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfGuardValidatorUser<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre><br />
<p>Take a look at the validator's class <a href="http://trac.symfony-project.org/browser/plugins/sfDoctrineGuardPlugin/trunk/lib/validator/sfGuardValidatorUser.class.php">here</a>. In short, the validator retrieves the user object and compares its password stored in the database with the one accessible from the request. As I mentioned, no simple validator can help, so a post validator has to be used on the entire validator schema:</p><br />
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">validatorSchema</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setPostValidator</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfGuardValidatorUser<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre><br />
<p>However, you can have the same effect without creating a custom validator. Symfony framework provides a <a href="http://www.symfony-project.org/forms/1_4/en/B-Validators#chapter_b_sub_sfvalidatorcallback"> callback validator</a>:</p><br />
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">validatorSchema</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setPostValidator</span><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorCallback<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a>
<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'callback'</span> <span style="color: rgb(51, 153, 51);">=></span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'customer_code_callback'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre><br />
<p>The <i>customer_code_callback</i> has to be created now:</p><br />
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 0); font-weight: bold;">public</span> <span style="color: rgb(0, 0, 0); font-weight: bold;">function</span> customer_code_callback<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$validator</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$values</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
<span style="color: rgb(0, 0, 136);">$customer_count</span> <span style="color: rgb(51, 153, 51);">=</span> Doctrine_Query<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">create</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">from</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'Customer c'</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">where</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'c.name = ?'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$values</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'customer'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">andWhere</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'c.code = ?'</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 136);">$values</span><span style="color: rgb(0, 153, 0);">[</span><span style="color: rgb(0, 0, 255);">'code'</span><span style="color: rgb(0, 153, 0);">]</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">count</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(177, 177, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(51, 153, 51);">!</span><span style="color: rgb(0, 0, 136);">$customer_count</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">{</span>
throw <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorError<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 136);">$validator</span><span style="color: rgb(51, 153, 51);">,</span> <span style="color: rgb(0, 0, 255);">'Niepoprawny kod lub firma'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span>
<span style="color: rgb(177, 177, 0);">return</span> <span style="color: rgb(0, 0, 136);">$values</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 153, 0);">}</span></pre><br />
<p>Following the principle <i>the simplest is the best</i>, I'm doing almost the same stuff as the sfGuardValidatorUser, but with less work. Of course, it is up to you to decide whether you prefer to create a simple callback function or a reusable, entire validator class (which is more complicated, more elegant but provides the same functionality). So this is enough for the form to validate the customer's security code, before the offer file can be uploaded.</p><br />
<p>Of course, apart from the post validator, all form widgets can be validated with simple validators:</p><br />
<pre class="php" style="font-family: monospace;"><span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setWidgets</span><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'customer'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormInputText<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'code'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormInputText<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'file'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfWidgetFormInputFile<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>
<span style="color: rgb(0, 0, 136);">$this</span><span style="color: rgb(51, 153, 51);">-></span><span style="color: rgb(0, 64, 0);">setValidators</span><span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'customer'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorString<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">true</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'trim'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">true</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">'Podaj nazwę firmy'</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'code'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorString<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">true</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'trim'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">true</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">'Podaj kod weryfikacyjny'</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'file'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 0); font-weight: bold;">new</span> sfValidatorFile<span style="color: rgb(0, 153, 0);">(</span><a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 153, 0); font-weight: bold;">true</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 0, 255);">'path'</span> <span style="color: rgb(51, 153, 51);">=></span> sfConfig<span style="color: rgb(51, 153, 51);">::</span><span style="color: rgb(0, 64, 0);">get</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">'sf_upload_dir'</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">.</span><span style="color: rgb(0, 0, 255);">'/offer/'</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">,</span> <a href="http://www.php.net/array"><span style="color: rgb(153, 0, 0);">array</span></a><span style="color: rgb(0, 153, 0);">(</span>
<span style="color: rgb(0, 0, 255);">'required'</span> <span style="color: rgb(51, 153, 51);">=></span> <span style="color: rgb(0, 0, 255);">'Wybierz załącznik'</span><span style="color: rgb(51, 153, 51);">,</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span>
<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span></pre><br />
<h2>further usage</h2><br />
<p>The example I gave seems quite similar to password check. But as you can see, you can just replace the example Doctrine query with any other queries, e.g.<br />
<ul><li>check number of offers submitted by a customer, which are still being processed (not finished), if their total count exceeds 10, the validation shall fail</li>
<li>check if there is any free machine/employee that can handle a service, requested by the user; for example, all machines are busy, the validation shows an error <i>you have to wait 36 minutes to wait for the first free machine</i> and, optionally, store the IP to be handled as the first in the queue</li>
</ul></p>Tomaszhttp://www.blogger.com/profile/08377176323730229843noreply@blogger.com6