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

<channel>
	<title>maxgarrick.com &#187; doctrine</title>
	<atom:link href="http://maxgarrick.com/tag/doctrine/feed/" rel="self" type="application/rss+xml" />
	<link>http://maxgarrick.com</link>
	<description>Come take a look under the hood</description>
	<lastBuildDate>Sun, 08 Mar 2009 23:32:09 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.5</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Using Doctrine ORM for more effective and productive development</title>
		<link>http://maxgarrick.com/effective-development-using-doctrine-orm/</link>
		<comments>http://maxgarrick.com/effective-development-using-doctrine-orm/#comments</comments>
		<pubDate>Mon, 01 Dec 2008 05:14:19 +0000</pubDate>
		<dc:creator>max</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[tips]]></category>

		<guid isPermaLink="false">http://maxgarrick.com/?p=70</guid>
		<description><![CDATA[Doctrine ORM is a PHP library that implements the ActiveRecord pattern we have all grown to love.  I&#8217;ve been using it for the past 7 months and feel it is one of the reasons we were able to deliver the MessageBoards web application on-time and within budget.
This doesn&#8217;t mean using Doctrine has been all flowers [...]]]></description>
			<content:encoded><![CDATA[<p><a href='http://www.doctrine-project.org/'>Doctrine ORM</a> is a PHP library that implements the <a href="http://en.wikipedia.org/wiki/Active_record_pattern">ActiveRecord pattern</a> we have all grown to love.  I&#8217;ve been using it for the past 7 months and feel it is one of the reasons we were able to deliver the MessageBoards web application on-time and within budget.</p>
<p>This doesn&#8217;t mean using Doctrine has been all flowers and sunshine: <strong>Doctrine will kick you in the face when you&#8217;re not paying attention.</strong></p>
<p>Today I&#8217;d like to revisit all of those black eyes and bloody noses in the hope of helping fellow developers avoid the same missteps I made.  Doctrine is complex and quirky, and has some unanticipated architectural &#8220;features&#8221; that are not well documented.</p>
<p><strong>Use Record::toArray() and Record::fromArray() to store/retrieve Doctrine objects from the session.</strong></p>
<ul>
<li>Save space in the session store by adding only the column attributes of Record objects to the session.</li>
<li>The session will quickly fill up otherwise, as Doctrine adds considerable bulk to model objects.</li>
</ul>
<p><strong>Improve performance by extending Doctrine_Table and implement custom DQL queries for complex and frequently used queries.</strong></p>
<ul>
<li>If the controller or view will need a record&#8217;s related record, use a DQL query to join with the related table.</li>
</ul>
<p><strong>Optimize performance by getting to know and love Doctrine_Connection_Profiler.</strong></p>
<ul>
<li>Add the connection listener at the beginning of execution and print SQL queries at the end of execution in order to identify areas of effective performance optimization.</li>
<li>Example code that adds the listener and renders query events as HTML:
<pre class="brush: php">

// Set the connection listener

$profiler = new Doctrine_Connection_Profiler();
Doctrine_Manager::connection()-&gt;setListener($profiler);

// Code goes here...

// Render database connection events as HTML:

$query_count = 0;
$time = 0;
echo &quot;&lt;table width=&#039;100%&#039; border=&#039;1&#039;&gt;&quot;;
foreach ( $profiler as $event ) {
    if ($event-&gt;getName() != &#039;execute&#039;) {
        continue;
    }
    $query_count++;
    echo &quot;&lt;tr&gt;&quot;;
    $time += $event-&gt;getElapsedSecs() ;
    echo &quot;&lt;td&gt;&quot; . $event-&gt;getName() . &quot;&lt;/td&gt;&lt;td&gt;&quot; . sprintf ( &quot;%f&quot; , $event-&gt;getElapsedSecs() ) . &quot;&lt;/td&gt;&quot;;
    echo &quot;&lt;td&gt;&quot; . $event-&gt;getQuery() . &quot;&lt;/td&gt;&quot; ;
    $params = $event-&gt;getParams() ;
    if ( ! empty ( $params ) ) {
          echo &quot;&lt;td&gt;&quot;;
          echo join(&#039;, &#039;, $params);
          echo &quot;&lt;/td&gt;&quot;;
    }
    echo &quot;&lt;/tr&gt;&quot;;
}
echo &quot;&lt;/table&gt;&quot;;
echo &quot;Total time: &quot; . $time . &quot;, query count: $query_count &lt;br&gt;\n &quot;;
</pre>
</li>
</ul>
<p><strong>Effectively mitigate performance issues with <a href="http://www.danga.com/memcached/">memcache</a>.</strong></p>
<ul>
<li>The query and result cache can drastically offset Doctrine&#8217;s performance overhead.</li>
<li>If you already have memcached running, this is one of the most cost-effective performance tweaks you can do.</li>
<li>Note: I received mysterious fatal errors when using INDEXBY in DQL queries.  After removing the INDEXBY, the errors stopped.</li>
</ul>
<p><strong>Play nice with fellow coders or testers by automating database migrations.</strong></p>
<ul>
<li>Add a <a href="http://git.or.cz/">git</a> merge hook that runs the Doctrine migration.</li>
<li>Alternatively, check if a migration is needed on every page view while in development mode.</li>
</ul>
<p><strong>Play nice with other web applications by prefixing database tables.</strong></p>
<ul>
<li>Set the table name prefix by calling $this->setTableName(&#8217;zzz_model_name&#8217;), where &#8216;zzz&#8217; is the tool&#8217;s prefix.</li>
</ul>
<p><strong>Create a &#8220;resource-oriented&#8221; URL structure that closely follows the application&#8217;s models.</strong></p>
<ul>
<li>This is borrowed from the <a href="http://oreilly.com/catalog/9780596529260/">RESTful</a> architecture, and not necessarily Doctrine-specific.</li>
<li>For example an HTTP GET on http://site.com/messageboard/m34/f11/ would display forum ID 11 in messageboard 34 in tool &#8220;messagebord&#8221;.</li>
</ul>
<p><strong>Use a workaround when using LEFT JOINs on models with actAs(&#8217;SoftDelete&#8217;) behavior.</strong></p>
<ul>
<li>SoftDelete will automatically add the WHERE condition &#8220;deleted = 0&#8243; to all queries.</li>
<li>This prevents queries with LEFT JOIN from returning a row where &#8220;deleted IS NULL&#8221;.</li>
<li>Either use INNER JOIN instead, or add the following to DQL queries: $query->andWhere(&#8217;deleted = 0 or deleted IS NULL&#8217;);</li>
</ul>
<p><strong>Timestampable cannot be disabled temporarily, causing challenges when importing data with dates.</strong></p>
<ul>
<li>Doctrine provides no way to easily disable or override the timestamp behavior in order to import a pre-existing date.</li>
<li>Until this behavior is resolved, try using <a href='http://trac.phpdoctrine.org/ticket/1590'>this patch</a> to set the &#8216;disabled&#8217; option of Timestampable.</li>
</ul>
<p><strong>Use &#8220;cascade => array(&#8217;delete&#8221;)&#8221; to propagate soft deletes through model relations.</strong></p>
<ul>
<li>The faster onDelete => &#8216;CASCADE&#8217; performs the delete in MySQL, which does not set the deleted flag.</li>
</ul>
<p><strong>Put authorization code in one place, when possible.</strong></p>
<p><strong>Implement checkbox plus text input as two columns in a model.</strong></p>
<ul>
<li>For example:<br />
<input type='checkbox' /> Require password:<br />
<input type='password' /></li>
<li>This approach simplifies validation of optional attributes.</li>
</ul>
<p><strong>Use $model->setAttribute(Doctrine::ATTR_COLL_KEY, &#8216;id&#8217;) to key collections off of the primary key.</strong></p>
<ul>
<li>If this attribute is not set, Collections will be indexed starting from 0 and counting upwards.</li>
<li>This can simplify controller logic.</li>
</ul>
<p><strong>Use Doctrine_Pager only for the most basic views.</strong></p>
<p><strong>Use actAs(&#8217;NestedSet&#8217;) to model hierarchies that are read more frequently than written.</strong></p>
<p><strong>Use unix timestamps in Timestampable columns to ease formatting.</strong></p>
<ul>
<li>Using a datetime works fine if the view never needs to change how a datetime is displayed.</li>
<li>A unix timestamp allows for flexibly changing how dates are rendered, e.g.: &#8220;Jan 1st 2008&#8243; or &#8220;Yesterday&#8221;.</li>
<li>Example actAs() code:
<pre class="brush: php">
$this-&gt;actAs(&#039;Timestampable&#039;, array(
    &#039;created&#039; =&gt; array(&#039;name&#039; =&gt; &#039;created_at&#039;,
        &#039;type&#039;    =&gt;  &#039;integer&#039;,
        &#039;format&#039;  =&gt;  &#039;U&#039;,
        &#039;disabled&#039; =&gt; false,
        &#039;options&#039; =&gt;  array()),
    &#039;updated&#039; =&gt; array(&#039;name&#039;    =&gt;  &#039;updated_at&#039;,
        &#039;type&#039;    =&gt;  &#039;integer&#039;,
        &#039;format&#039;  =&gt;  &#039;U&#039;,
        &#039;disabled&#039; =&gt; false,
        &#039;options&#039; =&gt;  array())));
</pre>
</li>
</ul>
<p><strong>For multi-step forms, add a &#8217;state&#8217; column to aid in validating each step.</strong></p>
<ul>
<li>In the model&#8217;s validate() function, use the state column to switch between validation logic.</li>
<li>For instance, in state 1, validate columns a and b.  In state 2, validate columns a, b, c, and d. In state 3, validate the complete object.</li>
</ul>
<p>I hope this list saves some heartache on what is really a very elegant ActiveRecord implementation in PHP!</p>
]]></content:encoded>
			<wfw:commentRss>http://maxgarrick.com/effective-development-using-doctrine-orm/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
