The Propel Blog2024-02-04T11:46:03+00:00http://www.propelorm.org/Propel2 Beta 2 Release2022-07-04T00:00:00+00:00http://www.propelorm.org/blog/2022/07/04/propel2-beta2-release<p>With this release, we continue version cleanups aiming API stabilization.</p>
<p>Many thanks to our contributors!</p>
<h2 id="200-beta2">2.0.0-beta2</h2>
<p>Here are the changes:</p>
<ul>
<li>PHP 8.1 and 8.2 compatibility</li>
<li>Added strict types to generated code</li>
<li>Added Symfony 6 support</li>
<li>Dropped Symfony 3 support. The minimum required Symfony version is 4.4.0 now.</li>
<li>Added Monolog 2 support</li>
<li>Added a type safe access to the <code class="language-plaintext highlighter-rouge">StandardServiceContainer</code> via <code class="language-plaintext highlighter-rouge">Propel::getStandardServiceContainer()</code> method</li>
<li>Added support for the <code class="language-plaintext highlighter-rouge">DATETIME</code> column type</li>
<li>Moved templates into own root level</li>
<li>Overall code quality improvements</li>
<li>Fixed <code class="language-plaintext highlighter-rouge">DatabaseComparator</code> in order to skip migration creation if table has <code class="language-plaintext highlighter-rouge">skipSql</code> flag</li>
<li>Fixed an issue with many-to-many mapping</li>
<li>Fixed usage of deprecated date format</li>
<li>Fixed column’s time format issue</li>
<li>Fixed issue with identifier quoting being ignored</li>
<li>Fixed a debug mode behavior in order to use new connection with it</li>
</ul>
<h3 id="bc-breaking-impact">BC breaking impact</h3>
<p>Please note that all methods have param and return types being added now where they were feasible and ensure better code quality.
Make sure any extensions are updated here regarding their method signature.
TIMESTAMP column type in schema files for the MySql databases now generates column with actual TIMESTAMP type instead of DATETIME as it was previously. Propel diff considers it as a table structure change and generates migration.
As another side effect timestamps are only valid until 2037 (32bit). Make sure to adjust any databuilders or fixtures accordingly.</p>
<h3 id="download">Download</h3>
<p>You can download this release as usual via Composer.
Please give it a try and <a href="https://github.com/propelorm/Propel2/issues/new">report any bugs</a>
you spot:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-beta2"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>All changes/commits: <a href="https://github.com/propelorm/Propel2/compare/2.0.0-beta1...2.0.0-beta2">github.com/propelorm/Propel2/compare/2.0.0-beta1…2.0.0-beta2</a></p>
<p>All releases: <a href="https://github.com/propelorm/Propel2/releases">github.com/propelorm/Propel2/releases</a></p>
Propel2 Beta 1 Release2021-12-07T00:00:00+00:00http://www.propelorm.org/blog/2021/12/07/propel2-beta1-release<p>With this release, we continue version cleanups aiming API stabilization.</p>
<p>Many thanks to our contributors!</p>
<h2 id="200-beta1">2.0.0-beta1</h2>
<p>Here are the changes:</p>
<ul>
<li>PHP 8.1 compatibility</li>
<li>Fixes for PHP 7.4 preloading</li>
<li>Fixed usage of default on-update and on-delete behavior</li>
<li>Show names of uncommitted migrations</li>
<li>BehaviorLocator now looks in dev packages, as well</li>
<li>Aggregate multiple columns behavior & parameter list support</li>
<li>Fixes around aliases and cross joins and subqueries</li>
<li>Added support for <code class="language-plaintext highlighter-rouge">keytype</code> in the magic import/export methods</li>
<li>PSR naming fixes for variables and methods</li>
<li>Reset partial flag when populating a relation</li>
<li>Added <code class="language-plaintext highlighter-rouge">exists</code> operator</li>
<li>Escape quotes in behavior</li>
<li>Quote primary table name if identifier quoting is enabled</li>
<li>Formats insert <code class="language-plaintext highlighter-rouge">DATE</code> values as <code class="language-plaintext highlighter-rouge">Y-m-d</code> instead of <code class="language-plaintext highlighter-rouge">Y-m-d H:i:s.u</code></li>
<li>Allow default-value for concrete-inheritance to be instantiable</li>
<li>Pluralize <code class="language-plaintext highlighter-rouge">Box</code> to <code class="language-plaintext highlighter-rouge">Boxes</code></li>
<li>Allow <code class="language-plaintext highlighter-rouge">NO ACTION</code> for foreign key references (in the dtd/xsd)</li>
<li>Use object-equality instead of reference-equality to compare object properties</li>
<li>Generates data dictionary documentation</li>
<li>PHPStan related code cleanup</li>
</ul>
<h3 id="bc-breaking-impact">BC breaking impact</h3>
<p>Please note that methods have param and return types being added now where they were feasible and ensure better code quality.
Make sure any extensions are updated here regarding their method signature.
Some internal methods were also renamed to fit PSR coding standards.</p>
<p>Due to the support of PHP 7.4 preloading, an update will need the configuration to be rebuilt once by calling <code class="language-plaintext highlighter-rouge">config:convert</code>, see https://github.com/propelorm/Propel2/wiki/Exception-Target:-Loading-the-database#for-imported-configuration</p>
<h3 id="download">Download</h3>
<p>You can download this release as usual via Composer.
Please give it a try and <a href="https://github.com/propelorm/Propel2/issues/new">report any bugs</a>
you spot:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-beta1"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>All changes/commits: <a href="https://github.com/propelorm/Propel2/compare/2.0.0-beta1...2.0.0-beta2">github.com/propelorm/Propel2/compare/2.0.0-alpha12…2.0.0-beta1</a></p>
<p>All releases: <a href="https://github.com/propelorm/Propel2/releases">github.com/propelorm/Propel2/releases</a></p>
Propel2 Alpha 12 Release2021-01-22T00:00:00+00:00http://www.propelorm.org/blog/2021/01/22/propel2-alpha-12-release<p>With this release, we continue Alpha-version cleanups aiming API stabilization.</p>
<p>Many thanks to our contributors!</p>
<h2 id="200-alpha12">2.0.0-alpha12</h2>
<p>Here are the changes:</p>
<ul>
<li>PHP 8 compatibility</li>
<li>Widening the range of Symfony v4 to 4.0+ (instead of 4.3+)</li>
<li>Fixed transaction handling when \Throwable is thrown</li>
<li>Fixed identifierQuoting for Versionable behavior</li>
<li>Fixed invalid hydration when using mergeWith of criteria with “with” models</li>
<li>Adds the ability for locking reads, either shared or exclusive</li>
<li>Updated TableMap generator to add column name map for normalization and performance speedup</li>
<li>Use temporal formatter in the toArray() generator, fixes the issue of entities wrongly being marked as dirty due to differences in the datetime formatting</li>
</ul>
<h3 id="bc-breaks">BC breaks</h3>
<p>Please note that due to PHP7 + PHP8 versions both able to be supported with this library, the PDO access had to be refactored in a not fully BC way. Instead of directly extending the PHP core classes, we now depend on interface contracts.</p>
<p>If your software has directly extended those in the past, please make sure to adjust your extensions accordingly.</p>
<p>PDOStatement => Propel\Runtime\Connection\StatementInterface
PdoConnection extends PDO implements ConnectionInterface => only implements the latter and proxies to PDO instead.</p>
<h3 id="download">Download</h3>
<p>You can download this release as usual via Composer. Please give it a try and <a href="https://github.com/propelorm/Propel2/issues/new">report any bugs</a>
you spot:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-alpha12"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>All changes/commits: <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha11...2.0.0-alpha12">github.com/propelorm/Propel2/compare/2.0.0-alpha11…2.0.0-alpha12</a></p>
<p>All releases: <a href="https://github.com/propelorm/Propel2/releases">github.com/propelorm/Propel2/releases</a></p>
Propel2 Alpha 11 Release2020-08-07T00:00:00+00:00http://www.propelorm.org/blog/2020/08/07/propel2-alpha-11-release<p>With this release, we continue Alpha-version cleanups aiming API stabilization.
Many thanks to our contributors, who made possible this release to come that fast. 👍</p>
<h2 id="200-alpha11">2.0.0-alpha11</h2>
<p>Here are the changes:</p>
<ul>
<li>Fixed return value for “no migration needed” case in MigrationMigrateCommand</li>
<li>Always create unique indices by constraint for Postgres (@daniel-rose)</li>
<li>Do not try to fetch related objects of a new object (@gharlan)</li>
<li>Map JSON type to native Postgres type (@tienbuide)</li>
<li>Fixed nullable docblock for mutator methods (@dereuromark)</li>
<li>PHP 7.2+ cleanups (class visibility modifiers, native types etc)</li>
<li>Dropped EOL Symfony 2, Postgres 9.4 from test matrix</li>
<li>Fixed docblocks and typehinting</li>
<li>PHPStan level 5 static analyzing</li>
<li>Yoda notation cleanup</li>
</ul>
<h3 id="bc-breaks">BC breaks</h3>
<ul>
<li>PHP7.1 is not supported anymore (EOL)</li>
<li>Symfony 2 is not supported anymore (EOL)</li>
</ul>
<h3 id="download">Download</h3>
<p>You can download this release as usual via Composer. Please give it a try and <a href="https://github.com/propelorm/Propel2/issues/new">report any bugs</a>
you spot:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-alpha11"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>All changes/commits: <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha10...2.0.0-alpha11">github.com/propelorm/Propel2/compare/2.0.0-alpha10…2.0.0-alpha11</a></p>
<p>All releases: <a href="https://github.com/propelorm/Propel2/releases">github.com/propelorm/Propel2/releases</a></p>
Propel2 Alpha 10 is released2020-07-13T00:00:00+00:00http://www.propelorm.org/blog/2020/07/13/propel2-alpha-10-is-released<p>Less than a month passed after the latest Alpha release, and we already happy to introduce a
new Alpha 10, which includes a full support of Symfony 5 components,
a first wave of PhpStan code quality fixes and a bunch of bugfixes.</p>
<p>You can consider this release as a good/safe option for dependent project migrations, as it contains the
longest dependency list, including EOL components (which will be reduced in the next releases).</p>
<p>See you soon!</p>
<!-- more -->
<h2 id="200-alpha10">2.0.0-alpha10</h2>
<p>Here are the changes:</p>
<ul>
<li>Full support with Symfony components 2.7+, 3.3+, 4.0+, 5.0+</li>
<li>Adds support for MYSQL_ATTR_SSL_VERIFY_SERVER_CERT configuration option</li>
<li>Fixed an issue where propel reverse breaks with system-versioned tables</li>
<li>Applies PHPStan Level 1 Fixes, which corrects many warnings, type checks, missing types, and incorrect annotations.</li>
</ul>
<h3 id="bc-breaks">BC breaks</h3>
<ul>
<li>We don’t expect any. Please create a new issue on guthub if you are facing any problems.</li>
</ul>
<h3 id="download">Download</h3>
<p>You can download this release as usual via Composer. Please give it a try and <a href="https://github.com/propelorm/Propel2/issues/new">report any bugs</a>
you spot:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-alpha10"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>All changes/commits: <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha9...2.0.0-alpha10">github.com/propelorm/Propel2/compare/2.0.0-alpha9…2.0.0-alpha10</a></p>
<p>All releases: <a href="https://github.com/propelorm/Propel2/releases">github.com/propelorm/Propel2/releases</a></p>
Propel2 Alpha 9 is released2020-06-25T00:00:00+00:00http://www.propelorm.org/blog/2020/06/25/propel2-alpha-9-is-released<p>We just released Propel2-alpha9 as a part of stabilization process aiming a stable release.
There is still interest in AR based ORM with rapid development features and therefore demand
on Propel2 development.</p>
<p>The strategy we consider sustainable is evolutionary process rather than revolutionary.
Therefore avoiding heavy BC-breaks, while still evolving and cleaning up internals with
better architecture and patterns. This release will be followed with a few more alpha
releases where we need to get rid of outdated, EOD dependencies and enhanced compatibility.</p>
<!-- more -->
<h2 id="200-alpha9">2.0.0-alpha9</h2>
<p>Here are the changes:</p>
<ul>
<li>
<p>Added compatibility PHP 7.4</p>
</li>
<li>
<p>Added support of PSQL expressions [CURRENT_TIMESTAMP, LOCALTIMESTAMP]</p>
</li>
<li>
<p>Allowed Symfony 5 dependency</p>
</li>
</ul>
<h3 id="bc-breaks">BC breaks</h3>
<ul>
<li>Removed support of PHP5, which is EOL of 1.2019.</li>
</ul>
<h3 id="download">Download</h3>
<p>You can download this release as usual via Composer. Please give it a try and <a href="https://github.com/propelorm/Propel2/issues/new">report any bugs</a>
you spot:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-alpha9"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>All changes/commits: <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha8...2.0.0-alpha9">github.com/propelorm/Propel2/compare/2.0.0-alpha8…2.0.0-alpha9</a></p>
<p>All releases: <a href="https://github.com/propelorm/Propel2/releases">github.com/propelorm/Propel2/releases</a></p>
Propel2 current state and its future2015-06-27T00:00:00+00:00http://www.propelorm.org/blog/2015/06/27/propel2-current-state-and-future<p>We released Propel2-alpha4 over a half a year ago and with it the last blog post.
As this is a long time for an open-source project so much used, I’m going now to
let you know what its current state is and what I planned for the future.</p>
<!-- more -->
<h2 id="200-alpha5">2.0.0-alpha5</h2>
<p>Since the last official release, we already have <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha4...master">many fixes and features committed</a>
which is now available under the <code class="language-plaintext highlighter-rouge">2.0.0-alpha5</code> tag. Here are the changes:</p>
<ul>
<li>
<p>Added <code class="language-plaintext highlighter-rouge">--fake</code> option for the commands <code class="language-plaintext highlighter-rouge">migration:up</code>, <code class="language-plaintext highlighter-rouge">migration:down</code> and <code class="language-plaintext highlighter-rouge">migration:migrate</code>. This is particulary useful if you update
to Propel 2 and need to flag all migrations in the database as “executed”. Since Propel 1 stored only the last executed migration in the database,
this is necessary to not execute migration files twice. Also if you executed a migration per hand and want to let Propel know this, you can now do
that using the <code class="language-plaintext highlighter-rouge">--fake</code> option.</p>
</li>
<li>
<p>Added <code class="language-plaintext highlighter-rouge">column</code> attribute <code class="language-plaintext highlighter-rouge">typeHint</code> and <code class="language-plaintext highlighter-rouge">foreign-key</code> attribute <code class="language-plaintext highlighter-rouge">interface</code>. The given value (an <code class="language-plaintext highlighter-rouge">array</code> or a PHP class/interface) is used as
<a href="http://php.net/manual/en/language.oop5.typehinting.php">PHP typeHint</a> for the generated method’s argument.
A good use case is for example <code class="language-plaintext highlighter-rouge"><column name="dummy_object" type="object" typeHint="Vendor\MyDummyInterface"/></code>.</p>
</li>
<li>
<p>Polymorphic relations are now supported. Long wished, now supported. You can use polymorphic relations to place a relation
to another entity <a href="https://github.com/propelorm/Propel2/blob/f20146a696ba5f9684fbc27d642e431286415b15/tests/Fixtures/bookstore/schema.xml#L392-L395">with additional conditions</a>.</p>
</li>
<li>
<p>Exclude tables using a new configuration entry, <code class="language-plaintext highlighter-rouge">exclude_tables</code>, under the root node. Thanks goes out to <a href="https://github.com/SCIF">Alexander Zhuravlev</a>.</p>
</li>
</ul>
<p>This is only a limited set of fixes and features. Please see <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha4...2.0.0-alpha5">the change log</a> to see all changes.</p>
<h3 id="bc-breaks">BC breaks</h3>
<ul>
<li>Migrations hard fail now per default. In previous versions, Propel just continued with the migration if one migration file failed.
This is now no longer the case. If you want to continue even after a failure use the new <code class="language-plaintext highlighter-rouge">--force</code> option. <code class="language-plaintext highlighter-rouge">--force</code> ignores all occurring errors.</li>
</ul>
<h3 id="download">Download</h3>
<p>You can download this release as usual via Composer. Please give it a try and <a href="https://github.com/propelorm/Propel2/issues/new">report any bugs</a>
you spot:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-alpha5"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>All changes/commits: <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha4...2.0.0-alpha5">github.com/propelorm/Propel2/compare/2.0.0-alpha4…2.0.0-alpha5</a></p>
<p>All releases: <a href="https://github.com/propelorm/Propel2/releases">github.com/propelorm/Propel2/releases</a></p>
<h2 id="current-state">Current state</h2>
<p>I got over the past months many emails and messages about questions like “Is Propel2 stable?”, “Do you advice using v2?” or “Can we use Propel2 already?”.
Please apologies if I haven’t found the time to answer all your emails. The short answer here is: it depends. The long is a bit more complicated.</p>
<p>Propel version 2 is currently in a state where it’s much more stable than every version before (including v1). Thanks to way more unit tests, covering now additional
SQLite and PostgreSQL in our test suite, using PSR 1/2/3/4, having great and better Symfony2 support, and with a lot of committed fixes this is without any doubt
the best Propel we ever had. I’m using it in a lot of projects (also very big projects having millions of page impressions, a lot of migrations and release tagging ongoing)
and it is very stable since months in every of my projects.</p>
<p>As awesome as this sounds, the bad news is: Do not use it if you haven’t the time to fight with BC breaks. Of course, you can already use it, but then you must know
that future bug-fixes may also introduce compatibility breaks, which may or may not lead you to some extra work.</p>
<p>You probably ask now, why am I not going to just release v2 as beta/stable and freeze its API? Well, because the code base of Propel 2 is pretty old and we have some showstopper, which
I definitely don’t want to support in the future. One of those showstoppers are:</p>
<ul>
<li>Enforced active-record and with it the tightly coupling of the database stuff and your domain model</li>
<li>A lot of static calls/globals</li>
<li>Very hard to maintain classes like <a href="https://github.com/propelorm/Propel2/blob/master/src/Propel/Generator/Builder/Om/ObjectBuilder.php">ObjectBuilder 6k LOC</a>
and <a href="https://github.com/propelorm/Propel2/blob/master/src/Propel/Generator/Builder/Om/QueryBuilder.php">QueryBuilder 2k LOC</a></li>
</ul>
<p>All of the points above are fixed with the overhaul of Propel in <a href="https://github.com/propelorm/Propel2/pull/795">PR #795</a>.
As you can imagine this brings a lot of compatibility breaks with it, and since I don’t want to release a version which is immediately after the release outdated and not maintained anymore,
I made the decision not to release anything beyond alpha before PR #795 is not finished. This is a tough decision as it will postpone Propel v2 release even more backward, but I’m sure
it’s worth the time.</p>
<h2 id="future">Future</h2>
<p>The current roadmap is visible in our GitHub wiki: <a href="https://github.com/propelorm/Propel2/wiki">github.com/propelorm/Propel2/wiki</a>.</p>
<p>You can see there two big points still opened, concentrated in one big pull-request: <a href="https://github.com/propelorm/Propel2/pull/795">PR #795</a>.</p>
<p>This is actually the big change Propel2 is waiting for. It dissolves our pain to maintain with the big classes,
introduces data-mapper/unit of work (which makes active-record optional), removes the need of having global/static data,
and adds more cool new features.</p>
<p>The result is that Propel can cover now a broader range of use cases and it can be better integrated in unit tests of modern applications.</p>
<p>What brings this PR now in detail?</p>
<ul>
<li>Separation of persisting logic and domain models. Using the data-mapper pattern and a unit-of-work give us the ability to have really
light models without base class anymore. They are pure POPOs (plain old PHP objects) without the need of having any magic.</li>
<li>Repositories. Active getters like relation getters (e.g. <code class="language-plaintext highlighter-rouge">$author->getBooks()</code>), behavior logic and your own business logic is no longer in the entity, but in the repository or
your own service using the built-in event-dispatcher.</li>
<li>Active-Record optional. <code class="language-plaintext highlighter-rouge"><entity name="Author" activeRecord="true"</code> activates the good old (good for prototyping) active-record using a generated PHP trait which is used in your model
and proxies active-record methods directly to the Session or Repository. Methods are <code class="language-plaintext highlighter-rouge">save</code>, <code class="language-plaintext highlighter-rouge">delete</code>, <code class="language-plaintext highlighter-rouge">isNew</code>, <code class="language-plaintext highlighter-rouge">isDeleted</code> and all active relation getters/counters method.</li>
<li>Opens the ability to support noSQL. Since we split the persisting logic and SQL related stuff into separated classes, it’s now easier to support noSQL databases.</li>
<li><a href="https://github.com/gossi/php-code-generator">OOP PHP code generator</a> and “Code Components” allowed us to split huge ObjectBuilder, QueryBuilder, EntityMapBuilder
et cetera into little very handy code classes. This was much work as we split those huge classes into over 100 components, but it was worth the work since their maintenance
is now very easy.</li>
</ul>
<h3 id="how-does-propel2-data-mapper-work">How does Propel2 data-mapper work?</h3>
<p>Well, if you activate active-record you shouldn’t notice many changes, since static creation of query classes or models behave the same using - as before - globals.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="k">include</span> <span class="s1">'path/to/propel-conf.php'</span><span class="p">;</span>
<span class="nv">$car</span> <span class="o">=</span> <span class="nc">CarQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span><span class="o">-></span><span class="nf">findByPk</span><span class="p">(</span><span class="mi">23</span><span class="p">);</span>
<span class="nv">$brand</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Brand</span><span class="p">(</span><span class="err">‘</span><span class="nc">Ford</span><span class="err">’</span><span class="p">);</span>
<span class="nv">$car</span><span class="o">-></span><span class="nf">setBrand</span><span class="p">(</span><span class="nv">$brand</span><span class="p">);</span>
<span class="nv">$car</span><span class="o">-></span><span class="nf">save</span><span class="p">();</span>
</code></pre></div></div>
<p>This is the current way of using Propel till alpha5.</p>
<p>The new data-mapper allows you to make the model lighter:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="nv">$configuration</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Propel\Runtime\Configuration</span><span class="p">(</span><span class="s1">'path/to/propel.yml'</span><span class="p">);</span>
<span class="nv">$session</span> <span class="o">=</span> <span class="nv">$configuration</span><span class="o">-></span><span class="nf">getSession</span><span class="p">();</span>
<span class="nv">$carRepository</span> <span class="o">=</span> <span class="nv">$configuration</span><span class="o">-></span><span class="nf">getRepository</span><span class="p">(</span><span class="err">‘\</span><span class="nc">Car</span><span class="err">’</span><span class="p">);</span> <span class="c1">// Provided usually per services/DI.</span>
<span class="nv">$car</span> <span class="o">=</span> <span class="nv">$carRepository</span><span class="o">-></span><span class="nf">find</span><span class="p">(</span><span class="mi">23</span><span class="p">);</span>
<span class="nv">$brand</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Brand</span><span class="p">(</span><span class="err">‘</span><span class="nc">Ford</span><span class="err">’</span><span class="p">);</span>
<span class="nv">$car</span><span class="o">-></span><span class="nf">setBrand</span><span class="p">(</span><span class="nv">$brand</span><span class="p">);</span>
<span class="nv">$session</span><span class="o">-></span><span class="nf">persist</span><span class="p">(</span><span class="nv">$brand</span><span class="p">);</span>
<span class="nv">$session</span><span class="o">-></span><span class="nf">persist</span><span class="p">(</span><span class="nv">$car</span><span class="p">);</span>
<span class="nv">$session</span><span class="o">-></span><span class="nf">commit</span><span class="p">();</span>
</code></pre></div></div>
<p>Since <code class="language-plaintext highlighter-rouge">persist</code> does not result in a database operation we can now save all changes in one batch (<code class="language-plaintext highlighter-rouge">commit()</code>). Our unit-of-work is
optimized to execute inserts in bulk inserts and updates in a more optimized way, which results basically in a
faster Propel for bigger change-sets.</p>
<h3 id="current-state-of-this-pull-request">Current state of this Pull-Request?</h3>
<p>Since I need to completely rewrite Propel’s object/query/tableMap builder, service container, persisting logic, all behaviors and
need to touch almost every test of our 3288 tests (By the way, almost more than 700 more tests than Propel v1) it took a while
to implement the rough basics. What has been done:</p>
<ul>
<li>Almost 10% of test suite is green (which means it’s compatible with the <a href="https://github.com/marcj/Propel2/blob/data-mapper/tests/Propel/Tests/BookstoreTest.php">old BookstoreTest</a>)</li>
<li>Rewrote <a href="https://github.com/marcj/Propel2/blob/data-mapper/src/Propel/Generator/Builder/Om/ObjectBuilder.php">big builder</a> into <a href="https://github.com/marcj/Propel2/tree/data-mapper/src/Propel/Generator/Builder/Om/Component">little pieces</a></li>
<li>MySQL/SQLite: Inserts/Updates/Deletes</li>
<li>Behaviors rewritten: AggregateField (a.k.a AggregateColumn), Archivable, AutoAddPk, ConcreteInheritance, Delegate, Timestampable, Sluggable</li>
</ul>
<p>What needs to be done:</p>
<ul>
<li>Rewrite behavior: NestedSet, I18n, Sortable, QueryCache, Validate, Versionable.</li>
<li>Make more tests green. A lot of tests are based on <code class="language-plaintext highlighter-rouge">Map\*TableMap::getTableMap()</code> which is not available anymore. <code class="language-plaintext highlighter-rouge">$configuration->getEntityMap(FooEntity::class)</code> is now the new way.</li>
<li>Make PostgreSQL support working.</li>
</ul>
<p>You can check-out my <a href="https://github.com/marcj/Propel2">current branch</a> if you want to play around with it, but take care: it’s far from being perfect, it’s slower than old versions and generated code may look ugly.</p>
<p>I’m planing now to write every ~two weeks a blog post about what has been done in that data-mapper development, so you’re up to date.
Please don’t judge badly about the slow development,
if it is not as fast as you want to see - the contribution to this new approach is rather low since it is very complicated to build a big
library like this and costs much time. But I really believe we will all be more than happy when we will finally have an alternative, full featured data-mapper ORM to Doctrine, which
is on top faster, easier to use and uses less magic.</p>
<p>Next plan is to implement the I18n and nested_set behavior, which both are pretty big. Hope to provide you more updates soon.</p>
Propel2 alpha4 is released2014-11-20T00:00:00+00:00http://www.propelorm.org/blog/2014/11/20/propel2-alpha4-released<p>We just released <a href="https://github.com/propelorm/Propel2/releases/tag/2.0.0-alpha4">Propel2 Alpha4</a> with lot of bug fixes
and some new major features.</p>
<!-- more -->
<h2 id="whats-new-in-this-release">What’s new in this release</h2>
<h3 id="configuration-system">Configuration System</h3>
<p><a href="https://github.com/cristianoc72">Cristiano Cinotti</a> did a fantastic job in integrating the Symfony configuration system
into Propel. The <code class="language-plaintext highlighter-rouge">build.properties</code> file is the past, you can now define Propel’s config as yml, php, ini and more.
You can find more information in the <a href="http://propelorm.org/documentation/10-configuration.html">Configuring Propel</a> documentation.</p>
<h3 id="propelbundle">PropelBundle</h3>
<p><a href="https://github.com/K-Phoen">Kévin Gomez</a> did also a incredible job in updating the PropelBundle so you can use
it with Propel2. You can find more information about the integration of Propel2 in Symfony in the
<a href="http://propelorm.org/documentation/cookbook/symfony2/index.html">Working With Symfony2</a> documentation.
It’s available also in composer via <code class="language-plaintext highlighter-rouge">"propel/propel-bundle": "2.0.0-alpha4"</code>.</p>
<h3 id="init-command">Init Command</h3>
<p><a href="https://github.com/mpscholten">Marc Scholten</a> gave us a brilliant <a href="https://github.com/propelorm/Propel2/pull/693">new <code class="language-plaintext highlighter-rouge">init</code> command</a>
to bootstrap your Propel project now even faster.</p>
<h3 id="bug-fixes">Bug fixes</h3>
<p>And as always we fixed a lot of bugs with this release. Thanks to <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha3...2.0.0-alpha4">all contributors</a>!</p>
<h2 id="roadmap">Roadmap</h2>
<p>We said in our latest alpha3 release blog post that the next version will be a beta. Unfortunately, we have to correct this.
We’re working hard to get things going, but we have still <a href="https://github.com/propelorm/Propel2/issues">many open issues</a>
that need some investigations first before we are able to provide you a beta. In our <a href="https://github.com/propelorm/Propel2/wiki">roadmap</a>
you’ll find also some new big features we’re planning. At the moment we can not provide you any date a beta is realistic,
although we know it’s highly requested. If you find the time to investigate some issues or even provide some fixes,
then please don’t hesitate - any contribution is highly appreciated by the whole propel community!</p>
<h2 id="download">Download</h2>
<p>You can download this release as usual via composer. Please give it a try and <a href="https://github.com/propelorm/Propel2/issues/new">report any bugs</a>
you spot:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-alpha4"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>All changes/commits: <a href="https://github.com/propelorm/Propel2/compare/2.0.0-alpha3...2.0.0-alpha4">github.com/propelorm/Propel2/compare/2.0.0-alpha3…2.0.0-alpha4</a></p>
<p>All releases: <a href="https://github.com/propelorm/Propel2/releases">github.com/propelorm/Propel2/releases</a></p>
<p>Thank you guys!</p>
Marc Scholten joined the Team2014-10-07T00:00:00+00:00http://www.propelorm.org/blog/2014/10/07/marc-scholten-joined-the-team<p><a href="https://github.com/mpscholten">Marc Scholten</a> (<a href="http://www.twitter.com/_marcscholten">@_marcscholten</a>) is now a Propel core developer.</p>
<p>You may know him already from the new fabulous Propel2 <code class="language-plaintext highlighter-rouge">init</code> command from <a href="https://github.com/propelorm/Propel2/pull/693">PR #693</a>. He continues
his great work with improving Propel from now on as a official member.</p>
<p>Please welcome Marc with me! You’ll find more information about him at our <a href="http://propelorm.org/about-us.html">about-us page</a>.</p>
Propel Has A New Leader2014-04-23T00:00:00+00:00http://www.propelorm.org/blog/2014/04/23/propel-has-a-new-leader<p>Three years ago, I became the lead developer of the Propel ORM project, a decision that probably changed
my life, and, for sure, literally propelled me out of my comfortable zone. This was not easy. I was young,
inexperienced, and a sort of “outsider”.</p>
<!-- more -->
<p>While Propel was a “one-man project” thanks to the awesome <a href="https://twitter.com/francoisz">François</a>, I decided
not to follow such path. I rather created an <a href="https://github.com/propelorm">organization</a>, gathering <a href="https://github.com/orgs/propelorm/members">skilled
people</a>, and new projects, with a long-term vision in mind. That
is actually what I like the most in Open Source, enrolling new people to this wonderful world, and giving them
responsibilities or, at least, opportunities to become Open Source developers.</p>
<p>In three years, Propel grew up a lot: a brand new upcoming version, more documentation, more tests, more components
to integrate with various frameworks, but also more standard tools and best practices. The whole project moved to
Git and GitHub, I simplified the way releases were built, as well as, how one could contribute to the project (code
but also documentation and, generally speaking, everything related to the project at large). In my opinion, this is
my main contribution to the project, and my only regret is not to ship <a href="https://github.com/propelorm/Propel2">Propel2</a>
in a stable version myself.</p>
<p>But now, after three years, it’s time to step down. Having me as project leader is what prevents Propel from
developing even faster, and for two simple reasons: I don’t have enough time to dedicate to it, and I don’t do any
web programming anymore. I took over the project three years ago because I did not want to see it die, not because
I wanted to lead an Open Source project. Today, Propel is still alive, and I like to think that the long-term vision
I chose to follow three years ago is not completely unrelated.</p>
<p>I am sincerely glad to welcome <strong>Marc J. Schmidt</strong> as <strong>new lead developer of Propel</strong>. He is
<a href="https://twitter.com/MarcJSchmidt">@MarcJSchmidt</a> on Twitter, and <a href="https://github.com/marcj">@marcj</a> on GitHub.
If you follow the Propel2 development, I am sure you know him already! He is German, and an amazing developer. His
understanding of the Propel design, of the Open Source philosophy, and of the ORM landscape, would be enough to let
him lead the project. Note that I won’t abandon the project right away. I will be right behind his shoulder watching
him for a while.</p>
<p>I am very happy for Propel. I’m also very happy for me, this will, hopefully, give me some time for other activities.
And most of all, I’m very happy for Marc, who deserves a warm welcome from you guys! :heart:</p>
Propel2 alpha3 is Released2014-04-16T00:00:00+00:00http://www.propelorm.org/blog/2014/04/16/propel2-alpha3-released<p>Propel2 got more love in the last weeks which lead to a new <a href="https://github.com/propelorm/Propel2/releases/tag/2.0.0-alpha3">alpha3 release</a>.
<a href="https://github.com/marcj">Marc</a> and a few other <a href="https://github.com/propelorm/Propel2/graphs/contributors">great people</a> are doing a great job with Propel2.</p>
<!-- more -->
<p>A few more devs prepare even more Pull Requests to polish Propel2.
The next release target will be a first beta, let’s see how things are going on.</p>
<p>Please give it a try and report any bug:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-alpha3"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Thank you guys!</p>
Propel 1.7.1 is Released2014-02-25T00:00:00+00:00http://www.propelorm.org/blog/2014/02/25/propel-1-7-1-is-released<p>Hi!</p>
<p>Propel 1.7.1 is released! You will find more details here:
<a href="https://github.com/propelorm/Propel/releases/tag/1.7.1">https://github.com/propelorm/Propel/releases/tag/1.7.1</a>.</p>
<p>Thank you!</p>
Propel 1.7.0 is Released2013-10-21T00:00:00+00:00http://www.propelorm.org/blog/2013/10/21/propel-1-7-0-is-released<p>Hi!</p>
<p>Propel 1.7.0 is released! It is the first release of the <strong>1.7</strong> version. You will find more details here:
<a href="https://github.com/propelorm/Propel/releases/tag/1.7.0">https://github.com/propelorm/Propel/releases/tag/1.7.0</a>.</p>
<p>Thank you!</p>
Propel2 "alpha1" Is Released!2013-06-05T00:00:00+00:00http://www.propelorm.org/blog/2013/06/05/propel2-alpha1-is-released<p><img src="https://a248.e.akamai.net/assets.github.com/images/icons/emoji/fireworks.png" alt="" /></p>
<p>A couple weeks ago, we announced that <a href="/blog/2013/05/13/propel2-is-about-to-be-released.html">Propel was about to be
released</a>,
and today is a special day for the Propel community. We are glad to inform you
that <strong>Propel2 alpha1</strong> is now <strong>available</strong>!</p>
<!-- more -->
<p>You will find the code for this first <code class="language-plaintext highlighter-rouge">alpha</code> version here on GitHub:
<a href="https://github.com/propelorm/Propel2/tree/2.0.0-alpha1">2.0.0-alpha1</a>.
The <a href="http://getcomposer.org">Composer</a> package is <code class="language-plaintext highlighter-rouge">propel/propel</code>, and the first
version is already available through
<a href="https://packagist.org/packages/propel/propel">Packagist</a>.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"propel/propel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0-alpha1"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>The <a href="https://github.com/propelorm/Propel2/pull/316">documentation</a> is not
entirely up to date, and we decided to postpone this issue for next release,
scheduled for the first week of July (next month).</p>
<p>Talking about the next releases, the
<a href="https://github.com/propelorm/Propel2/issues?milestone=3&state=open">alpha2</a>
version is our next milestone. We plan to release a few <em>alpha</em> versions, as
soon as possible (“release early, release often” they said).</p>
<p>Note that the <a href="http://github.com/propelorm/PropelBundle">PropelBundle</a> does not
support Propel2 yet.</p>
<p>Thank you to all contributors, and now, let’s celebrate!</p>
Propel2 Is About To Be Released2013-05-13T00:00:00+00:00http://www.propelorm.org/blog/2013/05/13/propel2-is-about-to-be-released<p>One year and a half ago, <a href="/blog/2011/10/06/propel2-has-begun-.html">Propel2 began</a>.
We decided to refactor the whole Propel code base in order to remove BC hacks, and
introduce new features. We, the community and the Propel core developers, wrote
a <strong>roadmap</strong> with all key points:</p>
<!-- more -->
<ul>
<li>Removing all <code class="language-plaintext highlighter-rouge">require()</code> and adding namespaces, that means the directory
structure will be modified to follow the <strong>PSR-0</strong> specification;</li>
<li>Adding an <strong>autoloader</strong> component, probably the Symfony2 ClassLoader
Component. Note: Composer has been used;</li>
<li>Fixing CS;</li>
<li>Fixing naming;</li>
<li><strong>Removing Phing</strong> and related stuffs;</li>
<li><strong>Removing</strong> late-static-binding <strong>hacks</strong>;</li>
<li>Adding a new component to handle the <strong>console logic</strong>: Symfony2 Console
Component is suitable for that part
(<a href="https://github.com/propelorm/Propel2/pull/100">#100</a>);</li>
<li><strong>Introducing a commons logic</strong> (useful for shared information between the
Platform (buildtime) and Adapters (runtime));</li>
<li><strong>Refactoring Adapters</strong> to be more generic (Proxy connection or something
else) (<a href="https://github.com/propelorm/Propel2/pull/33">#33</a>,
<a href="https://github.com/propelorm/Propel2/pull/39">#39</a>,
<a href="https://github.com/propelorm/Propel2/pull/47">#47</a>);</li>
<li>Adding new named exceptions
(<a href="https://github.com/propelorm/Propel2/pull/90">#90</a>);</li>
<li>Adding a new (or real) logging part: probably <strong>Monolog</strong>
(<a href="https://github.com/propelorm/Propel2/pull/101">#101</a>);</li>
<li><strong>Removing PEER classes</strong>
(<a href="https://github.com/propelorm/Propel2/pull/359">#359</a> and a lot of commits);</li>
<li>Moved <code class="language-plaintext highlighter-rouge">Base*</code> classes to a better location, with a better name
(<code class="language-plaintext highlighter-rouge">Om/BaseBook.php</code> => <code class="language-plaintext highlighter-rouge">Base/Book.php</code> for instance)
(<a href="https://github.com/propelorm/Propel2/pull/175">#175</a>);</li>
<li>Removing the old validation system, and use a behavior to <strong>integrate the
Symfony2 Validator component</strong>
(<a href="https://github.com/propelorm/Propel2/pull/96">#96</a>,
<a href="https://github.com/propelorm/Propel2/pull/156">#156</a>,
<a href="https://github.com/propelorm/Propel2/pull/227">#227</a>).</li>
</ul>
<p>That was the plan I announced at <a href="/blog/2012/07/09/propel2-what-why-when.html">Symfony Live
2012</a>. In the meantime, we changed
our mind, and decided to <a href="/blog/2012/08/08/propel2-and-php-5-4-here-we-go-.html">embrace <strong>PHP
5.4</strong></a>.
But we actually did a lot more, closing <a href="https://github.com/propelorm/Propel2/issues?milestone=2&page=1&state=closed">more than 300
issues</a>.</p>
<p>First of all, the <strong>Connection part</strong> has <a href="https://github.com/propelorm/Propel2/pull/39">been
rewritten</a>, introducing a <a href="https://github.com/propelorm/Propel2/pull/83">new <strong>Profiler
logic</strong></a>, allowing not only
<em>PDO</em> adapters, and adding new abstraction layers such as a
<a href="https://github.com/propelorm/Propel2/blob/master/src/Propel/Runtime/DataFetcher/DataFetcherInterface.php"><code class="language-plaintext highlighter-rouge">DataFetcher</code></a>.</p>
<p>Then, we wrote more <a href="https://github.com/propelorm/Propel2/pull/223">unit
tests</a> to make Propel more
stable. We also adopted the <a href="https://github.com/propelorm/Propel2/pull/295">Symfony2 Filesystem
component</a>, and contributed to
the Symfony2 framework by creating this standalone component. Propel2 relies on
<strong>five Symfony2 components</strong>:
<a href="http://symfony.com/doc/current/components/yaml/introduction.html">Yaml</a>,
<a href="http://symfony.com/doc/current/components/console/introduction.html">Console</a>,
<a href="http://symfony.com/doc/current/components/finder.html">Finder</a>, Validator, and
<a href="http://symfony.com/doc/current/components/filesystem.html">Filesystem</a>.</p>
<p>We introduced some
<a href="https://github.com/propelorm/Propel2/commit/0a96ef65e3282e8036f3e896a3da12645eb215bf"><strong>traits</strong></a>
as well as a <a href="https://github.com/propelorm/Propel2/commit/87f343190ec3a70174bce5608e9724696e2870b9"><strong>Service
Container</strong></a>.
However, that one is not configurable yet, see it as a compiled service
container. It was our first step to decouple the Propel code base.</p>
<p>We also took care of all patches applied to Propel 1.6, and ported them to
Propel2.</p>
<p>Last but not least, Propel2 is <strong>PSR-0</strong>, <strong>PSR-1</strong>, <strong>PSR-2</strong>, and
<a href="https://github.com/propelorm/Propel2/commit/24b0e35c2fcf8ce7885e42857577c40e63afafbe"><strong>PSR-3</strong></a> compliant.</p>
<p>We still have a few things to ship before a first <strong>alpha release</strong> like a
<a href="https://github.com/propelorm/Propel2/issues/368">Transaction API</a>, a <a href="https://github.com/propelorm/Propel2/issues/208">new
Pager</a>, and <a href="https://github.com/propelorm/Propel2/issues?milestone=2&state=open">some other things to clean
up</a>. This
first release is scheduled for the <strong>1st of June</strong>. Then, we will ship a
<a href="https://github.com/propelorm/Propel2/issues?milestone=3"><strong>beta</strong> version</a>,
probably in two months. Depending on users feedback, we will be able to release
a first <strong>stable version</strong> in September.</p>
<p>By now, our most important work in progress is <a href="https://github.com/propelorm/Propel2/issues/187">the
documentation</a> we want to
reorganize. By the way, <a href="https://github.com/robin850">Robin Dupret</a> is our new
documentation lead for Propel2.</p>
<p>I could not imagine how complicated it was to refactor such a project, we did a
lot but we could be even better, especially now that we have a cleaner code base.
I would like to apologize for the delay, it took more time than I thought. Rome
wasn’t built in a day, they said.</p>
<p>My final thoughts go out to <a href="https://github.com/propelorm/Propel2/contributors">all Propel
contributors</a>, you are really
awesome, thank you!</p>
Markus Staab Joined the Team2013-02-20T00:00:00+00:00http://www.propelorm.org/blog/2013/02/20/markus-staab-joined-the-team<p><a href="https://github.com/staabm">Markus Staab</a> (<a href="http://www.twitter.com/markusstaab">@markusstaab</a>) is now a Propel core developer.
He will continue his great work on Propel 1.6, and Propel2.</p>
<p>Welcome on board Markus!</p>
Marc J. Schmidt Joined the Team2013-02-18T00:00:00+00:00http://www.propelorm.org/blog/2013/02/18/marc-j-schmidt-joined-the-team<p>As announced on Twitter, <a href="https://github.com/MArcJ">Marc J. Schmidt</a> is now a Propel core developer. He will continue his great work on Propel 1.6, and Propel2.</p>
<p>Welcome on board Marc!</p>
Propel 1.6.8 is Released2013-02-14T00:00:00+00:00http://www.propelorm.org/blog/2013/02/14/propel-1-6-8-is-released<p><strong>Propel 1.6.8</strong> is now available, and it's <strong>the fifth bug fixes only release</strong>.</p>
<p>The release is available on <a href="https://github.com/propelorm/Propel" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">GitHub</a> under the <a href="https://github.com/propelorm/Propel/tree/1.6.8" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">1.6.8 tag</a> on GitHub, as PEAR package (<a href="http://pear.propelorm.org/index.php?package=propel_runtime&release=1.6.8&downloads" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">runtime</a> & <a href="http://pear.propelorm.org/index.php?package=propel_generator&release=1.6.8&downloads" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">generator</a>), as archives (<a href="https://github.com/propelorm/Propel/zipball/1.6.8" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">ZIP</a> and <a href="https://github.com/propelorm/Propel/tarball/1.6.8" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">TAR</a>), and also available through <strong>Composer</strong>: <a href="http://packagist.org/packages/propel/propel1" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">propel/propel1</a>.</p>
<p>As usual, the API documentation is at: <a href="http://api.propelorm.org/" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">http://api.propelorm.org/</a>, and here is the huge <a href="https://raw.github.com/propelorm/Propel/1.6.8/CHANGELOG" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: initial;">CHANGELOG</a>. Thanks to all contributors.</p>
<p> </p>
Propel2, Week #22013-01-15T00:00:00+00:00http://www.propelorm.org/blog/2013/01/15/propel2-week-2<p>Last week, <a href="http://github.com/propelorm/Propel2">Propel2</a> got some love! <a href="https://github.com/propelorm/Propel2/commit/24b0e35c2fcf8ce7885e42857577c40e63afafbe">Propel2 adopted PSR-3</a>, the one that defines a <em><a href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md">Logger Interface</a>. </em>Also, we started to work on PEER classes thanks to <a href="https://twitter.com/pixeljer">Jérémie</a>. It's <strong>the last thing to do before an alpha release</strong>. It's quite complicated, but once it will be done, we will be able to decouple more things like the behaviours. Stay tuned!</p>
Happy New Year!2013-01-08T00:00:00+00:00http://www.propelorm.org/blog/2013/01/08/happy-new-year-<p>We wish you a happy new year and all the best for 2013!</p>
<p>Quick news:</p>
<ul>
<li><a href="https://github.com/propelorm/Propel">Propel</a> 1.6.8 will be released soon, and includes a lot of fixes and some new light-weight features. As you may noticed, the number of new versions is decreasing in order to freeze all new developments on this project.</li>
<li><a href="https://github.com/propelorm/Propel2">Propel2</a> is not under heavy development right now, but we are close to the end, and we have new plans to make it awesome!</li>
<li>The <a href="https://github.com/propelorm/sfPropelORMPlugin">sfPropelORMPlugin</a> is not actively maintained, mostly due to the symfony 1.x end of life.</li>
<li>The Symfony2 <a href="https://github.com/propelorm/PropelBundle">PropelBundle</a> is on its way, and everything seems ok.</li>
<li>We are discussing the need for a <a href="http://framework.zend.com/">Zend Framework 2</a> Module to integrate Propel in ZF. No decision has been made though. If you want to follow this discussion, subscribe to <a href="http://propelorm.org/support.html">the Propel Mailing-List</a>.</li>
</ul>
www.propelorm.org is down2012-11-11T00:00:00+00:00http://www.propelorm.org/blog/2012/11/11/www-propelorm-org-is-down<p>The Propel documentation available at <a href="http://www.propelorm.org">www.propelorm.org</a> is down for some obscure reasons. We are working with GitHub to find a solution and to fix this major issue.</p>
<p>In the meanwhile, the <strong>documentation is available at</strong>: <a href="http://jaugustin.github.com/">http://jaugustin.github.com/</a>. This mirror is up to date.</p>
The Behavior Tour: Meet The Geocodable Behavior2012-08-13T00:00:00+00:00http://www.propelorm.org/blog/2012/08/13/the-behavior-tour-meet-the-geocodable-behavior<p><em>Propel comes with a <a href="http://www.propelorm.org/documentation/#behaviors_reference">large set of behaviors</a> but most of them are already well-known. However, we noticed that a lot of people didn't know about the awesome user contributions we have in the Propel community. </em><em>In this series, we will introduce a new user contributed Propel behavior each week.</em></p>
<p>The <a href="https://github.com/willdurand/GeocodableBehavior">GeocodableBehavior</a> helps you build geo-aware applications by providing geocoding features to your ActiveRecord objects. It's also a wrapper for the powerful <a href="https://github.com/willdurand/Geocoder">Geocoder</a> library designed to solve geocoding problematics in PHP.<!--more--></p>
<p>The GeocodableBehavior provides new methods to both the <strong>ActiveRecord</strong> and the <strong>ActiveQuery</strong> APIs, and it works fine without Geocoder. Basically, adding this behavior to your XML schema will:</p>
<ul>
<li>add <strong>two new columns</strong> to your model: <code>latitude</code> and <code>longitude;</code></li>
<li>add <strong>four new methods to the ActiveRecord API</strong>: <code>getDistanceTo()</code>, <code>isGeocoded()</code>, <code>getCoordinates()</code>, and <code>setCoordinates()</code>;</li>
<li>add <strong>two new methods to the ActiveQuery API</strong>: <code>filterByDistanceFrom()</code>, <code>filterNear()</code>.</li>
</ul>
<p>The <code>getDistanceTo()</code> method returns the distance between the current object and a given one. You can specify the measure unit: <code>KILOMETERS_UNIT</code>,<code>MILES_UNIT</code>, or <code>NAUTICAL_MILES_UNIT</code>.</p>
<p>The <code>filterByDistanceFrom()</code> method adds a filter by distance on your current query and returns itself for fluid interface. This method is useful when you want to get results around a given position. The <code>filterNear()</code> is pretty much the same but takes a geocoded object instead of a position.</p>
<p><script src="https://gist.github.com/9a865c8f23348982fea8.js"></script></p>
<p>Then again, you get these methods for free, you just have to install the behavior and that's it. But this behavior is also a wrapper for the Geocoder library, so it does a lot more like <strong>automatic geocoding</strong>. There are two strategies: <strong>IP-address based</strong>, or <strong>Street-address based</strong>. The behavior guesses useful columns in your table in order to perform geocoding requests. Basically, the behavior looks for attributes called street, locality, region, postal code/zip code, and country. It tries to make a complete address with them. As usual, you can add your own list of attributes that represents a complete street address thanks to the <code>address_columns</code> parameter.</p>
<p>By using Geocoder and the automatic geocoding feature, a new method will be available in the ActiveRecord API: <code>geocode()</code>. This method will be triggered on save or update, but you can disable the autofill on update thanks to the <code>auto_update</code> parameter. For more information, read <a href="https://github.com/willdurand/GeocodableBehavior#automatic-geocoding">the documentation about automatic geocoding</a> and <a href="http://williamdurand.fr/2012/05/31/geocoder-the-missing-php5-library/">this blog post about Geocoder</a>.</p>
<p>This behavior is highly configurable and here is the reference configuration:</p>
<p><script src="https://gist.github.com/500a1c6bd6fb3875145d.js"></script></p>
<p>As you can see, you can configure Geocoder to fit your needs without any effort. This behavior is really flexible, and works with Propel 1.6.4 or higher. It is fully unit tested, and hosted on GitHub: <a href="https://github.com/willdurand/GeocodableBehavior">willdurand/GeocodableBehavior</a>. The Geocoder library is also hosted on GitHub: <a href="https://github.com/willdurand/Geocoder">willdurand/Geocoder</a>.</p>
<ul>
</ul>
Propel2 and PHP 5.4, Here We Go!2012-08-08T00:00:00+00:00http://www.propelorm.org/blog/2012/08/08/propel2-and-php-5-4-here-we-go-<p>A few months ago, I gave a talk about <a href="https://github.com/propelorm/Propel2">Propel2</a> at Symfony Live 2012 where I announced a first release scheduled for this summer. We are almost ready, and we've refactored more code than expected, so Propel2 is getting better each day. We could actually release a version at the end of this month.</p>
<p>We already have early adopters on Propel2, even if I recommend to wait the first release. They gave us very positive feedback but they still miss one of the best Propel2 features: the <strong>traits</strong>. Actually, everyone misses it, and here we learnt a very interesting fact: people wants to use PHP 5.4 new features.<!--more--></p>
<p>Following <a href="https://groups.google.com/d/topic/propel-development/2NfeMCJJQ5U/discussion">the discussion on the propel-development Mailing-List</a>, we are glad to announce a major change for Propel2. Yes, <strong>Propel2 will require PHP 5.4, and will generate PHP 5.4 code</strong>. Both the core and the generated code will use <a href="http://php.net/manual/en/migration54.new-features.php">PHP 5.4 killer features</a>.</p>
<p>Thing is, <strong>when will we be able to release Propel2 then?</strong> I'm quite confident here. Propel 1.6 works fine, and as I use to say, we don't need to release something early to replace Propel 1.6. Propel2 is a new project, based on Propel 1.6 and we plan <strong>a release before the end of this year</strong>. We will spend this time to refactor the core with PHP 5.4 features, so that there won't be any BC breaks after a first release. Propel 1.6 works with PHP 5.2.4 as well as with PHP 5.4, we want to do the same with Propel2, and that's also why we decided to use PHP 5.4 as the minimum requirement.</p>
The Behavior Tour: Meet The Equal Nest Behavior2012-08-06T00:00:00+00:00http://www.propelorm.org/blog/2012/08/06/the-behavior-tour-meet-the-equal-nest-behavior<p><em>Propel comes with a <a href="http://www.propelorm.org/documentation/#behaviors_reference">large set of behaviors</a> but most of them are already well-known. However, we noticed that a lot of people didn't know about the awesome user contributions we have in the Propel community. </em><em>In this series, we will introduce a new user contributed Propel behavior each week.</em></p>
<p><a href="https://github.com/CraftyShadow">Ivan Tanev</a> wrote a wonderful behavior named <a href="https://github.com/CraftyShadow/EqualNestBehavior">EqualNestBehavior</a> to solve a common problem: <strong>relations between instances of a same class</strong>. The best example is probably a friendship relation between users/persons in your application. In a social networking world, it's a very common feature.<!--more--></p>
<p>The behavior provides all the methods you need to manage those relations: <code>addFriend()</code>, <code>removeFriend()</code>, <code>getFriends()</code>, <code>setFriends()</code>, <code>hasFriend()</code>. See <a href="https://github.com/CraftyShadow/EqualNestBehavior#activerecord-api">the complete list of ActiveRecord's methods</a> for more details.</p>
<p><script src="https://gist.github.com/fd31fb9a18cf21f5f519.js"></script></p>
<p>As usual, the <a href="https://github.com/CraftyShadow/EqualNestBehavior#activequery-api">ActiveQuery API</a> has useful methods, too: you can either find or count friends for a given object.</p>
<p>Under the hood, this behavior defines a new table to store relations. It's like a Many to Many relationship, but with the same table referenced twice. Using this behavior is super easy, just add it to your XML schema as following:</p>
<p><script src="https://gist.github.com/ea38fb87e8f7b7cea8dc.js"></script></p>
<p>The table name, <code>friend</code>, is important. It defines the relationship name, and everything is built around this name.</p>
<p>This behavior is really flexible, and works with Propel 1.6 all versions. It is fully unit tested, and hosted on GitHub: <a href="https://github.com/CraftyShadow/EqualNestBehavior">CraftyShadow/EqualNestBehavior</a>. Thanks for this great behavior Ivan!</p>
Propel 1.6.7 is Released2012-07-30T00:00:00+00:00http://www.propelorm.org/blog/2012/07/30/propel-1-6-7-is-released<p><strong>Propel 1.6.7</strong> is now available, and it's <strong>the fourth bug fixes only release</strong>.</p>
<p>The release is available on <a href="https://github.com/propelorm/Propel">GitHub</a> under the <a href="https://github.com/propelorm/Propel/tree/1.6.7" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">1.6.7 tag</a> on GitHub, as PEAR package (<a href="http://pear.propelorm.org/index.php?package=propel_runtime&release=1.6.7&downloads" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">runtime</a> & <a href="http://pear.propelorm.org/index.php?package=propel_generator&release=1.6.7&downloads" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">generator</a>), as archives (<a href="https://github.com/propelorm/Propel/zipball/1.6.7" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">ZIP</a> and <a href="https://github.com/propelorm/Propel/tarball/1.6.7" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">TAR</a>), and also available through <strong>Composer</strong>: <a href="http://packagist.org/packages/propel/propel1" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">propel/propel1</a>.</p>
<p>As usual, the API documentation is at: <a href="http://api.propelorm.org/" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">http://api.propelorm.org/</a>, and here is the <a href="https://raw.github.com/propelorm/Propel/1.6.7/CHANGELOG" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">CHANGELOG</a>. Thanks to all contributors.</p>
<p> </p>
Don't Do This At Home #6: Repeat The Same Filter For All Queries2012-07-30T00:00:00+00:00http://www.propelorm.org/blog/2012/07/30/don-t-do-this-at-home-6-repeat-the-same-filter-for-all-queries<p>Don't Do This At Home #6: Repeat The Same Filter For All Queries</p>
<p>Can you spot the problem in the following snippet?</p>
<p><script src="https://gist.github.com/db709f0e56f69e9dcf86.js"></script></p>
<p><!--more-->The <code>published()</code> filter appears in all other filters. Ok, that was easy, it's the title of the post! But how to do it better?</p>
<p>Why not create a specialized query class filtering on published artivcles by default? All the frontend controllers should use this <code>FrontendArticleQuery</code> instead of the more generic <code>ArticleQuery</code>. It would look like this:</p>
<p><script src="https://gist.github.com/2cb0d022d1aab2c1c2b5.js"></script></p>
<p>This method can add more default filters (for instance, to restrict to articles linked to an author, articles arleady indexed by a search engine, etc.) Now, everytime you call <code>FrontendArticleQuery::create()</code>, you know you only retreive published articles. No need to add this filter to other filters anymore.</p>
Propel2: What, Why, When?2012-07-09T00:00:00+00:00http://www.propelorm.org/blog/2012/07/09/propel2-what-why-when<p>A few weeks ago, I talked about <a href="/blog/2012/06/08/introduction-to-propel2-at-symfony-live-2012.html">Propel2 for the first time at Symfony Live 2012</a>. That was the first official announcement, and this blog post will sum up what I said.<!--more--></p>
<p>Basically, Propel2 is a <strong>huge refactoring</strong> of Propel 1.6. We didn't change the main design pattern (ActiveRecord), we didn't change Propel's philosophy. So people who already know Propel 1.6 will be able to use Propel2 without any problems. No need to learn a new tool. Propel2 is still easy to use, extensible and powerful.</p>
<p>As you may know, Propel is more than 7 years old, is well documented, and has a large community. That's important because you should always consider documentation, and community when you have to make a choice between different technologies.</p>
<p>During my talk, I asked attendees whether Propel was an ActiveRecord implementation, or not. They all said "yes", but it's wrong. Propel implements two main design patterns: <strong>Data Table Gateway</strong>, and <strong>Data Row Gateway</strong>. It's really important because that means we have two different APIs that address two different concerns.</p>
<p>The first API is called ActiveRecord API. I know, it's a bit confusing because I said Propel was not an ActiveRecord implementation. But, the Data Row Gateway pattern becomes ActiveRecord if you add business logic. This API is your model object. It's a smart object that owns data, and knows how to persist its own state.</p>
<p><script src="https://gist.github.com/afffec0c1355540a47a9.js"></script></p>
<p>The second API is called ActiveQuery API. This is a fully object oriented API so that you don't write SQL. This API is really fluent, and intuitive. An ORM abstracts your database layer. So don't expect PHP methods like select(), from(), where(). They don't make sense in an object oriented world. Propel provides filters, and termination methods.</p>
<p><script src="https://gist.github.com/e1e68463b3a6cfcef268.js"></script></p>
<p><script src="https://gist.github.com/1a8234ac40bbbaa30b63.js"></script></p>
<p>If you don't know Propel yet, you're probably wondering how to get these APIs. Propel relies on code generation. You start by describing your model layer in XML, and then ask Propel to generate SQL, and PHP classes with optimized code. This is not a dumb generation, and you can hook into this process. The best example is the use of behaviors.</p>
<p><script src="https://gist.github.com/78f78964647e78806211.js"></script></p>
<p>A behavior addresses a need. It's a reusable extension for your model objects. Propel has a lot of built-in behaviors, and thanks to third-party contributions, there are tons of useful behaviors you can use to enhance your model in a snap.</p>
<p><script src="https://gist.github.com/6da60290ff82f76783f0.js"></script></p>
<p>Last but not least thing, Propel is blazingly fast, and doesn't require any caching layer. Thanks to an optimized DBAL at runtime, and extreme code generation, Propel is fast by design. Propel is able to generate some SQL statements at buildtime so that it skips this step at runtime - that's what I call "extreme code generation".</p>
<p>All of these features are in Propel2 because we didn't rewrite the whole project. We just improved it, and gave it some love. Propel2 now requires PHP 5.3.2 (and maybe 5.3.3 soon). That allows us to use interesting features whitout "hacks".</p>
<p>Propel2 is also a voting member project in the Framework Interoperability Group. That's why we decided to adopt PSR-0 (Propel2 is fully namespaced), PSR-1, and PSR-2. The whole codebase has been reviewed, and improved a lot. Thanks to static analysis, we removed a lot of dead code, and reduced possible issues. In the same time, we added more tests, and we made the test suite compatible with different database vendors.</p>
<p>All of these changes allowed us to do more. We adopted five Symfony2 components: Console, Filesystem, Finder, Validator, and Yaml. That was probably the best decision we made. We removed the strong Phing dependency which was a pain to maintain. The Phing "console" was not really user friendly, and we are now happy to play with the Symfony2 Console. All of the Symfony2 components are heavily unit tested, so we started building Propel2 on strong foundations. We did the same for the logging. Propel2 relies on Monolog for that part.</p>
<p><img src="/images/blog/console.png" /></p>
<p>To manage all these new components, we trusted Composer since the beginning. That was the right decision because Composer is more and more adopted by the PHP community. Propel2 comes with a Service Container as well, but unlike Symfony2, we don't use configuration files. It's a pre-compiled container.</p>
<p>Another huge change is the new adapter layer. An adapter interacts with the database at runtime. In Propel 1.6, adapters are tied to PDO which, at first glance, is not a bad idea, but in reality, it's not really flexible. Propel2 comes with a new abstraction layer which is fully extensible (you can write a oci8 adapter if you want), and a default PDO implementation.</p>
<p>The last great improvement in Propel2 is the support of traits. Propel2 will be able to generate traits, to get rid of the classic inheritance and the base class, so that you will be able to use POPO (or domain objects) with Propel2!</p>
<p><script src="https://gist.github.com/1773b22654ddf0fedc1a.js"></script></p>
<p>Regarding the roadmap, we still have to remove the PEER classes. Once it's done, a first alpha version will be released, probably in August, or September.</p>
<p>That's all folks, and thanks to all Propel contributors!</p>
Propel 1.6.6 is Released2012-07-02T00:00:00+00:00http://www.propelorm.org/blog/2012/07/02/propel-1-6-6-is-released<p><strong>Propel 1.6.6</strong> is now available, and it's <strong>the third bug fixes only release</strong>.</p>
<p>The release is available on <a href="https://github.com/propelorm/Propel">GitHub</a> under the <a href="https://github.com/propelorm/Propel/tree/1.6.6" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">1.6.6 tag</a> on GitHub, as PEAR package (<a href="http://pear.propelorm.org/index.php?package=propel_runtime&release=1.6.6&downloads" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">runtime</a> & <a href="http://pear.propelorm.org/index.php?package=propel_generator&release=1.6.6&downloads" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">generator</a>), as archives (<a href="https://github.com/propelorm/Propel/zipball/1.6.6" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">ZIP</a> and <a href="https://github.com/propelorm/Propel/tarball/1.6.6" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">TAR</a>), and also available through <strong>Composer</strong>: <a href="http://packagist.org/packages/propel/propel1" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">propel/propel1</a>.</p>
<p>As usual, the API documentation is at: <a href="http://api.propelorm.org/" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">http://api.propelorm.org/</a>, and here is the <a href="https://raw.github.com/propelorm/Propel/master/CHANGELOG" style="margin: 0px; padding: 0px; color: #003f99; text-decoration: none;">CHANGELOG</a>. Thanks to all contributors.</p>
<p> </p>
<p> </p>
Introduction to Propel2 at Symfony Live 20122012-06-08T00:00:00+00:00http://www.propelorm.org/blog/2012/06/08/introduction-to-propel2-at-symfony-live-2012<p>Here are my slides for the talk I gave at Symfony Live 2012 in Paris: <a href="https://speakerdeck.com/u/willdurand/p/introduction-to-propel2">https://speakerdeck.com/u/willdurand/p/introduction-to-propel2</a>.</p>
<script async="" class="speakerdeck-embed" data-id="4fd1d178469d200187014dff" data-ratio="1.33333333333333" src="//speakerdeck.com/assets/embed.js"></script>
<p>If you attended my talk, please leave a feedback on <a href="https://joind.in/talk/view/6589">joind.in</a>.</p>
Discover Propel2 at Symfony Live 20122012-06-07T00:00:00+00:00http://www.propelorm.org/blog/2012/06/07/discover-propel2-at-symfony-live-2012<p>I will present <a href="http://github.com/propelorm/Propel2">Propel2</a> for the first time at <a href="http://paris2012.live.symfony.com/">Symfony Live 2012</a>, this friday in Paris. Even if Propel2 is not yet released, the major work has been done.</p>
<p>If you don't know Propel, you should come. If you know Propel 1.6, you should come. And, if you already know Propel2, you should come too because I have interesting news to share with everybody.</p>
The State Of Propel22012-04-30T00:00:00+00:00http://www.propelorm.org/blog/2012/04/30/the-state-of-propel2<p>A week ago, we held the second Propel2 IRC Meeting to talk about Propel2. It was really interesting, and constructive.</p>
<p>First, we talked about the roadmap. We asked people to delay the Twig/Builders refactoring. Some concepts we use today are not easily doable using Twig (filters for instance). In other words, we have to think a bit more on this part, and to build a proof of concept with Twig. Last thing on this point, we tagged <a href="https://github.com/propelorm/Propel2/issues?direction=desc&labels=Pre-Release+Blocker&milestone=&page=1&sort=updated&state=open">all blocking issues for a pre release</a>.</p>
<p class="p2">The second main topic was the documentation with a call for a leader, and contributors. We also decided to write a tutorial to introduce Propel2 in real life.</p>
<p class="p2"><span class="s3">The third point was Propel</span> 1.6, and Propel2 in the [s|S]ymfony world. The consensus is that symfony 1.x won't officially support Propel2. By the way, Propel 1.6 is still bug/security fixes only, and no plan to stop it. For Symfony2, the PropelBundle follows a <a href="https://github.com/propelorm/PropelBundle/wiki">new branching model</a> to prepare the Propel2's integration.</p>
<p class="p2">One more thing, we will have Behat tests in the first Propel2 release thanks to <a href="https://twitter.com/#!/everzet">@everzet</a>!</p>
<p>Please, find the transcript below:<!--more--></p>
<p class="p2"><script src="https://gist.github.com/2477550.js"></script></p>
Put Your Behaviors On Steroids2012-04-23T00:00:00+00:00http://www.propelorm.org/blog/2012/04/23/put-your-behaviors-on-steroids<p>Propel is bundled with a lot of <a href="http://www.propelorm.org/documentation/#behaviors_reference">great behaviors</a>. All of them are real behaviors, not just extensions, that's why it's really powerful. You can customize every generated class without any effort thanks to hooks, and filters.</p>
<p>To write a behavior is easy, you just have to read the <a href="http://www.propelorm.org/cookbook/writing-behavior.html">dedicated documentation page</a>, and you'll be ready to write awesome behaviors. Don't forget to look at <a href="http://www.propelorm.org/cookbook/user-contributed-behaviors.html">user contributed behaviors</a> before to start, maybe someone already wrote what you need.</p>
<p>A behavior adds logic to your model layer so it must be unit tested. Most of the time, you will have to rely on the <a href="https://github.com/propelorm/Propel/blob/master/generator/lib/util/PropelQuickBuilder.php">PropelQuickBuilder</a> to setup your test suite. This builder will build classes for a given XML schema. The code below shows an example of a test class:</p>
<p><!--more--></p>
<p><script src="https://gist.github.com/2469706.js"></script></p>
<p>The <em>MyObject</em> class is created in memory, and you can use it as usual in your code. The next step is to make your behavior easy to install. The best option is to rely on <a href="http://getcomposer.org/">Composer</a>:</p>
<p><script src="https://gist.github.com/2469507.js?file=composer.json"></script></p>
<p>The convention here is to prefix your package name with <em>propel-</em>, and to suffix it with <em>-behavior</em>. Please, follow this convention. Don't forget to add Propel as requirement, and you will be ready to add it on <a href="http://packagist.org/">Packagist</a>.</p>
<p>Now, what about using <a href="http://travis-ci.org/">travis-ci</a> to automatically run your behavior's test suite? <a href="https://github.com/Carpe-Hora/AuditableBehavior">Auditable</a>, <a href="https://github.com/willdurand/GeocodableBehavior">Geocodable</a>, or <a href="https://github.com/willdurand/TypehintableBehavior">Typehintable</a> are already on <a href="http://travis-ci.org/">travis-ci</a>, and use the following configuration:</p>
<p><script src="https://gist.github.com/2469507.js?file=.travis.yml"></script></p>
<p><script src="https://gist.github.com/2469507.js?file=bootstrap.php"></script></p>
<p>That's all! You know everything to build great behaviors.</p>
Propel2 IRC Meeting next Monday2012-04-20T00:00:00+00:00http://www.propelorm.org/blog/2012/04/20/propel2-irc-meeting-next-monday<p>Six months ago, I wrote a blog post titled <a href="http://propel.posterous.com/propel2-has-begun">Propel2 has begun!</a>, now it's time to speak about its release. Yes, no mistake there, Propel2 is almost ready, and we would love to talk with you.</p>
<p>That's why we will organize an IRC meeting on Monday, April 23 at 21:00 CEST (+02:00 - that's Paris/Zurich time) focused on <strong>Propel2</strong>.</p>
<p>The IRC meeting will be on the <a href="http://webchat.freenode.net/?channels=propel">#propel</a> channel on the Freenode IRC server.</p>
<p>See you on Monday!</p>
<p><strong> </strong></p>
<p> </p>
<p> </p>
Propel 1.6.5 is Released2012-03-27T00:00:00+00:00http://www.propelorm.org/blog/2012/03/27/propel-1-6-5-is-released<p><strong>Propel 1.6.5</strong><span style="color: #4e4e4e; font-size: 14px; line-height: 21px; text-align: left;"> is now available, it's </span><strong>the second bug fixes release</strong><span style="color: #4e4e4e; font-size: 14px; line-height: 21px; text-align: left;"> but it also contains a </span><strong>gift</strong><span style="color: #4e4e4e; font-size: 14px; line-height: 21px; text-align: left;">. Thanks to Denis Dalmais, and Cedric Lombardot, two awesome contributors, </span><span style="color: #4e4e4e; font-size: 14px; line-height: 21px; text-align: left;">Propel </span><span style="color: #4e4e4e; font-size: 14px; line-height: 21px; text-align: left;">now has an EXPLAIN feature, and </span><span style="color: #4e4e4e; font-size: 14px; line-height: 21px; text-align: left;">sfPropelORMPlugin, and PropelBundle already use it.<!--more--></span></p>
<p><span style="font-size: 14px; line-height: 21px; color: #4e4e4e; text-align: left;">[[posterous-content:gIGnAqfgCitvbrvGtqag]]</span></p>
<p><span style="color: #4e4e4e; font-size: 14px; line-height: 21px; text-align: left;">[[posterous-content:xHfvmvwmtJgfywFzjmkf]]</span></p>
<p>The release is available on <a href="https://github.com/propelorm/Propel" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">GitHub</a> under the <a href="https://github.com/propelorm/Propel/tree/1.6.5" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">1.6.5 tag</a>, as PEAR package (<a href="http://pear.propelorm.org/index.php?package=propel_runtime&release=1.6.5&downloads" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">runtime</a> & <a href="http://pear.propelorm.org/index.php?package=propel_generator&release=1.6.5&downloads" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">generator</a>) and as archives (<a href="https://github.com/propelorm/Propel/zipball/1.6.5" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">ZIP</a> and <a href="https://github.com/propelorm/Propel/tarball/1.6.5" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">TAR</a> or by browsing <a href="http://files.propelorm.org">files.propelorm.org</a> too).</p>
<p>This release is also available through <strong>Composer</strong>: <a href="http://packagist.org/packages/propel/propel1" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">propel/propel1</a>, and now uses the classmap autoloading feature provided by Composer. If you are using it to manage your dependencies, you should use this feature ;)</p>
<p>As usual, the API documentation is at: <a href="http://api.propelorm.org/" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">http://api.propelorm.org/</a>, and here is the <a href="https://github.com/propelorm/Propel/commit/4337da85673c687d557243decc0455ee45fe6719" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">CHANGELOG</a>. Thanks to all contributors.</p>
<p> </p>
Don't Do This At Home #5: Use where() Instead Of filterBy()2012-03-20T00:00:00+00:00http://www.propelorm.org/blog/2012/03/20/don-t-do-this-at-home-5-use-where-instead-of-filterby-<p>Can you spot the problem in the following snippet?</p>
<p><script src="https://gist.github.com/cf42a7131f1246f498b9.js"></script></p>
<p><!--more-->Firstly, this method can only be used if the query was created using the default class name. Remember: the developer can use an alias instead of the full model class name when creating a query:</p>
<p><script src="https://gist.github.com/ac2c1fd2417004c1e527.js"></script></p>
<p>This is useful when dealing with long classnames. Also, Propel automatically aliases a query when it is embedded into another one with <code>useXXXQuery()</code>. That means Propel may not recognize the column name <code>Book.PublishedAt</code> if the query was created using an alias. The correct way to implement a call to <code>where()</code> in a query method is to use <code>ModelCriteria::getModelAliasOrName()</code>, as follows:</p>
<p><script src="https://gist.github.com/ee53142335c95476da3a.js"></script></p>
<p>Secondly, calling <code>where($clause, $value)</code> triggers a parsing of the <code>$clause</code> string. Propel looks for column names in the string to determine the binding type to use for the value. In this example, <code>'Book.PublishedAt'</code> refers to a <code>TIMESTAMP</code> column, so Propel converts the <code>$value</code> to a timestamp string and binds it as a string. This clause parsing process uses regular expressions and has an impact on performance at runtime.</p>
<p>The alternative is to use the generated <code>filterByXXX()</code> method. When generating the query class, Propel knows the type of each column, and creates filter methods with the correct binding type baked in. Therefore, the execution of a <code>filterByXXX()</code> method requires no string parsing, and provides additional features. For instance, for date and time columns, the generated filter method allows for array values, which is especially useful in the <code>filterByPublishedAtBetween()</code> case:</p>
<p><script src="https://gist.github.com/e6a6adbc96d573c851f3.js"></script></p>
<p>And while we are at it, you don't have to name all your filter methods "filterBySomething". Propel uses this convention for column filters, but your custom filters should be named after the business rule they represent. So keep them as short and expressive as possible. In the <code>BookQuery</code> example, it's probably a better idea to rename <code>filterByPublishedAtBetween($begin, $end)</code> to <code>publishedBetween($begin, $end)</code>. That way, you can create very expressive queries:</p>
<p><script src="https://gist.github.com/6737827e5a3fcafcc482.js"></script></p>
Don't Do This At Home #4: Add Indices On Foreign Key Columns2012-03-05T00:00:00+00:00http://www.propelorm.org/blog/2012/03/05/don-t-do-this-at-home-4-add-indices-on-foreign-key-columns<p>Can you spot the problem in the following schema?</p>
<p><script src="https://gist.github.com/72182eb143556515d74f.js"></script></p>
<p><!--more-->This schema is clearly designed for a MySQL database. It turns out that Propel already adds extra indices on every column bearing a foreign key when using MySQL. So the <code><index></code> tag isn't necessary. You can remove it and reduce the table schema to:</p>
<p><script src="https://gist.github.com/084005095a645b23ef0e.js"></script></p>
<p>Remember: every line you write is a line you must maintain. Try to keep your files (code and configuration files) as small as possible. And try to remove unnecessary lines during regular cleanup sessions. These sessions, producing "red" commits (i.e. commits with a lot of removed code) feel very satisfactory, since they free your mind from future problems.</p>
Don't Do This At Home #3: Terminated Query Methods2012-02-27T00:00:00+00:00http://www.propelorm.org/blog/2012/02/27/don-t-do-this-at-home-3-terminated-query-methods<p>Can you spot the problem in the following snippet?</p>
<p><script src="https://gist.github.com/def1be217cd6931629c4.js"></script></p>
<p><!--more-->Firstly, this is a "termination method", which means that it doesn't return the query. Every termination method should take the connection as argument, otherwise you can't guarantee <a href="http://www.propelorm.org/documentation/06-transactions.html">transactional integrity</a>.</p>
<p>Secondly, this method hydrates all the Book entities satifying the queries, but just needs the first one. That's why you should use <code>findOne()</code> instead of <code>find()->getFirst()</code>, since <code>findOne()</code> includes a call to <code>limit(1)</code>.</p>
<p><strong>Update</strong>: There is a good reason why this just can't work (see the comments below). The following advice is therefore irrelevant, but is left published to testify that sometimes, code review is hard.</p>
<p>So the method should look like:</p>
<p><script src="https://gist.github.com/b7ab0bc93828a64cb032.js"></script></p>
<p>But there is more. As already mentioned in this blog, <a href="http://propel.posterous.com/design-your-queries-like-a-boss">you should never include the termination method in a custom query method</a>. What if you need to count the number of books satisfying the filters instead of returning the first result? You would have to refactor the filter part into another method, so let's do that in the first place:</p>
<p><script src="https://gist.github.com/642a6d177885b39f86c5.js"></script></p>
<p>Does this oblige you to write the call to <code>findOne()</code> manually? No, because <code>ModelCriteria::__call()</code> <a href="http://www.propelorm.org/reference/model-criteria.html#using_magic_query_methods">magically proxies</a> a call from <code>findOneByXXX($value, $con)</code> to <code>filterByXXX($value)->findOne($con)</code>. So the following still works:</p>
<p><script src="https://gist.github.com/d28038cacf7acd3a8484.js"></script></p>
<p>Finally, <code>Id</code> is actually the primary key of the <code>book</code> table. <code>findPk()</code> is more efficient than <code>findOne()</code>, because it takes advantage of the instance pooling and of the <a href="http://propel.posterous.com/propel-16-is-faster-than-ever">recent SQL optimizations introduced in Propel 1.6.3</a>. So the custom query method should be reduced to:</p>
<p><script src="https://gist.github.com/1afd569896d8e8d850c0.js"></script></p>
<p>And used as follows:</p>
<p><script src="https://gist.github.com/c8caf49468af851f4706.js"></script></p>
<p>Some purists would argue that the controller code should be as small as possible. My opinion is that it's not longer to write <code>->withPublishedComment()->findPk($id, $con)</code> than <code>->findOneByIdWithPublishedComment($id, $con)</code>, and it is more readable. But it comes down to a matter of coding style.</p>
Propel meets Silex2012-02-26T00:00:00+00:00http://www.propelorm.org/blog/2012/02/26/propel-meets-silex<p>Do you know <a href="http://silex.sensiolabs.org/">Silex</a>, the awesome PHP micro-framework? It's based on the Symfony2 components, and provides all you need to write applications.</p>
<p>Thanks to <a href="https://github.com/cristianoc72">Cristiano Cinotti</a>, we now have an official <strong><a href="https://github.com/propelorm/PropelServiceProvider">Propel Service Provider</a></strong> for <strong>Silex</strong> that eases the integration of Propel in a Silex based application. You'll find this project on our <a href="https://github.com/propelorm">GitHub organization</a>, so don't hesitate to contribute.</p>
<p>One more thing, to show how the provider works, I wrote <a href="https://github.com/willdurand/Propilex">Propilex</a>, a simple application to manage documents, with a generic <a href="https://github.com/willdurand/Propilex/blob/master/src/Propilex/Provider/RestController.php">RESTful controller</a>, and Backbone.</p>
Don't Do This At Home #2: Count After Hydration2012-02-15T00:00:00+00:00http://www.propelorm.org/blog/2012/02/15/don-t-do-this-at-home-2-count-after-hydration<p>Can you spot the problem in the following snippet?</p>
<p><script src="https://gist.github.com/bebcdcd21761858c07a0.js"></script></p>
<p>The problem is that, if all you need is the number of books, you just wasted a lot of memory.<!--more--> <code>find()</code> issues a SQL <code>SELECT</code> query, iterates over the resultset, and populates ("hydrates") a new <code>book</code> object for each row. If there are 10,000 results to the query, Propel hydrates 10,000 Model objects... for nothing, since all you need is the number of results.</p>
<p>The good way to count the number of results of a query is to use the <code>ModelCriteria::count()</code> method:</p>
<p><script src="https://gist.github.com/a349db5405518d177c0f.js"></script></p>
<p>Propel also uses <code>count()</code> internally when you call <code>paginate()</code>, so that only the results in the current page get hydrated.</p>
Design Your Queries Like A Boss2012-02-13T00:00:00+00:00http://www.propelorm.org/blog/2012/02/13/design-your-queries-like-a-boss<p>As I often repeat the same advice on questions like "What to put in Propel Queries?", or "How to name things?", I decided to share it here.</p>
<p>When you design a software, it's <a href="http://williamdurand.fr/2012/01/24/designing-a-software-by-naming-things/">important to use appropriate names</a> for each part, each class, and even each method. In Propel, the ActiveQuery API is probably the most powerful part of your Model, that's why you should take care of it.<!--more--></p>
<p>First, you should use objects, and forget SQL. If you try to write Propel Queries like SQL queries, you are on the wrong path. A few months ago, someone complained about Propel queries on Twitter because he couldn't write a query. Here is my advice, and his answer:</p>
<blockquote class="twitter-tweet">
<p>that's because you're too close to a SQL mind, try to think with an object oriented mind :)</p>
— William DURAND (@couac) <a href="https://twitter.com/couac/status/123082534262280192">October 9, 2011</a></blockquote>
<blockquote class="twitter-tweet">
<p>@<a href="https://twitter.com/couac">couac</a> m.. yes, thinking about "traversing across object relations" instead of "let's retrieve data" may help when doing complex queries.thx</p>
— Nacho Martín (@nacmartin) <a href="https://twitter.com/nacmartin/status/123087738131066880">October 9, 2011</a></blockquote>
<p>That's exactly the point! Propel is an ORM you probably use in an OO framework, so why do you want to use SQL, or another query language?</p>
<p>Once you are aware of that, you'll be able to write queries offering the same advantages as objects: encapsulation, reusability, expressivity. The next step is to understand filters. Filters are reusable methods which add conditions to a query. In a Propel Query, you have generated filters like <code>filterById()</code>, <code>filterByUser()</code>, etc. which allow you to write nice queries:</p>
<p><script src="https://gist.github.com/1789608.js"></script></p>
<p>Custom filters have to follow the same convention. You can also wrap filter methods in methods with names that make sense:</p>
<p><script src="https://gist.github.com/1789643.js"></script></p>
<p>You should always name this kind of methods by describing a use case ; that's how you'll get a clean API. Methods in a Propel Query should always return <code>$this</code> to make the API fluent, and you should never add a termination method (<code>find()</code>, <code>count()</code>, etc.). It's the controller's responsability to determine which termination method is needed.</p>
<p>The last advice is about hub methods. Since a combination of filters can be a bit too long in a controller's action, you should write hub methods that combine filters to answer a specific use case. That way, a controller just needs to call this method, it's explicit, and short :)</p>
<p>You now have all keys to write awesome Propel Queries!</p>
Behavior driven development2012-02-06T00:00:00+00:00http://www.propelorm.org/blog/2012/02/06/behavior-driven-development<p>You may know that Propel allows to define behaviors. Up to recently, I used to think behaviors just added cool features to my models. It took me time to realize that in fact, behaviors allow my models to have a uniform API. In this article, I'll expose how to define <em>application scoped behaviors</em>, but also how you should extend existing behaviors.</p>
<h3>Extending existing behaviors</h3>
<p><a href="http://www.propelorm.org/behaviors/versionable.html" title="The versionable behavior documentation">Versionable </a>is definitely a cool feature. It's easy to use, quite transparent, and it does its job : keep track of the entity modifications. But how can you keep track of who made the changes? Should you set the <code>created_by</code> fields in every place your application saves an object? And what about the behavior configuration? Should you repeat it in every table?</p>
<p>The answer is obviously no, remember the <a href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY principle</a>. So, how can you centralise this code and configuration in a better place than in <code>MyApplicationVersionableBehavior</code>?</p>
<p>Extend the original behavior, override the <code>parameters</code> property, define a <code>preSave()</code> method retrieving the current user, and from now on just focus on your business logic!</p>
<h3>Creating application behaviors</h3>
<p>If you use an ORM and a framework, it usualy means that your application requires a specific business logic. Most of the time you may feel like repeating yourself in different models, for pieces of logic concerning resource access policy, object image...</p>
<p>These are perfect candidates to become behaviors, as the API will be unified for every objects. The good thing is that logic is coded once, tested once, and available everywhere...</p>
<p>Oh, and if your behavior can be shared, just do it, it might avoid people from reinventing the wheel. Who knows, if there are enough shared behaviors, maybe Propel2 will provide a way to simply manage external behaviors!</p>
Don't Do This At Home #1: Update Many Rows By Looping on save()2012-02-03T00:00:00+00:00http://www.propelorm.org/blog/2012/02/03/don-t-do-this-at-home-1-update-many-rows-by-looping-on-save-<p>Can you spot the problem in the following snippet?</p>
<p><script src="https://gist.github.com/d62b3172814a9e80f04f.js"></script></p>
<p>The problem is that this program hydrates a lot of <code>Book</code> objects for nothing, just to update them afterwards. It's a waste of time and memory. <!--more-->The good way to do this is to use the <code>BookQuery::update()</code> method, as follows:</p>
<p><script src="https://gist.github.com/57be123d7c150729d585.js"></script></p>
<p>This second version takes almost no memory, and most important: the execution time doesn't depend on the number of records concerned by the change.</p>
<p>Note that you may need to use the first version, for instance if the model uses a behavior, and if this behavior has <code>preSave()</code> or <code>postSave()</code> hooks. In that case, instead of falling back to the first example, use the third argument of the <code>update()</code> method. This will let your code reviewer know that you did that on purpose:</p>
<p><script src="https://gist.github.com/0a81b48230240e3d45c8.js"></script></p>
Introducing ExtraPropertiesBehavior2012-02-01T00:00:00+00:00http://www.propelorm.org/blog/2012/02/01/introducing-extrapropertiesbehavior<p>Hi all,</p>
<p>when I released <a href="https://github.com/Carpe-Hora/ExtraPropertiesBehavior">ExtraPropertiesBehavior</a> on github, William asked me to introduce it on the propel blog to make sure everybody is aware it exists. I'm definitely not used to blog, so I'll try to do my best.</p>
<p>Sometimes in a dev's life you have to store fields you did not or can not plan about, that's usualy what happens when a model can be extended through plugins, but also for user preferences or configuration. To handle this kind of extension, the best way is a key/value table related to your model.</p>
<p>Most of use cases do not require this kind of behavior as joining with a key/value table can slow down queries, so always make sure that <span style="text-decoration: underline;">delegate</span> or <span style="text-decoration: underline;">concrete inheritance</span> cannot solve your problem but sometimes there is no other way. If you already had to manage such things, you probably know it can be really painful.</p>
<p>Not anymore, thanks to <a href="https://github.com/Carpe-Hora/ExtraPropertiesBehavior">ExtraPropertiesBehavior</a>, all you need is this line in your model declaration : <span class="nt"><behavior</span> <span class="na">name=</span><span class="s">"extra_properties"</span> <span class="nt">/>, rebuild the model, and you're done !</span></p>
<p><span class="nt">From now on, declare, get or set properties with dedicated methods, you even can handle simple collections. Behavior is fully documented in the README file, so no more hesitation, fork me on github ;)</span></p>
<p><span class="nt">Cheers</span></p>
Propel 1.6.4 is Released2012-01-16T00:00:00+00:00http://www.propelorm.org/blog/2012/01/16/propel-1-6-4-is-released<p><strong>Propel 1.6.4</strong> is now available, and it's <strong>the first bug fixes only release</strong>.</p>
<p>The release is available on <a href="https://github.com/propelorm/Propel">GitHub</a> under the <a href="https://github.com/propelorm/Propel/tree/1.6.4">1.6.4 tag</a> on GitHub, as PEAR package (<a href="http://pear.propelorm.org/index.php?package=propel_runtime&release=1.6.4&downloads">runtime</a> & <a href="http://pear.propelorm.org/index.php?package=propel_generator&release=1.6.4&downloads">generator</a>) and as archives (<a href="https://github.com/propelorm/Propel/zipball/1.6.4">ZIP</a> and <a href="https://github.com/propelorm/Propel/tarball/1.6.4">TAR</a>). But this release is also available through <strong>Composer</strong>: <a href="http://packagist.org/packages/propel/propel1">propel/propel1</a>.</p>
<p>As usual, the API documentation is at: <a href="http://api.propelorm.org/">http://api.propelorm.org/</a>, and here is the <a href="https://raw.github.com/propelorm/Propel/master/CHANGELOG">CHANGELOG</a>. Thanks to all contributors.</p>
A Week of Propel2011-11-21T00:00:00+00:00http://www.propelorm.org/blog/2011/11/21/a-week-of-propel<p>Yes, I know... Here are the last news related to Propel:</p>
<p> </p>
<p><span style="font-size: medium;">Propel 1.6</span></p>
<p>The Propel 1.6.x branch is always quite active and we merge bug fixes frequently. Propel 1.6 is now available through <a href="https://github.com/composer/composer">Composer</a>: <a href="http://packagist.org/packages/propel/propel1">propel/propel1</a>.</p>
<p> </p>
<p><span style="font-size: medium;">Propel2</span></p>
<p>The activity of <strong>Propel2</strong> is really active and tons of RFCs are written and discussed. You should come and discuss <a href="https://github.com/propelorm/Propel2/issues?sort=updated&direction=desc&state=open">Propel2 issues</a>. And, any help is welcomed. You'll find Propel2 on Packagist too: <a href="http://packagist.org/packages/propel/propel">propel/propel</a>.</p>
<p> </p>
<p><span style="font-size: medium;">symfony 1.x</span></p>
<p>You'll find a new <strong>sfPropelORMPlugin</strong> version available at <a href="http://www.symfony-project.org/plugins/sfPropelORMPlugin">symfony-project.org</a> and <a href="https://github.com/propelorm/sfPropelORMPlugin/tags">GitHub</a>. It contains minor fixes and embeds Propel 1.6.3.</p>
<p> </p>
<p><span style="font-size: medium;">Symfony2</span></p>
<p>Propel is now part of Symfony2 with the recent merge of a <a href="https://github.com/symfony/symfony/commit/d59bde758541f64ca2df555eb996eee1f85d49eb">Propel Bridge</a>. The <a href="https://github.com/propelorm/PropelBundle">PropelBundle</a> has been updated to use this bridge. That means the PropelBundle's <strong>master</strong> branch is compatible with Symfony2 <strong>master</strong> only (and with the upcoming <strong>2.1</strong> version). If you want to use the PropelBundle with Symfony2 version 2.x.x, you'll find a <strong>2.0</strong> branch.</p>
<p>More and more bundles are Propel compliant like the <a href="https://github.com/FriendsOfSymfony/FOSUserBundle">FOSUserBundle</a> and a work on the Symfony2 documentation will be done to write agnostic bundles compatible with both Doctrine2 and Propel.</p>
<p> </p>
<p>Long life to Propel :)</p>
Propel2 Meets Behat For The Win !2011-11-15T00:00:00+00:00http://www.propelorm.org/blog/2011/11/15/propel2-meets-behat-for-the-win-<p>Hi there,</p>
<p>Do you know <a href="http://behat.org/"><strong>Behat</strong></a> ? It's a <strong>B</strong>ehavior <strong>D</strong>riven <strong>D</strong>evelopment (BDD) Framework inspired by Ruby's Cucumber project, originally written by Konstantin Kudryashov aka <a href="https://twitter.com/everzet">@everzet</a>. The aim of this library is to provide <strong>functional tests</strong> to your application.</p>
<p>You'll probably ask me why I'm talking about Behat on the Propel's blog, and you are right. Yesterday, I received an incredible email from Konstantin and some of <a href="https://twitter.com/xosofox/status/136213138650566656">you asked us what was the subject</a>. Actually, Konstantin and the KnpLabs' team want to offer a gift to you, Propel community, by taking part in the Propel2 development! That's really fantastic!<!--more--> They'll bring <strong>Behat</strong> and <strong>BDD</strong> to the Propel2 development process, for instance:</p>
<script src="http://gist.github.com/5a92c81c16bc6f5180c5.js"></script>
<p>So, please welcome Konstantin and Behat to the <a href="https://github.com/propelorm/Propel2">Propel2</a> project!</p>
Propel Queries: Now With Manual Binding, Too2011-11-10T00:00:00+00:00http://www.propelorm.org/blog/2011/11/10/propel-queries-now-with-manual-binding-too<p>Propel is quite good at guessing the binding type to use in your queries. But sometimes you need to force a binding type which is not the one Propel would have guessed. Starting with the next minor release (1.6.4), Propel will be able to do it.<!--more--></p>
<h3>Propel Guesses Binding Types From Your Schema</h3>
<p>Consider the following query:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create()
->filterByTitle('War%')
->filterByPrice(array('max' => 20))
->find();</pre></div>
</div>
<p>Propel translates this query into the following SQL prepared statement:</p>
<div class="CodeRay">
<div class="code"><pre>SELECT book.* FROM book
WHERE book.TITLE LIKE ?
AND book.PRICE < ?</pre></div>
</div>
<p>Then, when you call <code>find()</code>, Propel uses PDO to <em>bind</em> the question mark placeholders with the values used in the <code>filterByXXX()</code> methods. Propel uses the binding type of the column as declared in the schema. Continuing on the previous example, where the <code>book.TITLE</code> column is a <code>VARCHAR</code> and the <code>book.PRICE</code> column is a <code>INTEGER</code>, Propel binds the values as follows:</p>
<div class="CodeRay">
<div class="code"><pre>$stmt = $con->prepare($sql);
$stmt->bindValue(1, 'War%', PDO::PARAM_STR); // book.TITLE is a VARCHAR
$stmt->bindValue(2, 20, PDO::PARAM_INT); // book.PRICE is an INTEGER
$stmt->execute();</pre></div>
</div>
<p>But what if you want to use another binding type?</p>
<h3>Cases When You Need a Custom Binding Type</h3>
<p>The <code>filterByXXX()</code> methods are always tied to a column, so for these Propel always knows what binding to use. However, when you use the <a href="http://www.propelorm.org/reference/model-criteria.html#relational_api">relational API</a>, you can create conditions on more than just columns.</p>
<p>For instance:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create()
->where("LOCATE('War', Book.Title) = ?", true)
->find();</pre></div>
</div>
<p>In this case, the binding should use <code>PDO::PARAM_BOOL</code>, and not <code>PDO::PARAM_STR</code>, which is the type Propel uses for the <code>book.TITLE</code> column, declared as <code>VARCHAR</code>.</p>
<p>Another example is when using <code>having()</code>:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create()
->withColumn('SUBSTRING(Book.Title, 1, 4)', 'title_start')
->having('title_start = ?', 'foo')
->find();</pre></div>
</div>
<p>Here, Propel simply refuses the query, failing with a loud:</p>
<div class="CodeRay">
<div class="code"><pre>PropelException: Cannot determine the column to bind to the parameter in clause 'title_start = ?'</pre></div>
</div>
<p>This is because the virtual column <code>title_start</code> has no intrinsec type, so Propel cannot determine which binding to use.</p>
<p>Concretely, that means that the <code>having()</code> support is somehow broken in Propel 1.6. Apart from concatenating the value to the SQL clause (and risking SQL injection), you cannot add a <code>HAVING</code> clause using <code>ActiveQuery</code>...</p>
<h3>Forcing a Custom Binding Type.</h3>
<p>...until now. A way to force a custom binding type <a href="https://github.com/propelorm/Propel/pull/182">has just made its way</a> to the <a href="https://github.com/propelorm/Propel">Propel 1.6 master branch</a> - and that means that it will be available in Propel 1.6.4.</p>
<p>It's as simple as it should be: just add the desired binding type as third parameter of either <code>where()</code> or <code>having()</code>, and you're good to go:</p>
<div class="CodeRay">
<div class="code"><pre>// custom binding in where()
$books = BookQuery::create()
->where("LOCATE('War', Book.Title) = ?", true, PDO::PARAM_BOOL)
->find();
// custom binding in having()
$books = BookQuery::create()
->withColumn('SUBSTRING(Book.Title, 1, 4)', 'title_start')
->having('title_start = ?', 'foo', PDO::PARAM_STR)
->find();</pre></div>
</div>
<p>No more errors, no more SQL injection risk. You're in control of the binding type when you need it.</p>
<p>More than ever, there is no limit to what you can do with Propel - and less limits to what you can do with the awesome PropelQuery API.</p>
Propel Gets Its Continuous Integration Server2011-11-03T00:00:00+00:00http://www.propelorm.org/blog/2011/11/03/propel-gets-its-continuous-integration-server<div style="color: #000000; font-family: Arial, Helvetica, sans-serif; font-size: 13px; background-color: #ffffff; margin: 8px;">
<p>Hi Propelers,</p>
<p>We just set up a CI server for Propel projects. You'll find it at <a href="http://ci.propelorm.org/">ci.propelorm.org</a>.</p>
<p>We choose <a href="http://jenkins-ci.org/">Jenkins</a> to build our projects as it's one of the best CI servers. Thanks to the GitHub plugin, each commit into our GitHub repository will trigger the CI server, and will launch unit tests.</p>
<p>[[posterous-content:wmymtADaufjpipCtlpmn]]</p>
<p>Oh! <a href="https://github.com/propelorm/Propel2">Propel2</a>, <a href="https://github.com/propelorm/sfPropelORMPlugin">sfPropelORMPlugin</a>, and <a href="https://github.com/propelorm/PropelBundle">PropelBundle</a> will be added soon.</p>
</div>
<p> </p>
Help Us Improve The Propel Documentation2011-11-01T00:00:00+00:00http://www.propelorm.org/blog/2011/11/01/help-us-improve-the-propel-documentation<p>The Propel documentation available at <a href="http://www.propelorm.org/">http://www.propelorm.org/</a> is hosted on GitHub. It runs with <a href="http://jekyllrb.com/">Jekyll</a>, a powerful static site generator. It allows us to manage the documentation as a real project. You'll find the repository here: <a href="https://github.com/propelorm/propelorm.github.com">https://github.com/propelorm/propelorm.github.com</a>.</p>
<p>The documentation is one of the Propel's strenghts, that's why we have to improve it all the time. To ease the process, we've added a <strong>fork & edit</strong> link to quickly fix typos or more substantial errors:</p>
<p>[[posterous-content:mcjnHkfhHHIJnECgIHAu]]</p>
<p>By clicking on this link, you'll get a fantastic editor which allows you to fix the doc, and to submit a new Pull Request. As you can see, it's really easy!</p>
<p>[[posterous-content:xmfweEkHHchmaiyIgFze]]Thanks to GitHub, updating the Propel documentation is really easy and really fast, you just need a GitHub account. And, to be honest, <a href="https://twitter.com/francoisz/status/67485227794169856">who doesn't have an account yet</a> ?</p>
<p>You're all welcome to contribute to the documentation. Oh! And, there is also a <a href="http://www.propelorm.org/documentation/#cookbook">Cookbook</a> section in which you can write your favorite recipe.</p>
Propel2 is on GitHub2011-10-16T00:00:00+00:00http://www.propelorm.org/blog/2011/10/16/propel2-is-on-github<p>Hi there,</p>
<p>I'm glad to announce that <strong>Propel2</strong> is on <strong>GitHub</strong>: <a href="https://github.com/propelorm/Propel2">https://github.com/propelorm/Propel2</a>. You'll find the <strong>Roadmap</strong> in the issues section: <a href="https://github.com/propelorm/Propel2/issues/1">https://github.com/propelorm/Propel2/issues/1</a>.</p>
<p>Everyone can contribute to Propel2, and we hope to release a first stable version within a month.</p>
Propel 1.6.3 is Released2011-10-13T00:00:00+00:00http://www.propelorm.org/blog/2011/10/13/propel-1-6-3-is-released<p><strong>Propel 1.6.3</strong> is now available with a bunch of new awesome features.</p>
<p>Main thing is speed improvement, just read <a href="http://propel.posterous.com/propel-16-is-faster-than-ever">the previous post</a> to know more about how we achieved that.</p>
<p>Among new features, the highlight is collection setter for active record object, that means you now can give a whole collection to your object and</p>
<ul>
<li>new objects will be inserted,</li>
<li>missing objects will be deleted,</li>
<li>updated objects will be updated.</li>
</ul>
<p>And all in a single line, cool isn't it ?</p>
<p>Thanks to you several bugs and improvements has been released too, keep on the good work !</p>
<p>The important thing to know about this release, is that Propel 1.6.3 is the last featured version of the 1.x branch. All next versions of this branch will be bug fixes, but don't worry, we'll continue to maintain it. Propel2 has begun and you can expect more news soon.</p>
<p>The release is available on <a href="https://github.com/propelorm/Propel">GitHub</a> under the <a href="https://github.com/propelorm/Propel/tree/1.6.3" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">1.6.3 tag</a> on GitHub, as PEAR package (<a href="http://pear.propelorm.org/index.php?package=propel_runtime&release=1.6.3&downloads" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">runtime</a> & <a href="http://pear.propelorm.org/index.php?package=propel_generator&release=1.6.3&downloads" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">generator</a>) and as archives (<a href="https://github.com/propelorm/Propel/zipball/1.6.3" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">ZIP</a> and <a href="https://github.com/propelorm/Propel/tarball/1.6.3" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">TAR</a>). As usual, the API documentation is at: <a href="http://api.propelorm.org/" style="color: #003f99; text-decoration: none; padding: 0px; margin: 0px;">http://api.propelorm.org/</a>.</p>
<p>Thanks to every contributors for this release!</p>
Propel2 has begun!2011-10-06T00:00:00+00:00http://www.propelorm.org/blog/2011/10/06/propel2-has-begun-<p>Hi there,</p>
<p>Yesterday we held an IRC meeting to talk about the future of Propel. We focused on the next major evolution, named <strong>Propel2</strong> to emphasize on the internal refactoring, and because we want to be free to break backward compatibility to get things right.</p>
<p>Hey! Propel2 ? What’s new about it ? Will I have to learn it all again ? Don’t worry, current Query and Active record API are safe, this is the reason Propel is so great so be sure it will be for long.</p>
<p>And, contrary to what was announced here, Propel2 won’t be built on top of Doctrine2. I’ll let you read the IRC transcript below to know why.</p>
<!--more-->
<p>Actually, Propel2 will be based on the 1.6.3 version. To make it up to date with today’s standards, the buildtime and runtime classes will use namespaces. That will open the door for a new autoloader (probably the Symfony2 ClassLoader Component). That means Propel2 will run on PHP version > 5.3. We’ll add new exceptions to be more explicit (there are only two named exceptions for now). We’ll remove the current Phing dependency for command line tasks and replace with another, more modern Command line component (probably the Symfony2 Console component). We’ll get rid of the Peer classes; to unclutter your model directories. We also discussed about the logging part and Monolog will probably be used. To ease the communication with more RDBMS we’ll introduce adapters instead of extending PDO for connections. And the last part was about builders: today we use PHP scripts to build PHP classes, and it’s a pain ; we proposed to use Twig instead and it was accepted by the community - like all other points.</p>
<p>That means the upcoming Propel 1.6.3 version is the last version on the 1.x branch. We’ll just maintain it by merging bugfixes. Propel 1.6 is full-featured, stable, and used since a long time by a lot of people. We recently improved its speed and provided an API to deal with collections.</p>
<script src="https://gist.github.com/1266792.js?file=Propel%20IRC%20Meeting"></script>
<p>The development of Propel2 will be held on <a href="https://github.com/propelorm/Propel2">GitHub</a>,
you’ll find the roadmap <a href="https://github.com/propelorm/Propel2/issues/1">here</a>
and any help is welcome. If you want to contribute on this huge refactoring, just come
and give a hand :)</p>
Propel 1.6 Is Faster Than Ever2011-10-03T00:00:00+00:00http://www.propelorm.org/blog/2011/10/03/propel-1-6-is-faster-than-ever<p>The upcoming Propel release, dubbed Propel 1.6.3, is faster than ever. Special optimizations were introduced in the Propel generator to make your entities blazingly fast when performing persistence actions.</p>
<h3>What Is Fast?</h3>
<p>ORMs have a bad reputation concerning performance. But complex models are often slow because the underlying database queries are slow. Before blaming the ORM, you must measure the share of the database in the processing time of a script. A slow SQL query can't be faster using an ORM.</p>
<p>That's why we, the Propel developers, measure the Propel performance relative to reference queries. Propel is not slow or fast per se. A Propel query is n times slower than the same query without an ORM, and we work hard to keep the n factor low.<!--more--></p>
<p>In fact, our target is to keep the Propel ORM under a factor 4. That means that using Propel should not be more than four times slower than bare PDO.</p>
<p>In addition, Propel provides tools to minimize database queries, as explained in <a href="http://propel.posterous.com/reduce-your-query-count-with-propel-15">a previous post</a>. Instance pooling, joined hydration, lazy loading, lazy hydration, all those features avoid a return trip to the database in many cases - and they can make Propel faster than raw PDO.</p>
<h3>What Is Slow?</h3>
<p>Four times slower than PDO can seem like a lot slower. It is not. Think about all the things that Propel has to do when you call <code>save()</code> on an entity:</p>
<ul>
<li>Check if there are related objects to save (e.g. saving an <code>Author</code> also saves all related <code>Book</code>s)</li>
<li>Determine if the object is new, to choose between an INSERT or an UPDATE statement (we'll consider an INSERT for the rest of this example)</li>
<li>List the properties of the entity that were modified</li>
<li>Craft a SQL query string using the names of the columns in the database matching the modified properties in the object</li>
<li>Prepare the values for the query (rewind resources, format dates, etc.)</li>
<li>Find the connection to use (and if there is a master and a slave, find the master connection)</li>
<li>Start a database transaction</li>
<li>If the table uses a sequence, get the value to use as primary key</li>
<li>Bind the values of each modified properties to the query string using the PDO type corresponding to the column (e.g. <code>PDO::PARAM_BOOL</code> for a boolean column, <code>PDO::PARAM_NULL</code> for a null value, etc.)</li>
<li>Execute the insertion query against the database</li>
<li>If the table doesn't use sequences, retrieve the inserted id</li>
<li>Save the related objects in the same transaction</li>
<li>Commit the transaction</li>
<li>Mark the entity as not new and not modified anymore</li>
<li>Add the entity to the instance pool</li>
<li>Return the number of modified objects (including all the related objects)</li>
</ul>
<p>That's a lot of object manipulations, and the actual <code>INSERT</code> SQL query is only one step in this long recipe.</p>
<h3>What Is Faster?</h3>
<p>In previous Propel versions, a lot of the operations described above were executed at runtime. Crafting the SQL query string, choosing the PDO type for a modified column, getting the primary key value either before the main query (for tables using sequences) or after (for tables using autoincrement), etc. There were really many things to do each time an object was saved.</p>
<p>In the next Propel version, all the computation necessary for these operations is now done at build time. The generated <code>save()</code> method actually does exactly what is described above, in the fastest PHP code possible. If you wrote a generic PHP script to do all the steps just described, it would probably be slower than Propel. Because the Propel generated classes aren't generic: they are specific to your model, and take away all the heavy duty stuff to introspect your schema, and map the object oriented world with the relational world.</p>
<p>Upgrade to the Propel master, rebuild your model, and take a look at the generated code for the <code>save()</code> method. You will understand all that it does, even without knowing how Propel works. It's because the <code>save()</code> method contains nothing Propel-specific. It's pure PHP and PDO. It's pure speed.</p>
<h3>How Much Faster Is It?</h3>
<p>In Propel 1.6.2, the "n" factor of the insert operation was around 10. It used to take ten times longer to persist an entity using Propel than executing the insert statement with PDO alone. The "n" factor is now well under nine. It's also well under the target four.</p>
<p>We use a test project called <a href="http://code.google.com/p/php-orm-benchmark/">php-orm-benchmark</a>, released under the MIT license, to compare the speeds of some basic scenarios. In addition to a simple insertion, we measure the n factor for a database retrieval using a primary key, the execution of a complex query, the raw hydration of an object based on a resultset, and a joined hydration. Here are the results:</p>
<table>
<tr>
<th> </th> <th>insert </th> <th>find pk </th> <th>complex </th> <th>hydrate </th> <th>with </th>
</tr>
<tr>
<td>PDO</td>
<td>111</td>
<td>109</td>
<td>95</td>
<td>106</td>
<td>99</td>
</tr>
<tr>
<td>Propel 1.4 </td>
<td>1260</td>
<td>502</td>
<td>123</td>
<td>311</td>
<td>303</td>
</tr>
<tr>
<td>Propel 1.5</td>
<td>1050</td>
<td>522</td>
<td>165</td>
<td>414</td>
<td>602</td>
</tr>
<tr>
<td>Propel 1.6</td>
<td>363</td>
<td>198</td>
<td>176</td>
<td>423</td>
<td>466</td>
</tr>
</table>
<p>The tests repeats the basic scenarios enough times to reach an approximate score of 100 for PDO. That's the reference. So a score of 300 means that, compared to PDO, the library has an overhead of factor 3 for this scenario. According to the load of the server used for testing, results may vary of about 10%. So any difference of less than 10% is not significant.</p>
<p>What this chart shows is that simple operations used to be quite heavy in Propel. An insertion, or a Pk find, are very fast database operations. The Propel overhead for these operations was important - even if that didn't mean Propel was slow. If the raw SQL score (using PDO) was of 10ms for an insertion, the same operation would take 100ms with Propel. That's not slow per se, but it's slower than PDO.</p>
<p>The chart also shows that the Propel overhead becomes much less noticeable when the SQL query is complex. That's because the Propel overhead depends on the number of objects to hydrate, while the SQL query time depends on the complexity of the query (and the pertinence of the indices).</p>
<p>Finally, the chart shows that the latest version of Propel 1.6 reaches the sweet spot of the "4 factor", and even goes beyond.</p>
<h3>Are The Other ORMs Faster?</h3>
<p>You will ask this question, so we'd better give you the answer. Here is how Doctrine compares with Propel on these tests:</p>
<table>
<tr>
<th> </th> <th>insert </th> <th>find pk </th> <th>complex </th> <th>hydrate </th> <th>with </th>
</tr>
<tr>
<td>PDO</td>
<td>111</td>
<td>109</td>
<td>95</td>
<td>106</td>
<td>99</td>
</tr>
<tr>
<td>Doctrine 1.2</td>
<td>2187</td>
<td>3425</td>
<td>545</td>
<td>2276</td>
<td>2365</td>
</tr>
<tr>
<td>Doctrine 1.2 with cache </td>
<td>2508</td>
<td>1500</td>
<td>665</td>
<td>1481</td>
<td>933</td>
</tr>
<tr>
<td>Doctrine 2</td>
<td>151</td>
<td>709</td>
<td>160</td>
<td>800</td>
<td>488</td>
</tr>
</table>
<p>As already noted in a <a href="http://propel.posterous.com/how-fast-is-propel-15">previous benchmark</a>, Doctrine 1.2 is VERY slow. The overhead varies between 6 and 25, which is a lot. As for Doctrine 2, it performs a lot better. In fact, it even outperforms Propel in the insertion scenario - because Doctrine 2 has a special feature to accelerate mass insertion. So a scenario made to test raw insertion speed, and repeated to make the duration significant, becomes a mass insertion, and therefore takes advantage of the Doctrine optimization.</p>
<p>Overall, Doctrine 2 performs very well, and keeps the ORM overhead under a factor 8 all the time.</p>
<h3>The Benefits of Code Generation</h3>
<p>We could boost the Propel results even further by using a cache engine, just like Doctrine does. But having to use cache brings a lot of worries: you have to think about naming the cached queries, and invalidating the cache when the underlying data changes. Unfortunately, these two are <a href="http://martinfowler.com/bliki/TwoHardThings.html">the hardest things in Computer Science</a>, according to Phil Karton. So if you can avoid using a cache engine, by all means, do it.</p>
<p>Propel's raw speed is enough to remove the need of a cache engine. That's because Propel uses code generation to prepare the base entity and query classes for runtime. The philosophy of code generation was only pushed a little further in Propel 1.6, so you can now use the Propel ORM without any afterthought about performance.</p>
Announcing Propel IRC Meeting Next Wednesday2011-09-29T00:00:00+00:00http://www.propelorm.org/blog/2011/09/29/announcing-propel-irc-meeting-next-wednesday<div>
<p>Hi there,</p>
<p>As I said previously, we will organize an IRC meeting on Wednesday October 5th at 17:00 CET (+01:00 - that's Paris time). We will talk about the future of Propel, and we will introduce <strong>Propel2</strong>. Yes! You read that well ; we have plenty ideas to discuss : PSR-0 autoloader, refactoring of builders using Twig, improvement of the whole code and so on!</p>
<p>The IRC meeting will be on the <a href="http://webchat.freenode.net/?channels=propel">#propel</a> channel on the Freenode IRC server.</p>
<p>See you on Wednesday !</p>
</div>
<p> </p>
You Don't Need A Computer To Learn Propel2011-09-28T00:00:00+00:00http://www.propelorm.org/blog/2011/09/28/you-don-t-need-a-computer-to-learn-propel<p>That may sound like a contradiction, since Propel is classically used by developers on computers, but you don't need to be in front of your desktop or laptop to learn Propel.</p>
<p>This is because the Propel website is now optimized for smartphones and tablets. Just browse to <a href="http://www.propelorm.org">http://www.propelorm.org</a> with your mobile device, and enjoy the complete Propel documentation in a readable form.<!--more--></p>
<p>[[posterous-content:dtwwDooIJcysfzJmiwfq]]</p>
<p>I don't know about you, but I often need to check some Propel implementation details, or to show some code examples, while not in front of a computer. Now it's possible!<p /></p>
Propel 1.6.2 is Released2011-09-14T00:00:00+00:00http://www.propelorm.org/blog/2011/09/14/propel-1-6-2-is-released<p>A month and few days ago, I became the new Propel lead developer. Today, I'm glad to announce the <strong>new Propel version</strong>: <strong>1.6.2</strong>. This is my very first release and I have tons of things to tell you.</p>
<p><strong><span style="font-size: medium;">Propel</span></strong></p>
<p>From 1.6.1 to 1.6.2, <em>10 contributors</em> worked hard and provided <em>145 commits</em> with two new incredible behaviors and a lot of bugfixes. I would like to thank François Zaninotto, Julien Muetton, Andrey Janzen, Markus Lervik, Niklas Närhinen, Denis Dalmais, Chris Lovell, Nibsirahsieu and mskarupianski. They contributed to improve Propel and that's great !<!--more--></p>
<p>Since Propel has moved to <a href="https://github.com/propelorm">GitHub</a>, the continuous development is intensive. We (the core team) are glad to see how things moved and we were suprised to see this great activity. Thanks guys to help keeping Propel alive.</p>
<p>New release is available on GitHub under the <a href="https://github.com/propelorm/Propel/tree/1.6.2">1.6.2 tag</a> on GitHub, as PEAR package (<a href="http://pear.propelorm.org/index.php?package=propel_runtime&release=1.6.2&downloads">runtime</a> & <a href="http://pear.propelorm.org/index.php?package=propel_generator&release=1.6.2&downloads">generator</a>) and as archives (<a href="https://github.com/propelorm/Propel/zipball/1.6.2">ZIP</a> and <a href="https://github.com/propelorm/Propel/tarball/1.6.2">TAR</a>).</p>
<p>You will find the new API documentation at: <a href="http://api.propelorm.org/">http://api.propelorm.org/</a> and XSD files under their own subdomain:<span style="color: #56db39; font-family: monospace;"><span style=""> </span></span><a href="http://xsd.propelorm.org/1.6/database.xsd">http://xsd.propelorm.org/1.6/database.xsd</a>. Thanks to Benjamin Börngen-Schmidt and Veikko Mäkinen for their work on the server/DNS part.</p>
<p> </p>
<p><strong><span style="font-size: medium;">sfPropelORMPlugin</span></strong></p>
<p>Propel is not the unique project in the <a href="https://github.com/propelorm">Propel organization</a> and it is widely used in the symfony world. I announced few weeks ago the release of the new Propel plugin for <em>symfony 1.x</em> : <strong>sfPropelORMPlugin</strong>. I would like to thank Julien Muetton and Luca Saba for their work on it to decouplate the plugin and to get it work with both Git and SVN.</p>
<p>Another good point is that the i18n support is fixed thanks to Vincent Mazenod. You'll be able to use both <a href="http://www.propelorm.org/cookbook/symfony1/how-to-use-old-SfPropelBehaviori18n-with-sf1.4">the (old) native symfony i18n</a> and <a href="http://www.propelorm.org/cookbook/symfony1/how-to-use-Propel%20i18n-behavior-with-sf1.4">the Propel i18n behavior</a> which is better.</p>
<p>Please, find the <a href="https://github.com/propelorm/sfPropelORMPlugin/tree/1.1">1.1 tag</a> on GitHub for the new version which bundles Propel 1.6.2, the <a href="https://github.com/propelorm/sfPropelORMPlugin/zipball/1.1">ZIP</a> and <a href="https://github.com/propelorm/sfPropelORMPlugin/tarball/1.1">TAR</a> files and <a href="http://www.symfony-project.org/plugins/sfPropelORMPlugin">the plugin on symfony-project.org</a>.</p>
<p> </p>
<p><strong><span style="font-size: medium;">PropelBundle</span></strong></p>
<p>There is another project in our organization, the <strong><a href="https://github.com/propelorm/PropelBundle">PropelBundle</a> </strong>which provides the integration of Propel in <em>Symfony2</em>. You can read the introduction to <a href="http://www.propelorm.org/cookbook/working-with-symfony2">Propel with Symfony2</a> on the Propel documentation.</p>
<p> </p>
<p><strong><span style="font-size: medium;">Propel documentation</span></strong></p>
<p>Propel documentation has a new design and a new way to improve it. Visit <a href="http://www.propelorm.org/">http://www.propelorm.org/</a> to see it in action. We are using <a href="https://github.com/mojombo/jekyll">Jekyll</a>, a static site generator written in ruby and the <a href="http://pages.github.com/">gh-pages</a> feature provided by GitHub. The code is available <a href="https://github.com/propelorm/propelorm.github.com">here</a>. Feel free to contribute on it by submiting Pull Request to fix typo errors and/or to provide new recipes in the cookbook.</p>
<p> </p>
<p>As you can see, we made a lot of changes to improve Propel and the way people can contribute. It's really important for us and GitHub eases our development process like a charm. Thanks to all contributors, bug reporters and all people who makes Propel better day by day.</p>
<p>Yes, Propel won't die.</p>
Introducing archivable behavior, and why soft_delete is deprecated2011-08-29T00:00:00+00:00http://www.propelorm.org/blog/2011/08/29/introducing-archivable-behavior-and-why-soft-delete-is-deprecated<p>Propel 1.6 introduces a new behavior called <code>archivable</code>. It gives model objects the ability to be copied to an archive table. By default, the behavior archives objects on deletion, acting as a replacement of the <code>soft_delete</code> behavior. So why is it exciting?</p>
<h3>The idea behind the <code>soft_delete</code> behavior</h3>
<p>The <a href="http://propelorm.github.com/propel-docs/behaviors/soft-delete"><code>soft_delete</code> behavior</a> promises to "override the deletion methods of a model object to make them 'hide' the deleted rows but keep them in the database." In order to achieve this soft deletion, the behavior adds a <code>deleted_at</code> column to the table it is applied on, and sets this column to the current date whenever an object is deleted:<!--more--></p>
<div class="CodeRay">
<div class="code"><pre>$book->delete();
// translates into MySQL as
// UPDATE book SET book.deleted_at = CURTIME() WHERE book.id = 123;</pre></div>
</div>
<p>Also, it modifies the read queries to ignore all objects having a not null <code>deleted_at</code> column value, so only not deleted objects show up.</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create()->find();
// SELECT * FROM book WHERE book.deleted_at IS NULL;</pre></div>
</div>
<p>It makes it easy to recover deleted objects (by setting the <code>deleted_at</code> date to NULL again), and to select the soft deleted objects for further treatment.</p>
<h3><code>soft_delete</code> behavior shortcomings</h3>
<p>It looks smart at first sight, but in reality the <code>soft_delete</code> behavior is flawed. If you have been using it a lot, you may have already spotted some of it shortcomings:</p>
<ul>
<li><strong>Performance</strong>: Deleted objects are still present in the table. The table size increases even when you delete objects to clean it up. So read queries become slower when the number of records in the table grows. Adding new indexes to the table to optimize queries can be counter-productive if you forget to put the <code>deleted_at</code> column in the indexes, because every query includes a snippet looking like <code>WHERE deleted_at IS NULL</code>.</li>
<li><strong>Doesn't work all the time</strong>: Read queries sometimes return objects that have been soft deleted. This usually happens when using <code>paginate()</code>, or <code>joinWith()</code>. It all comes down to the algorithm used to "hide" deleted objects: add a <code>WHERE deleted_at IS NULL</code> condition to the next query. It doesn't work if there is more than one query (which is the case for <code>paginate()</code>) or for joined tables (which is the case for <code>joinWith()</code>).</li>
<li><strong>Not compatible with unique constraints</strong>: Whether you have a non-autoincremental primary key, or a unique index, the addition of a <code>delete_at</code> column breaks you model. </li>
<li><strong>Not consistent across database vendors</strong>: If two tables have the <code>soft_delete</code> behavior, and share a foreign key with <code>ON DELETE CACADE</code>, you may expect that soft deleting in the main table also soft deletes related records in the second table. It works on SQLite, or if you set <code>propel.emulateForeignKeyConstraints</code> to true in the <code>build.properties</code>. But it doesn't work by default on MySQL for instance.</li>
</ul>
<p>There are more shortcomings in some very specific use cases. For instance, <a href="https://github.com/propelorm/Propel/issues/48">should the <code>postDelete()</code> event be fired after a soft deletion</a>? One could say yes, one could say no, and both would be right in their particular use case.</p>
<p>All these shortcomings are not specific to the Propel implementation. In fact, <a href="http://www.doctrine-project.org/documentation/manual/1_1/hu/behaviors:core-behaviors:softdelete">the <code>soft_delete</code> behavior for Doctrine 1</a>, <a href="http://www.symfony-project.org/plugins/sfPropelParanoidBehaviorPlugin">the <code>paranoid</code> behavior for Propel in symfony 1</a>, <a href="https://github.com/doctrine/mongodb-odm-softdelete">various</a> <a href="http://codeutopia.net/blog/2010/12/04/how-to-create-doctrine-1-style-soft-delete-in-doctrine-2/">attempts</a> at doing the same for Doctrine2, and the source of them all, <a href="https://github.com/technoweenie/acts_as_paranoid">the <code>acts_as_paranoid</code> behavior for Rails ActiveRecord</a> all have the same flaws. The shortcomings come from the principle of the added <code>deleted_at</code> column, and can't be fixed by implementation.</p>
<p>I'll write that again: the <code>soft_delete</code> behavior can't be fixed. It's a leaky abstraction. To achieve the same functionality, the paradigm must change.</p>
<h3>Introducing the <code>archivable</code> behavior</h3>
<p>If you want to be able to recover deleted objects, a better idea would be to put these into another repository. In database terms, this translates to "copy records to another table". This is the idea behind <code>archivable</code>. It provides a new ActiveRecord method, <code>archive()</code>, to persist a copy of the current object into an archive table.</p>
<div class="CodeRay">
<div class="code"><pre>$book = new Book();
$book->setTitle('War and Peace');
$book->save();
// INSERT INTO book (title) VALUES ('War and Peace');
$book->archive();
// INSERT INTO book_archive (id, title) VALUES (123, 'War and Peace');</pre></div>
</div>
<p>Here, the <code>archive()</code> method first checks the existence of a <code>book_archive</code> record with the same primary key. If found, it updates it; if not found, it inserts a new one. In both cases, the <code>book_archive</code> record columns copy the values from the <code>book</code> record.</p>
<p>The <code>archive()</code> method is easy to trigger before the deletion; as a matter of fact, the <code>archivable</code> behavior does that on <code>delete()</code>:</p>
<div class="CodeRay">
<div class="code"><pre>$book->delete();
// INSERT INTO book_archive (id, title) VALUES (123, 'War and Peace');
// DELETE FROM book WHERE book.id = 123;</pre></div>
</div>
<p>Even if <code>delete()</code> triggers <code>archive()</code>, it is easy to bypass the <code>archivable</code> and do a hard delete:</p>
<div class="CodeRay">
<div class="code"><pre>// hard delete a book
$book->deleteWithoutArchive()</pre></div>
</div>
<p>None of the shortcomings described above plague the <code>archivable</code> behavior, because a call to <code>delete()</code> actually deletes records from the main table. So the <code>archivable</code> behavior can be seen as a fixed <code>soft_delete</code> behavior providing the same functionality.</p>
<p>The <code>archivable</code> behavior landed in the Propel 1.6 branch last week, and will be bundled in the next stable version (1.6.2). As for every behavior included in the Propel core, it is heavily (unit) tested, and fully documented (see the <a href="http://propelorm.github.com/propel-docs/behaviors/archivable"><code>archivable</code> behavior documentation</a>).</p>
<h3>More than soft deletion</h3>
<p>The merit of a good idea is that it offers more benefits than just the reason for its creation. <code>archivable</code> covers the requirements of <code>soft_delete</code>, and much more.</p>
<p>First, you can archive an object without deleting it. Whether you need a copy for important actions, or to get a backup for dangerous operations, archiving is often a good idea.</p>
<div class="CodeRay">
<div class="code"><pre>// archive an existing book
$book->archive();
// the book still exists in the main table
echo $book->isDeleted(); // false</pre></div>
</div>
<p>Of course, you probably already have a backup of the whole database somewhere to avoid data loss. But <code>archivable</code> provides a more atomic way to archive data, and to recover it:</p>
<div class="CodeRay">
<div class="code"><pre>// find the archived version of an existing book
$archivedBook = $book->getArchive();
// populate a book based on an archive
$book = new Book();
$book->populateFromArchive($archivedBook);
// restore an object to the state it had when last archived
$book->restoreFromArchive();</pre></div>
</div>
<p>The Query class also gets an <code>archive()</code> method to archive a set of objects:</p>
<div class="CodeRay">
<div class="code"><pre>// archive all books by Leo Tolstoi
BookQuery::create()
->useAuthorQuery()
->filterByName('Leo Tolstoi')
->endUse()
->archive();</pre></div>
</div>
<p>You can override the <code>archive()</code> method to add custom logic, for instance to also archive related objects:</p>
<div class="CodeRay">
<div class="code"><pre>class Book extends BaseBook
{
public function archive(PropelPDO $con = null)
{
// archive the book reviews
BookReviewQuery::create()
->filterByBook($this)
->archive($con);
// archive the current object
return parent::archive($con);
}
}</pre></div>
</div>
<p>This method is called both when archiving a single object, and when archiving a set of object through the Query's <code>archive()</code> method.</p>
<p>You can even store the archive table in another database than the one containing the main table. The archive database can be in another server, or even in another datacenter, providing additional security to your archived data.</p>
<h3>Why did you delete objects in the first place?</h3>
<p>"Archive" is a better abstraction than "soft delete". But not all the time. Why did you want to keep deleted objects in the first place? You probably used the "delete" verb because your model didn't provide a better verb to suit your requirements. In that case, you should enhance your model to give it the ability you need, and leave <code>delete()</code> alone. In other terms, improve your domain model. Udi Dahan, who often blogs about Service-Oriented Architectures and Domain Models, <a href="http://www.udidahan.com/2009/09/01/dont-delete-just-dont/">puts it this way</a>:</p>
<blockquote class="posterous_medium_quote">
<p>Orders aren’t deleted – they’re cancelled. There may also be fees incurred if the order is canceled too late.</p>
<p>Employees aren’t deleted – they’re fired (or possibly retired). A compensation package often needs to be handled.</p>
<p>Jobs aren’t deleted – they’re filled (or their requisition is revoked).</p>
</blockquote>
<p>So there are many ways to circumvent the <code>soft_delete</code> shortcomings, and probably not good reason to keep using this behavior. For the better, <code>soft_delete</code> is now deprecated in Propel 1.6.</p>
Propel Gets Class Table Inheritance, With A Twist2011-08-22T00:00:00+00:00http://www.propelorm.org/blog/2011/08/22/propel-gets-class-table-inheritance-with-a-twist<p>Propel 1.6 already supports <a href="http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html">Single Table Inheritance</a> and <a href="http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html">Concrete Table Inheritance</a>, two powerful ways to map an object inheritance to a relational persistence. However, every once in a while, a Propel user pops in and asks for a Propel implementation of <a href="http://martinfowler.com/eaaCatalog/classTableInheritance.html">Class Table Inheritance</a>. This type of inheritance uses one table per class in the inheritance structure ; each table stores only the columns it doesn't inherits from its parent.<!--more--></p>
<p>For example, a sports news website displays statistics about various sports player. The Class Table Inheritance patterns translates that to a <code>player</code> table storing the identity, and two "children" tables, <code>footballer</code> and <code>basketballer</code>, with distinct statistics columns.</p>
<div class="CodeRay">
<div class="code"><pre>player
-------
first_name
last_name
footballer
------------
goals_scored
fouls_committed
basketballer
------------
points
field_goals</pre></div>
</div>
<h3>Implementing Class Table Inheritance via Joins</h3>
<p>I have always thought that Class Table Inheritance isn't really inheritance. Actually, it is usually achieved using joins, by defining a foreign key in the children tables to the parent table, as follows:</p>
<div class="CodeRay">
<div class="code"><pre>player
-------
id
first_name
last_name
footballer
------------
id
goals_scored
fouls_committed
player_id // foreign key to player.id
basketballer
------------
id
points
field_goals
three_points_field_goals
player_id // foreign key to player.id</pre></div>
</div>
<p>So to create a basketballer with an identity, relate a <code>Basketballer</code> to a <code>Player</code> the usual Propel way:</p>
<div class="CodeRay">
<div class="code"><pre>// create a Basketballer
basketballer = new Basketballer();
$basketballer->setPoints(101);
$basketballer->setFieldGoals(47);
$basketballer->setThreePointsFieldGoals(7);
// create a Player
$player = new Player();
$player->setFirstName('Michael');
$player->setLastName('Giordano');
// relate the two objects
$basketballer->setPlayer($player);
// save the two objects
$basketballer->save();</pre></div>
</div>
<h3>The Delegation Pattern</h3>
<p>But this isn't inheritance. What the user expects, with the inheritance concept in mind, is to deal only with a <code>Basketballer</code> instance to manage both the identity and the stats, as follows:</p>
<div class="CodeRay">
<div class="code"><pre>$basketballer = new Basketballer();
$basketballer->setPoints(101);
$basketballer->setFieldGoals(47);
$basketballer->setThreePointsFieldGoals(7);
// use inheritance to hide join
$basketballer->setFirstName('Michael');
$basketballer->setLastName('Giordano');
// save basketballer and player
$basketballer->save();</pre></div>
</div>
<p>Even if the two pieces of code would produce the same result (one <code>basketballer</code> record and one <code>player</code> record), the second one is more object-oriented.</p>
<p>But is it possible to achieve that using the PHP inheritance system? Not really, because the user wants the name information to be store in the <code>player</code> table, not in the <code>basketballer</code> table (otherwise Concrete Table Inheritance would be a better fit). As a matter of fact, the <code>Basketballer</code> object needs the <code>Player</code> object to handle the first name and last name for him. In object-oriented design, this is called "delegation". It's a very common design pattern, for example in Objective-C, where it is used extensively.</p>
<p>In PHP, a usual implementation of the delegation pattern is via the <code>__call()</code> magic method. So in order to make the previous code snippet work, all that's needed is the following code:</p>
<div class="CodeRay">
<div class="code"><pre>class Basketballer extends BaseBasketballer
{
/**
* Delegating not found methods to the related Player
*/
public function __call($method, $params)
{
if (is_callable(array('Player', $method))) {
if (!$delegate = $this->getPlayer()) {
$delegate = new Player();
$this->setPlayer($delegate);
}
return call_user_func_array(array($delegate, $method), $params);
}
return parent::__call($method, $params);
}
}</pre></div>
</div>
<p>And here you go, a <code>Basketballer</code> can reply to the <code>Player</code> method calls, and hide the join used to implement class table inheritance. For the end user, everything happens as if <code>Basketballer</code> actually extended <code>Player</code>, but the <code>Player</code> data is stored in a separate table.</p>
<h3>Introducing the <code>delegate</code> behavior</h3>
<p>Instead of providing yet another extension system in the Propel ActiveRecord classes, I implemented a behavior, called <code>delegate</code>, which allows to delegate method calls to another model. This behavior generates exactly the <code>__call()</code> code shown above, provided you set up your schema in the following way:</p>
<div class="CodeRay">
<div class="code"><pre><table name="player">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
<column name="first_name" type="VARCHAR" size="100"/>
<column name="last_name" type="VARCHAR" size="100"/>
</table>
<table name="basketballer">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="points" type="INTEGER" />
<column name="field_goals" type="INTEGER" />
<column name="three_points_field_goals" type="INTEGER" />
<column name="player_id" type="INTEGER" />
<foreign-key foreignTable="player">
<reference local="player_id" foreign="id" />
</foreign-key>
<behavior name="delegate">
<parameter name="to" value="player" />
</behavior>
</table></pre></div>
</div>
<p>Rebuild the model, and the <code>Basketballer</code> can now delegate all the method calls it can't manage on its own to his related <code>Player</code>, whether such a player already exists or not.</p>
<p>The <code>delegate</code> behavior, together with complete documentation and unit tests, <a href="https://github.com/propelorm/Propel/pull/46">has landed</a> in the Propel master yesterday, and will be part of the upcoming 1.6.2 release.</p>
<p>You may think: Why should I be enthusiast about a behavior generating six lines of code in a <code>__call()</code> method? First of all, the <code>delegate</code> behavior has more features than than just simulating Class Table Inheritance. Second of all, it allows you to design your object model with delegation in mind, and that opens a lot of new possibilities.</p>
<h3>Multiple Delegation</h3>
<p>In PHP, an object can only inherit from one parent. However, delegation isn't restricted to a single class. So the <code>Basketballer</code> class can delegate to both a <code>Player</code> and an <code>Employee</code> class:</p>
<div class="CodeRay">
<div class="code"><pre><table name="player">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
<column name="first_name" type="VARCHAR" size="100"/>
<column name="last_name" type="VARCHAR" size="100"/>
</table>
<table name="employee">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
<column name="salary" type="INTEGER"/>
</table>
<table name="basketballer">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="points" type="INTEGER" />
<column name="field_goals" type="INTEGER" />
<column name="three_points_field_goals" type="INTEGER" />
<column name="player_id" type="INTEGER" />
<foreign-key foreignTable="player">
<reference local="player_id" foreign="id" />
</foreign-key>
<column name="employee_id" type="INTEGER" />
<foreign-key foreignTable="employee">
<reference local="employee_id" foreign="id" />
</foreign-key>
<behavior name="delegate">
<parameter name="to" value="player, employee" />
</behavior>
</table></pre></div>
</div>
<p>Using only a <code>Basketballer</code> instance, a developer can now populate three records in three different tables:</p>
<div class="CodeRay">
<div class="code"><pre>$basketballer = new Basketballer();
$basketballer->setPoints(101);
$basketballer->setFieldGoals(47);
$basketballer->setThreePointsFieldGoals(7);
// delegate to player
$basketballer->setFirstName('Michael');
$basketballer->setLastName('Giordano');
// delegate to employee
$basketballer->setSalary(2000000);
// save basketballer and player and employee
$basketballer->save();</pre></div>
</div>
<p>The liberty to use multiple inheritance might scare you, for it breaks one of the constraints that prevent many developers from designing horrible conceptual data models. However, it makes it possible to support Class Table Inheritance for several levels. For instance, if you modify the class hierarchy to have a <code>ProBasketballer</code> extend <code>Basketballer</code> extend <code>Player</code>, simple delegation doesn't work there. Even if <code>ProBasketballer</code> delegates to <code>Basketballer</code>, the generated <code>ProBasketballer::__call()</code> code won't be able to manage delegating all the way up to <code>Player</code>. The solution is to use multiple delegation to explicitly delegate to all ancestors, as follows:</p>
<div class="CodeRay">
<div class="code"><pre><table name="player">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
<column name="first_name" type="VARCHAR" size="100"/>
<column name="last_name" type="VARCHAR" size="100"/>
</table>
<table name="basketballer">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="points" type="INTEGER" />
<column name="field_goals" type="INTEGER" />
<column name="three_points_field_goals" type="INTEGER" />
<column name="player_id" type="INTEGER" />
<foreign-key foreignTable="player">
<reference local="player_id" foreign="id" />
</foreign-key>
<behavior name="delegate">
<parameter name="to" value="player" />
</behavior>
</table>
<table name="pro_basketballer">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="salary" type="INTEGER" />
<column name="basketballer_id" type="INTEGER" />
<foreign-key foreignTable="basketballer">
<reference local="basketballer_id" foreign="id" />
</foreign-key>
<column name="player_id" type="INTEGER" />
<foreign-key foreignTable="player">
<reference local="player_id" foreign="id" />
</foreign-key>
<behavior name="delegate">
<parameter name="to" value="basketballer, player" />
</behavior>
</table></pre></div>
</div>
<p>Now a <code>ProBasketballer</code> can have a salary, while a simple <code>Basketballer</code> can't.</p>
<h3>Delegating The Other Way Around</h3>
<p>In all the examples shown previously, the foreign key supporting the delegation relation was located in the table that was actually delegating. This is because the main table (<code>basketballer</code> in the example) must have only one delegate in the other table (<code>player</code> in the example). The model must show a many-to-one relationship, and that places the foreign key in the delegating table.</p>
<div class="CodeRay">
<div class="code"><pre>player
-------
id
first_name
last_name
basketballer // delegates to player
------------
id
points
field_goals
three_points_field_goals
player_id // foreign key to player.id</pre></div>
</div>
<p>But there is another way to have only one related record. Instead of using a many-to-one relationship, one could use a one-to-one relationship. In Propel, this is achieved by setting a foreign key which is also a primary key. So the <code>player_id</code> column can be removed, and the foreign key be placed on the <code>basketballer</code> primary key.</p>
<div class="CodeRay">
<div class="code"><pre>player
-------
id
first_name
last_name
basketballer // delegates to player
------------
id // foreign key to player.id
points
field_goals
three_points_field_goals</pre></div>
</div>
<p>Since this kind of model is also suitable for delegation, the <code>delegate</code> behavior has been designed to supports one-to-one relationships as well.</p>
<p>One-to-one relationships are reversible. That means that the foreign key could be placed in the other table. For the player/basketballer model, that would mean:</p>
<div class="CodeRay">
<div class="code"><pre>player
-------
id // foreign key to basketballer.id
first_name
last_name
basketballer // delegates to player
------------
id
points
field_goals
three_points_field_goals</pre></div>
</div>
<p>This is still supported by the behavior. But such a setup creates one constraint: a player can't have both <code>basketballer</code> and <code>footballer</code> stats anymore. In this case, it's not such a good idea. But think about this other use case:</p>
<div class="CodeRay">
<div class="code"><pre>user_profile
------------
id // foreign key to user.id
first_name
last_name
email
telephone
user // delegates to user_profile
-------
id
login
password</pre></div>
</div>
<p>This schema may sound familiar to users of the <code>sfGuardPlugin</code> for the symfony framework. In this plugin, the <code>User</code> class handles only the basic identification data for a user. All the other information, like email address or full identity, is "delegated" to another class, the <code>UserProfile</code>. It is <em>not</em> a use case for Single Table Inheritance, but it's a great one for delegation.</p>
<p>Using the <code>delegate</code> behavior, Propel can now give access to the profile information directly from the user class:</p>
<div class="CodeRay">
<div class="code"><pre><table name="user">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
<column name="login" type="VARCHAR" size="100"/>
<column name="password" type="VARCHAR" size="100"/>
<behavior name="delegate">
<parameter name="to" value="user_profile" />
</behavior>
</table>
<table name="user_profile">
<column name="id" type="INTEGER" primaryKey="true"/>
<column name="first_name" type="VARCHAR" size="100"/>
<column name="last_name" type="VARCHAR" size="100"/>
<column name="email" type="VARCHAR" size="100"/>
<column name="telephone" type="VARCHAR" size="100"/>
<foreign-key foreignTable="user">
<reference local="id" foreign="id" />
</foreign-key>
</table></pre></div>
</div>
<p>In PHP, the developer can now write:</p>
<div class="CodeRay">
<div class="code"><pre>$user = new User();
$user->setLogin('francois');
$user->setPassword('S€cr3t');
// Fill the profile via delegation
$user->setEmail('francois@example.com');
$user->setTelephone('202-555-9355');
// save the user and its profile
$user->save();</pre></div>
</div>
<p>This is why the concept of delegation is more powerful than Class Table Inheritance. There are a lot of use cases that delegation solves, without even being designed to do so. And this is why the introduction of the <code>delegate</code> behavior in Propel is such a great news.</p>
Announcing sfPropelORMPlugin2011-08-18T00:00:00+00:00http://www.propelorm.org/blog/2011/08/18/announcing-sfpropelormplugin<div>Hi there,</div>
<p />
<div>
<div>I'm glad to announce a good news for symfony 1.x users. As you may know, the whole Propel project has moved to the <a href="https://github.com/propelorm">PropelORM organization</a> on Github and you'll find the <a href="https://github.com/propelorm/Propel">Propel</a> project, the <a href="https://github.com/propelorm/PropelBundle">PropelBundle</a> for Symfony2 and the <a href="https://github.com/propelorm/sfPropelORMPlugin">sfPropel15Plugin</a> for symfony 1.x.</div>
<p />
<div>But, wait ? You said "sfPropel15Plugin" while the last Propel version is 1.6.1 ?! Agreed, it does not make sense. That's why, a few weeks ago <a href="https://github.com/propelorm/sfPropelORMPlugin/issues/51">I proposed to rename this plugin</a>. Thanks to François, we found a lovely name : <strong>sfPropelORMPlugin</strong>.</div>
<p />
</div>
<div>I started to work on it and thanks to <a href="http://www.twitter.com/themouette">Julien</a> who did a great work to fix all tests, we can proudly announce the release of this plugin: <a href="https://github.com/propelorm/sfPropelORMPlugin">https://github.com/propelorm/sfPropelORMPlugin</a>.</div>
<div>Basically, it provides the same features but it's designed to work fine with both SVN and Git through Github. I let you read the README to learn how to deal with it. Upgrade is really easy, you just have to install <a href="https://github.com/propelorm/sfPropelORMPlugin">sfPropelORMPlugin</a> as a new plugin and migrate your behaviors registration in propel.ini.</div>
<p />
<div>As usual, feel free to fork the plugin and to participate by reporting issues or send pull requests. Oh, and expect more news on this blog soon !</div>
Propel won't die.2011-08-04T00:00:00+00:00http://www.propelorm.org/blog/2011/08/04/propel-won-t-die-<p>Hi there, my name is William Durand and, François introduced me <a href="http://propel.posterous.com/propel-has-a-new-leader">in the previous post</a>. He said a lot of positive points about me and I'm glad to hear that from him.</p>
<p>Let me introduce myself a bit more, I'm currently working with François at e-TF1 and while I'm mainly focused on symfony2 developments using Propel and I can assure you it works well !</p>
<p>A month ago, some people asked if I'd become the new Propel lead developer and my answer was quite clear : very unlikely. François did a huge work on Propel, thanks for that but it makes it even harder to take over.</p>
<p>I though a lot before asking him the keys and I would probably never asked him without the trust of two guys : <a href="https://github.com/themouette">Julien Muetton</a> (<a href="https://twitter.com/themouette">@themouette</a>) and <a href="https://github.com/mazenovi">Vincent Mazenod</a> (<a href="https://twitter.com/mazenovi">@mazenovi</a>) who were sharing my feeling : <strong>Propel can't die</strong>.<!--more--></p>
<div>
<p>Open source is about involving people together to build awesome things, that's why I'm proud to announce that Julien, Vincent and I are what we can call "<strong>the new Propel core team</strong>".</p>
</div>
<p>From now on, the Propel project is not just the <strong>Propel ORM</strong>, it's also the <strong>sfPropel15Plugin </strong>plugin for symfony 1.x and the <strong>PropelBundle </strong>bundle for Symfony2 and we are three developers to improve these three projects.</p>
<p>We've known each other for a long time and we share the same philosophy about how to improve Propel. Our main goal is to provide a <em>better integration of Propel with Symfony2</em> in order to propose a real alternative to Doctrine2. <em>More points</em> will be presented <em>in the next weeks</em> on this blog. We are walking in the same direction in order to keep Propel on the state of the art.</p>
<div>
<p>By the way, a major change just appeared. The plain old <em>SVN repository is frozen as well as the TRAC system</em>, you should now look at the <strong>Github organization</strong> : <a href="https://github.com/propelorm" target="_blank">https://github.com/propelorm</a>. We are now using <strong>Git </strong>as the main Version Control System and you will find the following projects :</p>
<ul>
<li><strong><a href="https://github.com/propelorm/Propel">Propel</a> </strong>1.6 which became the new foundation for Propel ORM ;</li>
<li><strong><a href="https://github.com/propelorm/sfPropel15Plugin">sfPropel15Plugin</a> </strong>: the symfony 1.x plugin ;</li>
<li><strong><a href="https://github.com/propelorm/PropelBundle">PropelBundle</a> </strong>: the Symfony2 bundle.</li>
</ul>
</div>
<p><em>New issues will have to be reported in through Github</em>, in the appropriate repository. Old tickets will be reviewed but if you expect us to work on your problem, <em>you are encouraged to duplicate your issue in Github tracker</em>. We also expect a lot of Pull Requests from you and we will happily to merge them. And please, don't forget unit tests ;)</p>
<p>The last words will be for François, you made an amazing work on Propel and working day by day with you is always a pleasure.</p>
<p>Oh, one more thing ! We'll organize a meetup in september so stay tuned :)</p>
<p><em>Propel is really back !</em></p>
Propel Has A New Leader2011-08-03T00:00:00+00:00http://www.propelorm.org/blog/2011/08/03/propel-has-a-new-leader<p>A month and a half ago, <a href="http://propel.posterous.com/propel-needs-a-new-leader-do-you-want-to-take">I announced</a> that I wished to pass the lead of the Propel project. Many people inquired about the workload that charge implied, and most of them were frightened by my answer. Between 4 and 16 hours a week, that’s a lot of time, but I suppose that’s what it takes to maintain such a large library as Propel.</p>
<p><span>Propel is a very promising project, and even more now than ever. With <a href="http://propel.posterous.com/propel-16-is-released">version 1.6</a>, Propel gets on par with all other ActiveRecord implementations featurewise, and is still way ahead performancewise. The initial choice of code generation for runtime speed still pays, and adapts well to most development workflows. The lack of migrations kept some developers away from Propel, but with Propel 1.6 they have no more reason not to try it.</span></p>
<p><span>And Propel is also a very viable alternative to DataMapper implementations in PHP – read <a href="http://www.doctrine-project.org/projects/orm/2.0/docs/en">Doctrine2</a> here. Talk with developers actually using Doctrine2, most of them regret the ease of use of the ActiveQuery, the robustness of behaviors, the ability to debug problems in no time, the IDE integration, and the speed you can find in Propel. Doctrine2 is not (yet?) a RAD ORM.<!--more--> </span></p>
<p><span>So there was no reason Propel wouldn’t get a new leader. It would take some courage, a clear vision, and great experience to carry on with the development. It would mean a personal investment of importance. But the library is worth it. And sure enough, one man volunteered that is a good fit for the job. </span></p>
<p><span>His name is William Durand. You may know him as <a href="http://twitter.com/couac">@couac</a> from twitter or <a href="https://github.com/willdurand">@willdurand</a> from GitHub. He is French like me ; actually, we’ve been working 15 meters away from each other for the past months, since I hired him to work on prospective projects at eTF1. He is the developer who created and maintained the <a href="https://github.com/willdurand/PropelBundle">PropelBundle</a>, which is the bridge between Propel and <a href="http://symfony.com/">Symfony2</a>. He also contributes to many other projects, including open-source ones. His understanding of the Propel design, of the open-source philosophy, and of the ORM landscape, would be enough to let him lead the project. But he is also an excellent developer, an extremely fast learner, and someone who is able to share his opinions and convince his coworkers. Let’s stop the introduction there, I’m sure William will soon introduce himself in this very blog.</span></p>
<p><span>From this day on, William is the leader of the Propel project, of the sfPropel15Plugin, in addition to the PropelBundle. He has all the necessary access, and will be your primary contact regarding tickets, milestones, commit right, etc.</span></p>
<p><span>As for me, I’m still leading a team using Propel on a day-to-day basis, with more than 250 Propel models actively running in production across various projects. No doubt that I’ll still be around, suggesting patches in code or documentation. But it will be up to William to commit them to the trunk!</span></p>
<p><span>I am very happy for Propel, which won’t be abandoned at all. I’m also very happy for me – this will give me some time for other activities. And most of all, I’m very happy for William, who deserves a warm welcome from you guys!</span></p>
<p> </p>
Propel needs a new leader: Do you want to take the responsibility for the future of an awesome ORM?2011-06-19T00:00:00+00:00http://www.propelorm.org/blog/2011/06/19/propel-needs-a-new-leader-do-you-want-to-take-the-responsibility-for-the-future-of-an-awesome-orm-<p>For the past two years, I've been the Propel Project lead. It's been an incredibly pleasant experience. Being involved in an open-source project brings a lot of feedback and motivation, but leading one is even better. You get to draw the architecture, meet great people, guide contributors to match your ideas, communicate and make other contributors communicate with each other, give conferences, trainings, advice, etc. And the Propel ORM is not your neighbor's pet project. It's been created six years ago, it's now used in tens of thousand of websites across the world, it covers a wide range of uses, it brings innovative solutions, and it follows professional development methods. Well, if you read this blog, you know what I'm talking about. So leading this particular project brought even more exposure, contributions, expectations, and pressure than with a standard open-source project.</p>
<p>But now, after two years, it's time to step down. Having me as project leader is what prevents Propel from developing even faster, and for a simple reason: I don't have enough time to dedicate to it. I can't qualify all the tickets, answer the proposals for conferences, design the next great features, with only a few hours a week. And to be completely honest, I took over the project two years ago because I didn't want to see it die, not because I wanted to lead an open-source project. But today Propel is not dying anymore, and I want to say "mission accomplished". And I would like to give the beautiful baby to someone that will know better than me how to make it grow even further.</p>
<p>So this is an incredible opportunity for a developer wanting to test - or confirm - his/her abilities at leading an open-source project. </p>
<p>Do you need to know the Propel source code by heart? Of course not. When I took over Propel, I had only a vague understanding of all the cogs that make Propel work. And even today, some of them are still somehow mysterious to me (can someone please explain me how validation works?). That's a big library, but that's also a big community, so there are many people willing to help you fix bugs for a particular platform, a particular use case, or to address a new feature. </p>
<p>Do you need to be a Propel contributor already? Not necessarily. If you can provide examples of your coding style so that I can make sure I'm not passing the jewel to a smuggler, I'm fine with it. Of course, you should favor test-driven development, have a few real-world projects using Propel to test your ideas, and read a lot of code from other libraries to avoid reinventing the wheel.</p>
<p>Do you need to be passionate about Propel? At least a little. This is not something you get paid for. This is not something your girlfriend will encourage you to do. The motivation you need to lead this project has to see with the will to ease the life of your fellow developers, and to build up a kick-ass ActiveRecord implementation in PHP.</p>
<p>If you think you fit the costume, send me an email - you should know my address if you're a subscriber of any of the Propel mailing-lists. As for me, I will continue to fix bugs on the 1.6 branch. My team use Propel on an everyday basis, so they will go on contributing as well. But expect no new feature to come from me. </p>
<p>I'm retiring from Propel: make way for the young!</p>
<p> </p>
Propel 1.6.1 is Released2011-06-14T00:00:00+00:00http://www.propelorm.org/blog/2011/06/14/propel-1-6-1-is-released<p>Propel 1.6.1, the first maintenance release of the 1.6 branch, is now available. Most of the changes fix bugs in migrations, phpDoc comments, and limit cases. You can find the <a href="http://www.propelorm.org/wiki/Documentation/1.6/CHANGELOG">full changelog</a> in the Propel 1.6 documentation. Thanks to all the developers who contributed to this release by opening tickets, providing patches, and trying to find the best solution.</p>
<p>To upgrade, use your favorite distribution:</p>
<h3>Subversion tag</h3>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.6.1</pre></div>
</div>
<h3>Git clone</h3>
<div class="CodeRay">
<div class="code"><pre>> git clone git://github.com/Xosofox/propel.git</pre></div>
</div>
<h3>PEAR</h3>
<div class="CodeRay">
<div class="code"><pre>> sudo pear upgrade propel/propel-generator
> sudo pear upgrade propel/propel-runtime</pre></div>
</div>
<h3>Download</h3>
<ul>
<li><a href="http://files.propelorm.org/propel-1.6.1.tar.gz">http://files.propelorm.org/propel-1.6.1.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.6.1.zip">http://files.propelorm.org/propel-1.6.1.zip</a> (Windows)</li>
</ul>
Propel 1.6 is Released2011-05-09T00:00:00+00:00http://www.propelorm.org/blog/2011/05/09/propel-1-6-is-released<p>Propel 1.6.0 stable is there. It’s full of new features, robust, and still fast as hell. It’s the best Propel ever. If you’ve been waiting for happiness in the ORM world, if you want web development to be fun, and if you prefer to let the computer do all the heavy duty stuff for you, you’ve got to try this new release.</p>
<p>Propel 1.6.0 is backwards compatible with Propel 1.4 and 1.5. It means there is no upgrade costs, just benefits. As a consequence, the 1.5 branch is no longer maintained. Switch to 1.6 to get the latest bug fixes, in addition to the new features!<!--more--></p>
<h3>New Features</h3>
<p>And boy, those new features will ease your life a lot. The <a href="http://www.propelorm.org/wiki/Documentation/1.6/WhatsNew">extensive list</a> is too long to be copied in this blog post, and the <a href="http://www.propelorm.org/wiki/Documentation/1.6">Propel online guide</a> provides extensive documentation about all of theses new features already. So here is a glimpse of what’s new in the 1.6 release:</p>
<ul>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Migrations"><strong>Migrations</strong></a>: No more <code>insert-sql</code> calls that erase your whole database, no more manual <code>ALTER TABLE</code> statements. Propel migrations detect modifications in your XML schemas, and generate the SQL code to migrate the table structure, while preserving existing data. Propel also generates the SQL to rollback the migration in case of problem. Even better, Propel generates a PHP migration class, that you can put under version control, and where you can add data migration code. Migrations are dead easy to use, use vendor-specific SQL, and work even for existing projects.</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Behaviors/versionable"><strong>Versionable Behavior</strong></a>: Did you ever dream of persisting each state of a given object in a database, just like you can do with files using Subversion or Git? Using the new versionable behavior, you can keep an audit log of all the changes to an object, revert to a previous state, get the revision history, and even compare revisions with each other. And what’s unique about the Propel versionable behavior is that it knows how to version related objects, too!</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Behaviors/i18n"><strong>I18n Behavior</strong></a>: One model, several translations, seamlessly, that’s what this behavior provides. Multilingual applications can now adapt the content to the user locale without any boilerplate code. And Propel does it right: IDE integration is taken into account, and the retrieval of i18n objects is optimized to keep a low query count.</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/ActiveRecord#ImportandExportCapabilities"><strong>XML/YAML/JSON/CSV Parsing and Dumping</strong></a>: Propel 1.6 allows you to serialize and unserialize your model objects to and from your favorite format in a single method call. This feature is fully extensible, so the import/export capabilities of your object model are only limited by your imagination. Also, since YAML becomes the default string representation of Propel objects, it’s just as if you had a <a href="http://propel.posterous.com/propel-16-phpsh-awesome-cli-to-your-database">complete Command-Line Interface to your object model persistence</a>!</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/ModelCriteria#CombiningSeveralConditions"><strong>Easier OR in queries</strong></a>: Forget <code>orWhere()</code>, and the inability to use generated filters when a query uses an OR. Propel 1.6 introduces the <code>ModelCriteria::_or()</code> method, and simplifies the writing of queries that used to be more complex in Propel 1.5.</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/BuildConfiguration#DatabaseSettings"><strong>Multiple Buildtime Connections</strong></a>: Projects using more than one database connection used to face a few cumbersome steps during the build phase. Propel 1.6 supports a <code>buildtime-conf.xml</code> configuration file, using the same syntax as the <code>runtime-conf.xml</code> file, to allow several database connections at buildtime. So a project mixing MySQL and Oracle persistences won’t have issues with the generated code anymore.</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Using-SQL-Schemas"><strong>Support For SQL Schemas</strong></a>: At last, Propel supports grouping tables into database schemas in PostgreSQL, MSSQL, and MySQL. Propel also supports foreign keys between tables assigned to two different schemas. For MySQL, where “SQL schema” is a synonym for “database”, this allows for cross-database queries.</li>
<li><a href="http://propel.posterous.com/introducing-virtual-foreign-keys"><strong>Virtual Foreign Keys</strong></a>: Propel models can now share relationships even though the underlying tables aren’t linked by a foreign key. This ability may be of great use when writing Propel code on top of a legacy database.</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Advanced-Column-Types"><strong>Advanced Column Types</strong></a>: In addition to LOB columns, Propel now supports enums, arrays, and value objects as object model properties. The database-agnostic implementation allows these column types to work on all supported RDBMS. And since code generation gives Propel a power that no other ORM has, these new column types are also available as filters in the generated query classes.</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/ModelCriteria#UsingAQueryAsInputForASecondQueryTableSubqueries"><strong>Table Subqueries (a.k.a “Inline Views”)</strong></a>: The new <code>ModelCriteria::addSelectQuery()</code> method makes it easy to use a first query as the source for the SELECT part of a second query. This allows to solve complex cases that a single query can’t solve, or to optimize slow queries with several joins.</li>
<li><a href="http://propel.posterous.com/propel-gets-better-at-naming-things"><strong>Better Pluralizer</strong></a>: Have you ever considered Propel as a lame English speaker? Due to its poor pluralizer, Propel used to be create bad getter method names in one-to-many relationships, especially when dealing with foreign objects named ‘Child’, ‘Category’, ‘Wife’, or ‘Sheep’. Starting with Propel 1.6, Propel adds a new pluralizer class named <code>StandardEnglishPluralizer</code>, which should take care of most of the irregular plural forms of your domain class names.</li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/ActiveRecord"><strong>Active Record Reference Documentation</strong></a>: There wasn’t any one-stop place to read about the abilities of the generated Active Record objects in the Propel documentation. Since Propel 1.6, the new Active Record reference makes it easier to learn the usage of Propel models using code examples.</li>
</ul>
<p>There are a thousand more code changes, smaller additions, backwards compatible tweaks and optimizations. All inspired by usage and real life projects. All designed to simplify your web development. All fully unit tested and already documented, as usual.</p>
<h3>Symfony Integration</h3>
<p>Propel 1.6 can be used right away in a Symfony 1.4 project using the <a href="https://github.com/fzaninotto/sfPropel15Plugin">sfPropel15Plugin</a> (don’t trust the name, it bundles with Propel 1.6), and in a Symfony2 project using the <a href="https://github.com/willdurand/PropelBundle">PropelBundle</a>.</p>
<p>Used together with the Symfony framework, Propel feels even more powerful and easy to use. All the initial configuration and setup phases are taken care of by the framework, and the Symfony Command Line Tools is an improvement on Propel’s native buildtime script.</p>
<h3>Propel 1.6 as a Community Project</h3>
<p>Niklas, tuebernickel, thadin, lvanderree, Crafty_Shadow, ardell, ddalmais, rozwell, oschonrock, Richtermeister, Joss, fonsinchen, paul.hanssen, lathspell, poisson, couac, vmakinen, KRavEN, in no particular order, all contributed code to the 1.6 release. There are many more who tested beta releases, opened tickets on the Propel Trac, or proofread the documentation. And even more who blogged about the new features even though the release wasn’t stable yet.</p>
<p>All these people show how much Propel is a community project. Thanks to all of them for their work. Without their help, Propel 1.6 wouldn’t be there today.</p>
<p>I also want to thank all the brave Propel users who answered questions on the Propel mailing-list and on the #Propel IRC channel. Newcomers and beta testers found a welcoming community, and that’s half of what people ask of a good Open-Source project.</p>
<h3>Download Propel 1.6</h3>
<p>Again, Propel 1.6 is backwards compatible with Propel 1.4 and 1.5. All you need to do to upgrade an existing project is to update your Propel version, and rebuild your project. The Propel library is available in all the usual formats:</p>
<p><strong>Subversion tag</strong></p>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.6.0</pre></div>
</div>
<p><strong>Git clone</strong></p>
<div class="CodeRay">
<div class="code"><pre>> git clone git://github.com/Xosofox/propel.git</pre></div>
</div>
<p><strong>PEAR</strong></p>
<div class="CodeRay">
<div class="code"><pre>> sudo pear upgrade propel/propel_generator
> sudo pear upgrade propel/propel_runtime</pre></div>
</div>
<p><strong>Download</strong></p>
<ul>
<li><a href="http://files.propelorm.org/propel-1.6.0.tar.gz">http://files.propelorm.org/propel-1.6.0.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.6.0.zip">http://files.propelorm.org/propel-1.6.0.zip</a> (Windows)</li>
</ul>
<h3>Speak About It</h3>
<p>Propel still leverages code generation to provide a fast, professional, user- and IDE-friendly ORM to PHP. The Propel project is very much alive, and you’re going to love building applications with the new 1.6 release.</p>
<p>But Propel 1.6 took more than a year to complete. During this time, there was not much to talk about, so Propel needs some publicity. Don’t hesitate to leave your feedback as comments, tickets, or emails to the Propel mailing-lists.</p>
<p>And if you use Propel 1.6 and like it, please share your experience with your friends.</p>
Propel 1.6.0RC2 is released2011-04-29T00:00:00+00:00http://www.propelorm.org/blog/2011/04/29/propel-1-6-0rc2-is-released<p>The Second Release Candidate for the 1.6.0 version has just been released. Get it while its hot!</p>
<h3>Changelog</h3>
<ul>
<li>Fixed diff task combined with skipSQL (closes #1368)</li>
<li>Fixed issue with i18n behavior and classPrefix (closes #1365)</li>
<li>Fixed i18n behavior with namespaces (closes #1359)</li>
<li>Fixed a problem with setting DISTINCT and using LIMIT at the same time with MSSQL server (patch from KRavEN) (closes #825)</li>
<li>Fixed missing namespace in sortable behavior (closes #1358)</li>
<li>Emphasized the expected syntax of pre and post hooks </li>
<li>Fixed namespace.autoPackage when database namespace adds to the table namespace (closes #1357)</li>
<li>Fixed incompatibility between Propel and symfony over sfYaml (closes #1356)</li>
<li>Fixed packaged schemas with different namespaces (based on a patch by couac) (closes #1355)</li>
<li>Fixed PropelOMTask doesn’t read propel.disableIdentifierQuoting from build.properties (patch from vmakinen) (closes #1353)</li>
<li>Fixed versionable behavior when defaultPhpNamingMethod is noChange (patch from niklas) (closes #1343)</li>
<li>Fixed bug in toXML() method stripping numbers from column names (closes #1352)</li>
<li>Slightly improved the soft_delete behavior doc, to emphasize the use of <code>includeDeleted()</code> over <code>disableSoftDelete()</code> (closes #1340)</li>
<li>Fixed full query logging regression (closes #1347)</li>
<li>Fixed typo in exception message thron by ModelCriteria (closes #1349)</li>
<li>Fixed regression in full query logging (closes #1344)</li>
<li>Fixed problem with reverse task and empty foreignSchema attributes</li>
</ul>
<h3>Installing</h3>
<p>As usual, Propel is available in the format you prefer:</p>
<p><em>Subversion tag</em></p>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.6.0RC2</pre></div>
</div>
<p><em>Git clone</em></p>
<div class="CodeRay">
<div class="code"><pre>> git clone git://github.com/Xosofox/propel.git</pre></div>
</div>
<p><em>PEAR</em></p>
<div class="CodeRay">
<div class="code"><pre>> sudo pear config-set preferred_state beta
> sudo pear upgrade propel/propel_generator
> sudo pear upgrade propel/propel_runtime</pre></div>
</div>
<p><em>Download</em></p>
<p><a href="http://files.propelorm.org/propel-1.6.0RC2.tar.gz">http://files.propelorm.org/propel-1.6.0RC2.tar.gz</a> (Linux)</p>
<p><a href="http://files.propelorm.org/propel-1.6.0RC2.zip">http://files.propelorm.org/propel-1.6.0RC2.zip</a> (Windows)</p>
<h3>Next steps</h3>
<p>If no critical bug hits the Propel Trac in the upcoming week, this version will become the 1.6.0 stable. Please test and report any bug you may encounter.</p>
The Propel2 development has begun2011-04-14T00:00:00+00:00http://www.propelorm.org/blog/2011/04/14/the-propel2-development-has-begun<p>Back in September 2010, we <a href="http://propel.posterous.com/propel2-will-be-an-activerecord-implementatio">announced</a> what the future of Propel would be once the 1.6 branch is released.<p /><div>It turns out that 1.6 took a long time to finish, but now that it's <a href="http://propel.posterous.com/propel-160rc1-is-released">coming to an end</a>, I can dedicate some time to laying the foundations of the next major iteration of Propel.</div> <p /><div>As expected, Propel2 is built on top of <a href="http://www.doctrine-project.org/projects/orm/2.0/docs/en">Doctrine2</a>, and uses <a href="http://www.twig-project.org/">Twig</a> for code generation. You can follow the development on <a href="https://github.com/fzaninotto/Propel2">GitHub</a> - we're still at the early stage, but the generator runs and generates working ActiveRecord objects.</div> <p /><div>You're welcome to comment and/or contribute anytime you want. GitHub is a great collaboration platform, so contributing to Propel2 should be even easier than with Propel1.</div><p /><div> We see a great future for the Propel ecosystem!</div></p>
Propel 1.6.0RC1 Is Released2011-04-06T00:00:00+00:00http://www.propelorm.org/blog/2011/04/06/propel-1-6-0rc1-is-released<p>Propel 1.6.0 stable is not far away, for today the first Release Candidate was just published. From now on, no new feature will be added to the 1.6.0 version. And if no major bug appears, this Release Candidate will be the next 1.6.0 stable.</p>
<h3>Changelog</h3>
<p>As compared to the last beta, this release contains mostly bugfixes. A few new features found their way at the last moment:</p>
<ul>
<li>Generated setters and filters now share the same capabilities. For instance, you can now set a boolean column to ‘yes’, or filter a temporal column on the ‘now’ string. The phpDoc blocks of generated setters and filters were rewritten to reflect these changes, so check the generated classes for details. </li>
<li>Propel now supports <a href="http://www.propelorm.org/changeset/2247">Table Subqueries</a>, a.k.a. “Inline Views”, thanks to a patch by lvanderree. </li>
<li>Behaviors can now modify existing methods even if no hook is called in the builders, thanks to a new service class called <code>PropelPHPParser</code>. This class can remove a method, replace a method by another one, or add a new method before or after an existing one. See the <a href="http://www.propelorm.org/wiki/Documentation/1.6/Behaviors#ReplacingorRemovingExistingMethods">Behavior Documentation</a> for details.</li>
<li>The built-in <a href="http://www.propelorm.org/wiki/Documentation/1.6/Runtime-Introspection">Runtime Introspection</a> classes are now a little smarter: <code>TableMap</code> objects have the knowledge of the primary string column.<!--more--></li>
</ul>
<h3>Backwards Compatibility</h3>
<p>As the previous beta, Propel 1.6.0RC1 is backwards compatible with Propel 1.5. Just upgrade Propel, rebuild your model, and you’re good to go.</p>
<p>Propel 1.6.0 is the next 1.5 release, since the 1.5.6 was the last release in the 1.5 branch. So if you want to prepare the next upgrade of your Propel 1.5 application, you should test this release.</p>
<h3>Installing</h3>
<p>As usual, Propel is available in all the formats you can dream of:</p>
<p><em>Subversion tag</em></p>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.6.0RC1</pre></div>
</div>
<p><em>Git clone</em></p>
<div class="CodeRay">
<div class="code"><pre>> git clone git://github.com/Xosofox/propel.git</pre></div>
</div>
<p><em>PEAR</em></p>
<div class="CodeRay">
<div class="code"><pre>> sudo pear config-set preferred_state beta
> sudo pear upgrade propel/propel_generator
> sudo pear upgrade propel/propel_runtime</pre></div>
</div>
<p><em>Download</em></p>
<ul>
<li><a href="http://files.propelorm.org/propel-1.6.0RC1.tar.gz">http://files.propelorm.org/propel-1.6.0RC1.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.6.0RC1.zip">http://files.propelorm.org/propel-1.6.0RC1.zip</a> (Windows)</li>
</ul>
<h3>Send Us Your Feedback</h3>
<p>Please test and report any problem you may encounter with this release. Don’t hesitate to report documentation problems, phpDoc inaccuracies, good or bad surprises you got with that release.</p>
Slides of the Propel presentation at AFUP conference2011-03-31T00:00:00+00:00http://www.propelorm.org/blog/2011/03/31/slides-of-the-propel-presentation-at-afup-conference<p>Yesterday, I had the pleasure to introduce Propel to a panel of French developers invited by <a href="http://www.afup.org/">AFUP</a>, the French PHP User Group.</p>
<p>Here are the slides of the presentation.</p>
<p><a href="http://www.slideshare.net/francoisz/ce-bon-vieux-propel">http://www.slideshare.net/francoisz/ce-bon-vieux-propel</a></p>
<p>The conference ended with a debate with developers comparing the merits of Propel, Doctrine, home-made ORMs and raw NoSQL. It was also a great opportunity to meet in real life people that I know online from the Propel mailing-lists, twitter, or my past life with symfony. Thanks to AFUP for organizing this event!</p>
The End of Autoloading2011-03-21T00:00:00+00:00http://www.propelorm.org/blog/2011/03/21/the-end-of-autoloading<p>Autoloading in PHP is a great time saver. It lets you write concise scripts without the knowledge of the exact directory structure of the libraries you use. But with the arrival of namespaces in PHP 5.3, and the influence of Java over new generation PHP frameworks, autoloading is changing. In the near future, explicit autoloading will be ubiquitous, but with none of the advantages of the old style autoloading.<!--more--></p>
<h3>Before Autoloading, There Were File Paths</h3>
<p>Before autoloading, every class file had to explicitly declare the path to its dependencies. Source code would look like the following, taken from the <a href="http://pear.php.net/">PEAR library</a>:</p>
<div class="CodeRay">
<div class="code"><pre><?php
require_once 'PEAR.php';
require_once 'PEAR/DependencyDB.php';
class PEAR_Registry extends PEAR {
//...
}</pre></div>
</div>
<p>Dependencies appeared clearly at the top of every class. In this code snippet, even if the first use of the <code>PEAR_DependencyDB</code> class is hidden line 328 of the <code>PEAR_Registry</code> class, the dependency is obvious.</p>
<p>In most cases, the path was relative, and the PHP runtime had to rely on the <code>include_path</code> configuration. Performance decreased as the <code>include_path</code> size increased. And the top of many source files was soon littered with <code>require_once</code> calls that harmed readability.</p>
<h3>Then Came SPL Autoloading</h3>
<p><code>require_once</code> was notably slow. On servers without a fast disk or an opcode cache, it was better not to use <code>require_once</code> at all. The PHP SPL library’s <a href="http://php.net/manual/en/function.spl-autoload.php"><code>spl_autoload_register()</code></a> function then came to a great use. It made it possible to remove <code>require_once</code> calls from source code completely. It made applications faster.</p>
<p>But the greatest benefit was that you could use a class without actually knowing where its source file was in the directory structure. Here is an extract from the <a href="http://www.symfony-project.org/tutorial/1_0/en/my-first-project">“My First Project” tutorial</a> for the symfony framework:</p>
<div class="CodeRay">
<div class="code"><pre><?php
class postActions extends sfActions
{
public function executeList()
{
$this->posts = PostPeer::doSelect(new Criteria());
}
}</pre></div>
</div>
<p>Here, no <code>require_once</code> at all, even if this class depends on the <code>sfActions</code>, <code>PostPeer</code>, and <code>Criteria</code> classes. Developers could dive into the business logic right away, without spending a single second figuring out where the dependencies were. This was <a href="http://en.wikipedia.org/wiki/Rapid_application_development">Rapid Application Development</a> at work.</p>
<h3>Autoloading Implementations</h3>
<p>Implementation of the actual autoloading would vary. Some libraries, like the <a href="http://www.propelorm.org/">Propel</a> runtime, included a list of all the classes that could be required, together with the path to the class source. Here is an extract from the <code>Propel</code> class source code:</p>
<div class="CodeRay">
<div class="code"><pre><?php
class Propel
{
// ..
protected static $autoloadMap = array(
'DBAdapter' => 'adapter/DBAdapter.php',
'DBMSSQL' => 'adapter/DBMSSQL.php',
'MssqlPropelPDO' => 'adapter/MSSQL/MssqlPropelPDO.php',
'MssqlDebugPDO' => 'adapter/MSSQL/MssqlDebugPDO.php',
// etc.
}</pre></div>
</div>
<p>This technique allowed to hide the actual class path, but forced the library developer to update the autoload map each time a new class was introduced. Another technique, used in the <a href="http://www.symfony-project.org/gentle-introduction/1_4/en/02-Exploring-Symfony-s-Code#chapter_02_sub_class_autoloading">symfony framework</a>, used a one-time file iterator that browsed the project directory structure, indexing all <code>.class.php</code> files. Despite the performance impact on the first request, this technique removed the burden to keep an autoload map up-to-date, and worked for classes outside the framework as well.</p>
<p>Even better, the symfony autoloading technique allowed to override framework classes with custom ones. The file iterator browsed the directory structure in a certain order: user directories first, then project directories, then plugin directories, and framework directories last. So the developer could create a custom <code>PostPeer</code> class that would override the other <code>PostPeer</code> class provided by a plugin.</p>
<p>Autoloading was at his top: fast, powerful, concise.</p>
<h3>Namespaces Autoloading</h3>
<p>The arrival of <a href="http://www.php.net/manual/en/language.namespaces.rationale.php">namespaces</a> in PHP 5.3 forced autoloading techniques to change. An initiative started by some framework authors tried to allow technical interoperability between libraries and the autoloader implementations. Called the <a href="http://groups.google.com/group/php-standards">“PHP Standards Working Group”</a>, this community agreed that explicit is better than implicit, and that a fully qualified classname would be the relative path to the class source file:</p>
<div class="CodeRay">
<div class="code"><pre>\Doctrine\Common\IsolatedClassLoader
=> /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php
\Symfony\Core\Request
=> /path/to/project/lib/vendor/Symfony/Core/Request.php
\Zend\Acl
=> /path/to/project/lib/vendor/Zend/Acl.php
\Zend\Mail\Message
=> /path/to/project/lib/vendor/Zend/Mail/Message.php</pre></div>
</div>
<p>Libraries agreeing with the initiative should follow the naming and file structure principles, and provide an autoloading implementation compatible with the example <a href="https://gist.github.com/221634"><code>SplClassLoader</code></a> class. This is the case of most “new-generation” frameworks in 2011. For instance, here is an extract of the new <a href="http://symfony.com/doc/2.0/quick_tour/the_big_picture.html">“My First Project” tutorial</a> in Symfony2:</p>
<div class="CodeRay">
<div class="code"><pre><?php
namespace Application\HelloBundle\Controller;
use Symfony\Framework\WebBundle\Controller;
class HelloController extends Controller
{
public function indexAction($name)
{
$author = new \Application\HelloBundle\Model\Author();
$author->setFirstName($name);
$author->save();
return $this->render('HelloBundle:Hello:index', array('name' => $name, 'author' => $author));
}
}</pre></div>
</div>
<p>There is still no <code>require_once</code> in this code - autoloading is at work. The PHP autoloading looks for a <code>Symfony\Framework\WebBundle\Controller</code> class in the <code>Symfony/Framework/WebBundle/Controller.php</code> file. The file path is no longer relative to the <code>include_path</code>, since the autoloader must be initialized with the base path to the library directory.</p>
<p>A first advantage is that there is no more “first request” penalty on performance. Also, dependencies are explicit again. Lastly, if you want to override a class provided by the framework, alias a custom class using a <code>use</code> and you’re ready to go:</p>
<div class="CodeRay">
<div class="code"><pre><?php
namespace Application\HelloBundle\Controller;
// use a custom Controller class instead of the framework's Controller
use Application\HelloBundle\Tools\Controller;
class HelloController extends Controller
{
// same code as before
}</pre></div>
</div>
<h3>This Is No Longer Rapid Application Development</h3>
<p>Doesn’t the initial <code>use</code> in the previous example remind you of something? Right, it’s very similar to the <code>require_once</code> calls of the first example without autoloading:</p>
<div class="CodeRay">
<div class="code"><pre><?php
// old style
require_once 'Application/HelloBundle/Tools/Controller.php';
// new style
use 'Application\HelloBundle\Tools\Controller';</pre></div>
</div>
<p>The added verbosity of the namespace autoloading reduces the ease of use introduced by SPL autoloading in the first place.</p>
<p>The problem is not only about having to write more code. Consider the work to do to use a class from a “new generation” framework:</p>
<ol>
<li>Parse the framework directory structure, looking for the source file of the class to use</li>
<li>Open the source file, and copy the <code>namespace</code> declaration</li>
<li>Paste the namespace declaration inside a <code>use</code> statement in the custom code.</li>
</ol>
<p>This copy/paste task happens a lot when working with Symfony2, for instance. This can be somehow improved when you use an IDE with code completion, but you still have to know the fully qualified name of the classes you need. You must know the framework classes by heart to be able to use them. That’s a step backwards in terms of usability when compared to first-generation autoloading, where only knowing the class name was enough.</p>
<h3>There Is No Better Way In PHP</h3>
<p>Wouldn’t it be great if you could use new generation framework code without knowing where the required dependencies lay on the filesystem? What if you could write a Symfony2 controller like this:</p>
<div class="CodeRay">
<div class="code"><pre><?php
class HelloController extends Controller
{
public function indexAction($name)
{
$author = new Author();
$author->setFirstName($name);
$author->save();
return $this->render('HelloBundle:Hello:index', array('name' => $name, 'author' => $author));
}
}</pre></div>
</div>
<p>A smart autoloader could catch the call for the <code>Controller</code> class, open a default implementation (in <code>Symfony/Framework/WebBundle/Controller.php</code>), and dynamically alias <code>Symfony\Framework\WebBundle\Controller</code> to <code>Controller</code>. Except that in PHP, <code>use</code> creates aliases at compile time, so that doesn’t work. There is a possibility to implement such an autoloader using <code>eval()</code>, but that’s probably worst than requiring files by hand.</p>
<p>Also, aliasing all classes in a usability layer on top of the framework isn’t possible either. It would defeat the lazy loading of core classes, and fail on duplicate class names (e.g. <code>Symfony\Framework\WebBundle\Command</code> and <code>Symfony\Components\Console\Command\Command</code>).</p>
<p>Unless framework authors change their mind on autoloading, the future of PHP will be verbose.</p>
<h3>Solving The Problem</h3>
<p>I personally think that the added verbosity slows down the development a lot. Take <a href="http://johnsonpage.org/more/php-microframeworks">microframeworks</a> for instance: they give you a way to answer an http request in a fast way but with minimum MVC separation. Compare the code for a “Hello, world” application written using <a href="http://www.slimframework.com/">Slim</a>, a microframework without namespace autoloading, and <a href="http://github.com/fabpot/silex/">Silex</a>, a microframework using namespace autoloading:</p>
<div class="CodeRay">
<div class="code"><pre><?php
// Hello world with Slim
require_once 'slim/Slim.php';
Slim::init();
Slim::get('/hello/:name', function($name) {
Slim::render('hello.php', array('name' => $name));
});
Slim::run();
// Hello world with Silex
require_once 'silex.phar';
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\Engine;
use Symfony\Component\Templating\Loader\FilesystemLoader;
use Silex\Framework;
$framework = new Framework(array(
'GET /hello/:name' => function($name) {
$loader = new FilesystemLoader('views/%name%.php');
$view = new Engine($loader);
return new Response($view->render(
'hello',
array('name' => $name)
));
}
));
$framework->handle()->send();</pre></div>
</div>
<p>In the second example, autoloading comes in the way, and makes things harder.</p>
<p>Developers of new generation frameworks explain that the added verbosity is the price to pay for a better quality code. I’m not sure I’m willing to pay this price. I don’t like to see PHP as the next Java, where the code is great from a CS graduate point of view, but very expensive to write. It makes me want to switch to other languages, where this namespace autoloading discussion never took place, and where rapid application development is still possible.</p>
<p>Take Ruby for instance. It offers a microframework called <a href="http://www.sinatrarb.com">Sinatra</a>, which makes the “Hello, world” application really concise:</p>
<div class="CodeRay">
<div class="code"><pre>require 'sinatra'
require 'erb'
get '/hello/:name' do |name|
@name = name
erb :hello
end</pre></div>
</div>
<p>Oh, look, there are <code>require</code> statements in this script. And yet, it’s so fast and easy to use.</p>
We’re Hiring PHP5 Developers2011-03-18T00:00:00+00:00http://www.propelorm.org/blog/2011/03/18/we-re-hiring-php5-developers<p><span style="">In addition to being the Propel lead developer, I’m the CTO of e-TF1, and proud of the work of my development team. They are fast, they are efficient, and they deliver on time.</span></p>
<p><span style="">At e-TF1, we develop and maintain the French leading Media website, <a href="http://www.tf1.fr/">TF1.fr</a> (in 2010, TF1 was the 7<sup>th</sup> group in the French web, and the first Media group according to Nielsen NetRatings). We also edit a lot of other web properties (<a href="http://www.tf1vision.com/">TF1Vision.com</a>, <a href="http://www.plurielles.fr/">Plurielles.fr</a>, <a href="http://www.excessif.com/">Excessif.com</a>, <a href="http://www.psg.fr">PSG.fr</a>, <a href="http://www.om.net">OM.net</a>, etc.). We don’t just do web, though; e-TF1 also develops IPTV services (like <a href="http://www.01net.com/editorial/530254/la-television-de-rattrapage-de-tf1-disponible-sur-la-freebox/">MyTF1</a>, just released for the French ISP Free in addition to Orange and Bouygues Telecom), Mobile apps (the <a href="http://itunes.apple.com/fr/app/tf1/id407248490">TF1.fr</a> iPhone/iPad app, developed in-house, has been in the French top 25 apps since it’s been released), and ConnectTV apps.</span></p>
<p><span style="">Our job is to keep up with the constant evolution of the Media world. News TV shows, new ideas, new web opportunities, new video formats, skyrocketing traffic, TV and mobile devices changing every other month: that’s our day-to-day work. We always have to deliver on time with short notice – without sacrificing on the quality. Most our projects last a couple months at maximum, in small teams, and must support millions of users from day one. “Agile” flows in our veins, “teamwork” is written in our genes, and quality is not negotiable. We’re passionate. </span></p>
<p><span style="">All that work relies on technology. As you might expect, we’re strong fans of Open Source Software – <a href="http://www.propelorm.org">Propel </a>is our contribution back to the community for all the goodness we got from PHP, Symfony, Zend Framework, PHPUnit, PEAR, Apache, MySQL, MongoDB, NodeJS, Capistrano, Redmine, Jenkins, and the likes. We encourage innovation, and we emphasize on the fun. Our developers are not writing code: they’re building products. </span></p>
<p><span style="">Today, we’re looking to expand our internal development team. We are not looking for magicians. We are not looking for contractors. We are looking for technology enthusiasts, with at least a couple years of experience in PHP5, and a strong taste for good code. Autonomous and innovative, you’re aware of the state-of-the art coding techniques. Our team speaks French and English, and is located in Boulogne-Billancourt, close to Paris, France – that’s where the position is, we need proximity to be reactive enough. The salary is good – as good as you are.</span></p>
<p><span style="">If you are looking for the best place to work in France as a PHP developer, stop by and <a href="http://tf1.profils.org/offre-de-emploi/emploi-developpeur-se-h-f_3374.aspx">send us your CV</a>. You won’t be disappointed.</span></p>
<p> </p>
Propel 1.6.0 Beta 2 Released2011-03-11T00:00:00+00:00http://www.propelorm.org/blog/2011/03/11/propel-1-6-0-beta-2-released<p>The Propel team has the pleasure to announce the immediate availability of the second and last beta release of the 1.6 branch. This is mostly a bugfix release over the beta 1 released a <a href="http://propel.posterous.com/get-ready-for-propel-16-the-beta-1-is-release">month and a half ago</a>, but it also adds a few features:</p>
<ul>
<li><code>ModelCriteria::_or()</code>, a <a href="http://www.propelorm.org/wiki/Documentation/1.6/WhatsNew#EasierORinQueries">new and better way to add OR to your conditions</a></li>
<li>Virtual Foreign Keys, a.k.a <a href="http://www.propelorm.org/wiki/Documentation/1.6/WhatsNew#Model-OnlyRelationships">Model-Only Relationships</a></li>
<li>Support for <a href="http://www.propelorm.org/wiki/Documentation/1.6/Schema#OracleVendorInfo">Oracle Tablespaces</a> in schemas</li>
<li>A <a href="http://www.propelorm.org/ticket/1286">service class</a> allowing behaviors to replace existing methods in generated classes</li>
<li>A <a href="http://www.propelorm.org/wiki/Documentation/1.6/Writing-Behavior#SpecifyingaPriorityForBehaviorExecution">priority system for behaviors</a>, to solve behavior conflicts</li>
</ul>
<p>As the previous beta, Propel 1.6.0 Beta 2 is backwards compatible with Propel 1.5. Just upgrade Propel, rebuild your model, and you're good to go.</p>
<h3>Subversion tag</h3>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.6.0BETA2</pre></div>
</div>
<h3>PEAR</h3>
<div class="CodeRay">
<div class="code"><pre>> sudo pear config-set preferred_state beta
> sudo pear upgrade propel/propel_generator
> sudo pear upgrade propel/propel_runtime</pre></div>
</div>
<h3>Download</h3>
<ul>
<li><a href="http://files.propelorm.org/propel-1.6.0BETA2.tar.gz">http://files.propelorm.org/propel-1.6.0BETA2.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.6.0BETA2.zip">http://files.propelorm.org/propel-1.6.0BETA2.zip</a> (Windows)</li>
</ul>
<p>Don’t hesitate to send us feedback on the developers mailing-list, or by opening tickets on the Propel Trac. Since this is the last beta, it is also your last chance to contribute to the 1.6 branch if you want to add a feature. We want to release a RC soon, so don't wait.</p>
Paris Meetup about PHP ORMs on March 30th - Come and See Propel 1.62011-03-10T00:00:00+00:00http://www.propelorm.org/blog/2011/03/10/paris-meetup-about-php-orms-on-march-30th-come-and-see-propel-1-6<p>I'll be introducing Propel 1.6 during a PHP meetup in Paris organized by the French <a href="http://www.afup.org/pages/site/">AFUP</a> in two weeks time.</p>
<p>The conference is about <a href="http://www.afup.org/pages/site/?route=rendez-vous-de-l-afup/443/acces-aux-bases-de-donnees-relationnelles-et-orm-en-php">Database access in PHP (DBALs and ORMs)</a>, and will be in French. I'll briefly showcase the new additions of Propel 1.6, and what makes Propel a good fit for most projects. It's a good occasion to get a comparative view on DBAL and ORM solutions (a Doctrine2 presentation is also scheduled). And if you're still not using an ORM, it's the best time to get all the information you need.</p>
<p>Come and meet us on March 30th, 7:30pm at La Cantine - and don't forget to register first on the <a href="http://www.afup.org/pages/site/?route=rendez-vous-de-l-afup/443/acces-aux-bases-de-donnees-relationnelles-et-orm-en-php">afup.org</a> website.</p>
Don't Copy Code. Oh, and Inheritance and Composition are Bad, Too2011-03-03T00:00:00+00:00http://www.propelorm.org/blog/2011/03/03/don-t-copy-code-oh-and-inheritance-and-composition-are-bad-too<p>I often see, inside the code produced by some of our junior devs, entire blocks of code copied from one class to another. I'm always shocked by this very bad practice. But it's hard to get these developers back into the right path, because there is no perfect alternative.</p>
<h3>Don't Copy Code</h3>
<p>Ctrl+C and Ctrl+V are probably my favorite computer tools, just after email. That's a great invention and an incredible time saver. The problem is that it should never, ever be used by a developer. It's like giving food to a <a href="http://www.youtube.com/watch?v=h24CFZqSEAA">Gremlin</a>: no matter if you can do it without a second thought during daytime, you mustn't do it after midnight. That's the rule.</p>
<p>Why is it bad to copy code? Because each occurrence of the code will need to be tested and maintained separately. It multiplies the costs by the number of times Ctrl+V was hit. Plus it is a source of confusion and of a false security feeling. You find a bug, you fix it once, and you think you're done. Except that piece of code was copied 17 times across the application, but you won't remember it until the bug reappears all of a sudden.</p>
<p>So there should be an alarm bell ringing in your mind every time you see something like this:<!--more--></p>
<div class="CodeRay">
<div class="code"><pre>class Book
{
// some methods
public function persist()
{
apc_store(get_class($this) . $this->id, serialize($this));
}
public static function restore($id)
{
return unserialize(apc_fetch(get_class($this) . $id));
}
}
class Author
{
// some methods
public function persist()
{
apc_store(get_class($this) . $this->id, serialize($this));
}
public static function restore($id)
{
return unserialize(apc_fetch(get_class($this) . $id));
}
}</pre></div>
</div>
<p>Obviously, in this case the developer needed to give the same ability to two classes: the ability to be persisted in the APC cache, and then restored. And since it was done once in the <code>Book</code> class, the easiest way to give the same ability to the <code>Author</code> class was to copy the code. Right? Wrong.</p>
<p><a href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">Don't Repeat Yourself</a>. I won't write it twice.</p>
<p><strong>Tip</strong>: If you want to check if a PHP application contains some duplicated code blocks, I recommend <a href="https://github.com/sebastianbergmann/phpcpd"><code>phpcd</code></a>, a Copy/Paste detector library by Sebastian Bergmann. It's dead simple to install, and very efficient.</p>
<h3>Don't Use Inheritance for Horizontal Code Reuse</h3>
<p>Junior devs quickly understand their mistake once they fix a bug duplicated 17 times. And since they learned Object-Oriented Programming, they often turn up to Inheritance as a great pattern to avoid code duplication. Therefore, the previous piece of code ends up looking like:</p>
<div class="CodeRay">
<div class="code"><pre>class Persistable
{
public function persist()
{
apc_store(get_class($this) . $this->id, serialize($this));
}
public static function restore($id)
{
return unserialize(apc_fetch(get_class($this) . $id));
}
}
class Book extends Persistable
{
// some methods
}
class Author extends Persistable
{
// some methods
}</pre></div>
</div>
<p>That's much better: there is no more code duplication. A bug in one of the <code>Persistable</code> methods only needs to be fixed once, and this behavior class can be further reused with only two words (<code>extends Persistable</code>).</p>
<p>Except that's an abuse of the inheritance concept. From the business point of view, a <code>Book</code> <em>is not</em> a <code>Persistable</code>. It's a <code>Publication</code>, or a <code>StoreItem</code>. It <em>has</em> a persistable ability, but that's not the same verb. In fact, the <code>Book</code> class needs to reuse an ability that is not specific to its parent. That's called "horizontal code reuse", as opposed to the "vertical code reuse" of inheritance.</p>
<p>And to better distinguish the two types of reuse, a good rule of thumb is that a class often needs several horizontal reuses, while it can only have at most one vertical reuse. For instance, the <code>Book</code> class needs to be persistable, but it also needs to be sellable (so it must have a price), storable (so it must have a stock quantity), etc. But inheritance only accepts one parent (at least in PHP), so you'll have to choose one.</p>
<p>There is a pattern here: every time you create a class with a name ending with '-able', that's an ability, or "behavior" class, and that's a class that you should not extend. Or you won't be able to extend anything else. And a class isn't mostly distinguished by what it <em>can do</em>, but by what it <em>is</em>.</p>
<h3>Composition to The Rescue</h3>
<p>In PHP, a good workaround for taking the ability of multiple parent objects is <em>composition</em>. Transform a "behavior" class into a "service" class, and inject that class to all the classes that need it. Now that's clean:</p>
<div class="CodeRay">
<div class="code"><pre>class PersistenceService
{
public function persist($object, $class)
{
apc_store($class . $object->id, serialize($object));
}
public static function restore($id, $class)
{
return unserialize(apc_fetch($class . $id));
}
}
class Book
{
// some methods
protected $persistence;
public function setPersistence(PersistenceService $persistence)
{
$this->persistence = $persistence;
}
public function persist()
{
$this->persistence->persist($this, get_class($this));
}
public static function restore($id)
{
return $this->persistence::restore($id, get_class($this));
}
}
class Author
{
// some methods
protected $persistence;
public function setPersistence(PersistenceService $persistence)
{
$this->persistence = $persistence;
}
public function persist()
{
$this->persistence->persist($this, get_class($this));
}
public static function restore($id)
{
return $this->persistence::restore($id, get_class($this));
}
}</pre></div>
</div>
<p>All the persistence logic is now encapsulated into one unique class. It's testable, it's isolated, and it's easy to reuse. Cherry on the cake, the <code>Book</code> and <code>Author</code> classes are Plain Old PHP Objects (or POPO) again, meaning they don't extend anything. Unit test gurus advocate that everything should be POPO, and despise inheritance, so that is probably a good thing.</p>
<p>Notice that the end user isn't allowed to chain method calls and has no knowledge of the actual persistence layer. She manipulates the <code>Book::persist()</code> and the <code>Book::restore()</code> proxy methods, and there is no public way to access the persistence layer. That's following the "Principle of Least Knowledge", also called <a href="http://en.wikipedia.org/wiki/Law_of_Demeter">"Law of Demeter"</a>.</p>
<p>You may complain that the <code>Book</code> and <code>Author</code> classes are now bigger than in the first code snippet, and that they show a slight code duplication. That's a valid complaint, that I will address shortly. But before that, let me make things a little bit more complex.</p>
<h3>Manage Dependencies with a Dependency Injection Container</h3>
<p>So inheritance was replaced by composition, that's fine. If the <code>Book</code> class needs to reuse code from several service classes, then you'll add new service setters, and new proxy methods for the service abilities.</p>
<p>But the above code doesn't work out of the box: the service class must be initialized. Before, you could just write:</p>
<div class="CodeRay">
<div class="code"><pre>$book = new Book();
$book->persist();</pre></div>
</div>
<p>Now you must write:</p>
<div class="CodeRay">
<div class="code"><pre>$book = new Book();
$book->setPersistence(new PersistenceService());
$book->persist();</pre></div>
</div>
<p>Besides, you may not want to duplicate the <code>PersistenceService</code> class, because it may be initialized with some configuration settings, or because it's expensive in terms of memory consumption. So you should rather keep a service container somewhere with access to public services:</p>
<div class="CodeRay">
<div class="code"><pre>// $sc is a service container
$book = new Book();
$book->setPersistence($sc->getService('PersistenceService'));
$book->persist();</pre></div>
</div>
<p>There is a smarter way to do this - and to avoid the pitfall of creating a registry class, often plagued by the <code>static</code> keyword. A <a href="http://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a> Container (or DIC) lazy-loads services on demand, manages dependencies, and allows to subclass a service easily. There are a few good implementations of DIC in PHP, I recommend the one from the Symfony Components library, available <a href="https://github.com/symfony/DependencyInjection">in PHP 5.3</a> or in <a href="http://components.symfony-project.org/dependency-injection/">PHP 5.2</a>.</p>
<p>With a DIC, you can manage services into a configuration file. This file is then compiled into a PHP class for a faster execution.</p>
<div class="CodeRay">
<div class="code"><pre><?xml version="1.0" ?>
<container xmlns="http://symfony-project.org/2.0/container">
<services>
<service id="persistence" class="PersistenceService" shared="false">
</service>
</services>
</container></pre></div>
</div>
<h3>Code Generation: A Good Alternative</h3>
<p>If you're from a PHP background and not from a Java background, you may be overwhelmed by the complexity of a dependency injection container. I tend to agree - seeing that classes like <code>Compiler\ResolveDefinitionTemplatesPass</code> or <code>ContainerAwareInterface</code> are necessary for simple horizontal code reuse feels like using a sledgehammer to crack a nut.</p>
<p>And yet the classes using the services need a lot of duplicated code written by hand, just for the composition and interfaces to the injected services. In addition, the service class may execute expensive code to adapt the service to the class it's used in. For instance, a better persistence service class should check the class of the object passed to the <code>persist()</code> method instead of requiring it as an argument. That's not an expensive execution, but in the real world this kind of runtime introspection really penalizes performance.</p>
<p>One important thing to notice is that the complexity behind a DIC implies a compilation pass, which generates PHP code for a better runtime performance.</p>
<p>Since code generation is used, why not extend it to directly add the necessary code to the end class? What if you could just generate the following code?</p>
<div class="CodeRay">
<div class="code"><pre>class Book
{
// some methods
public function persist()
{
apc_store(get_class($this) . $this->id, serialize($this));
}
public static function restore($id)
{
return unserialize(apc_fetch(get_class($this) . $id));
}
}
class Author
{
// some methods
public function persist()
{
apc_store(get_class($this) . $this->id, serialize($this));
}
public static function restore($id)
{
return unserialize(apc_fetch(get_class($this) . $id));
}
}</pre></div>
</div>
<p>Yep, that's exactly the same code as in the first snippet. But if it's generated, code duplication isn't a bad thing anymore. There is still a central place where the code lies - in the generator. The public interface is mostly the same as in the DIC version. The service doesn't need to be injected - it's "baked in" by code generation.</p>
<p>And it's blazingly fast. No runtime introspection, no need to instantiate multiple service objects.</p>
<p>And it offers IDE completion for "composed" services.</p>
<p>Propel uses code generation to offer a fast and configurable way to do horizontal code reuse. Just like with a DIC, you need a few lines of configuration to enable a <a href="http://www.propelorm.org/wiki/Documentation/1.5/Behaviors">behavior</a> on a class:</p>
<div class="CodeRay">
<div class="code"><pre><table phpName="Book" name="book" >
<behavior name="persistable" />
</table></pre></div>
</div>
<p>Then, Propel generates all the necessary code in a 'Base' class that you only need to extend. That's right, horizontal reuse through inheritance becomes possible again once you use code generation. And it doesn't prevent <a href="http://www.propelorm.org/wiki/Documentation/1.5/Inheritance">true model inheritance</a> - Propel supports it out of the box.</p>
<p>Since you'll probably be using code generation to manage horizontal code reuse, use it for good. Skip the DIC and generate the code directly in the final classes.</p>
<h3>Mixins: The Only Good Solution</h3>
<p>Dependency injection and code generation are just workarounds for a limit of the PHP language. PHP doesn't offer a native way to handle horizontal code reuse. Other languages, like Ruby, support the concept of "<a href="http://en.wikipedia.org/wiki/Mixin">Mixin</a>", which offers some kind of multiple inheritance. Here is an example taken from the <a href="http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html">Ruby documentation</a>:</p>
<div class="CodeRay">
<div class="code"><pre>module Debug
def whoAmI?
"#{self.type.name} (\##{self.id}): #{self.to_s}"
end
end
class Phonograph
include Debug
# ...
end
class EightTrack
include Debug
# ...
end</pre></div>
</div>
<p>The <code>Phonograph</code> and <code>EightTrack</code> classes can reuse the code from the <code>Debug</code> behavior without extending it. That completely removes the need for code duplication, and for all the workaround that were exposed in this article.</p>
<p>Fortunately, PHP will soon have some sort of Mixin support - only they're called <a href="http://wiki.php.net/rfc/horizontalreuse#traits_-_reuse_of_behavior_committed_to_trunk">"Traits"</a>. Traits would allow the <code>Book</code> and <code>Author</code> to get the "persistable" behavior in a clean way:</p>
<div class="CodeRay">
<div class="code"><pre>trait Persistable
{
public function persist()
{
apc_store(get_class($this) . $this->id, serialize($this));
}
public static function restore($id)
{
return unserialize(apc_fetch(get_class($this) . $id));
}
}
class Book
{
use Persistable;
// some methods
}
class Author
{
use Persistable;
// some methods
}</pre></div>
</div>
<p>Notice the return of the "-able" suffix in this example. Classes are getting access to a behavior, not a service.</p>
<h3>Conclusion</h3>
<p>Traits are already implemented in the development version of PHP (probably called PHP 5.4). Until this version is released (no date for now), you're stuck with just workarounds. Choose the one that better fits your needs, and don't get too attached to it. Dependency Injection Containers and Code Generators will soon become overkill once you can do mixins in PHP.</p>
<p>But one thing will always remain valid: You do no copy code. This is the First Rule of the Developer. And do you know what the Second Rule is?</p>
Using OR In Propel Queries Becomes Much Easier With Propel 1.62011-02-21T00:00:00+00:00http://www.propelorm.org/blog/2011/02/21/using-or-in-propel-queries-becomes-much-easier-with-propel-1-6<p>Combining two generated filters with a logical <code>OR</code> used to be impossible in Propel - the alternative was to use <code>orWhere()</code> or <code>combine()</code>, but that meant losing all the smart defaults of generated filters.</p>
<p>Propel 1.6 introduces a new method for Query objects: <code>_or()</code>. It just specifies that the next condition will be combined with a logical <code>OR</code> rather than an <code>AND</code>.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="c1">// Basic usage: _or() as a drop-in replacement for orWhere()</span>
<span class="nv">$books</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Book.Title = ?'</span><span class="p">,</span> <span class="s1">'War And Peace'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">_or</span><span class="p">()</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Book.Title LIKE ?'</span><span class="p">,</span> <span class="s1">'War%'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="c1">// SELECT * FROM book</span>
<span class="c1">// WHERE book.TITLE = 'War And Peace' OR book.TITLE LIKE 'War%'</span>
<span class="c1">// _or() also works on generated filters:</span>
<span class="nv">$books</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByTitle</span><span class="p">(</span><span class="s1">'War And Peace'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">_or</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByTitle</span><span class="p">(</span><span class="s1">'War%'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="c1">// SELECT * FROM book</span>
<span class="c1">// WHERE book.TITLE = 'War And Peace' OR book.TITLE LIKE 'War%'</span>
<span class="c1">// _or() also works on embedded queries</span>
<span class="nv">$books</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByTitle</span><span class="p">(</span><span class="s1">'War and Peace'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">_or</span><span class="p">()</span>
<span class="o">-></span><span class="nf">useAuthorQuery</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByName</span><span class="p">(</span><span class="s1">'Leo Tolstoi'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">endUse</span><span class="p">()</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="c1">// SELECT book.* from book</span>
<span class="c1">// INNER JOIN author ON book.AUTHOR_ID = author.ID</span>
<span class="c1">// WHERE book.TITLE = 'War and Peace' // OR author.NAME = 'Leo Tolstoi'</span>
</code></pre></div></div>
<p>This new method is implemented in the <code>Criteria</code> class, so it also works for the old-style queries. And since <code>ModelCriteria::orWhere()</code> is a synonym for <code>->_or()->where()</code>, it is now deprecated.</p>
Introducing Virtual Foreign Keys2011-02-16T00:00:00+00:00http://www.propelorm.org/blog/2011/02/16/introducing-virtual-foreign-keys<p>Starting with version 1.6, Propel models can now share relationships even though the underlying tables aren't linked by a foreign key. This ability may be of great use when using Propel on top of a legacy database.</p>
<p>For example, a <code>review</code> table designed for a MyISAM database engine is linked to a <code>book</code> table by a simple <code>book_id</code> column:</p>
<div class="CodeRay">
<div class="code"><pre><table name="review">
<column name="review_id" type="INTEGER" primaryKey="true" required="true"/>
<column name="reviewer" type="VARCHAR" size="50" required="true"/>
<column name="book_id" required="true" type="INTEGER"/>
</table></pre></div>
</div>
<p>To enable a model-only relationship, add a <code><foreign-key></code> tag using the <code>skipSql</code> attribute, as follows:</p>
<div class="CodeRay">
<div class="code"><pre><table name="review">
<column name="review_id" type="INTEGER" primaryKey="true" required="true"/>
<column name="reviewer" type="VARCHAR" size="50" required="true"/>
<column name="book_id" required="true" type="INTEGER"/>
<!-- Model-only relationship -->
<foreign-key foreignTable="book" onDelete="CASCADE" skipSql="true">
<reference local="book_id" foreign="id"/>
</foreign-key>
</table></pre></div>
</div>
<p>Such a foreign key is not translated into SQL when Propel builds the table creation or table migration code. It can be seen as a "virtual foreign key". However, on the PHP side, the <code>Book</code> model actually has a one-to-many relationship with the <code>Review</code> model. The generated ActiveRecord and ActiveQuery classes take advantage of this relationship to offer smart getters and filters.</p>
How Can I Write This Query Using An ORM?2011-02-02T00:00:00+00:00http://www.propelorm.org/blog/2011/02/02/how-can-i-write-this-query-using-an-orm-<p>The Propel mailing lists often shows that typical question: How can I write this complicated query using the new Query syntax? The answer is not as simple as referring to the right section in the extensive <a href="http://www.propelorm.org/wiki/Documentation/1.5/ModelCriteria">Query documentation</a>, because most of the times the Query object is not the solution. And actually, the true answer is complicated, because it implies a deep understanding of the Object Relational Mapper approach. Let’s see through a few examples how various answers can lead to a better usage of ORMs.<!--more--></p>
<h3>Answer #1: You Don’t Need An ORM</h3>
<p>A recent post on the <a href="https://groups.google.com/forum/#!topic/propel-users/aZxqr48pnV4">propel-users</a> mailing list asked for the Propel version of the following query:</p>
<div class="CodeRay">
<div class="code"><pre>SELECT COUNT(t1.user) AS users, t1.choice AS lft, t2.choice AS rgt
FROM Choices t1 iNNER JOIN Choices t2 ON (t1.user = t2.user)
WHERE t1.choice IN (...) AND t2.choice IN (...)
GROUP BY t1.choice, t2.choice;</pre></div>
</div>
<p>This query is not object-oriented, it’s purely relational, so it doesn’t need an Object-Relational Mapping. The best way to execute this query inside an ORM is to skip the ORM and use PDO directly:</p>
<div class="CodeRay">
<div class="code"><pre><?php
$con = Propel::getConnection();
$query = 'SELECT COUNT(t1.user) AS users, t1.choice AS lft, t2.choice AS rgt
FROM choice t1 iNNER JOIN choice t2 ON (t1.user = t2.user)
WHERE t1.choice IN (?, ?) AND t2.choice IN (?, ?)
GROUP BY t1.choice, t2.choice';
$stmt = $con->prepare($query);
$stmt->bindValue(1, 'foo');
$stmt->bindValue(2, 'bar');
$stmt->bindValue(3, 'baz');
$stmt->bindValue(4, 'foz');
$res = $stmt->execute();</pre></div>
</div>
<p>Hints of a purely relational query are:</p>
<ul>
<li>The SELECT part cherry-picks some columns of the main table</li>
<li>The SELECT part aggregates data from several tables</li>
<li>The selected columns use vendor-specific SQL functions</li>
<li>The query joins tables through columns that don’t share a foreign key</li>
<li>The query is long and makes several joins</li>
<li>The query uses GROUP BY or HAVING</li>
<li>The user posts the query, but has no idea of the corresponding object model</li>
</ul>
<p>That’s the most common answer to the “How Can I Write…” question. It is not a bad thing to resort to a direct database query inside a project using an ORM when it’s the right tool for the job. If Propel makes the code much more complex to write, not reusable, or painfully slow, then don’t use it. Be pragmatic.</p>
<h3>Answer #2: You Don’t Need a Query Object</h3>
<p>Some queries appear closer to the object-oriented world but still very complex. The WHERE and JOIN parts still look very long, there may even be a subselect, but the user selects all the columns of the main table, and expects ActiveRecord objects as a result. For instance:</p>
<div class="CodeRay">
<div class="code"><pre>// find all the books not reviewed by :name
SELECT * FROM book
WHERE id NOT IN (SELECT book_review.book_id FROM book_review
INNER JOIN author ON (book_review.author_id=author.ID)
WHERE author.last_name = :name);</pre></div>
</div>
<p>Crafting this query using Propel’s Query objects (whether <code>Criteria</code> or <code>ModelCriteria</code>) would take a long time, or might even be close to impossible. But the query is already there, so why use the Query objects? To get Model objects as a result? You don’t need Query objects for that, just use a <a href="http://www.propelorm.org/wiki/Documentation/1.5/BasicCRUD#UsingCustomSQL">formatter object</a>:</p>
<div class="CodeRay">
<div class="code"><pre><?php
// prepare and execute an arbitrary SQL statement
$con = Propel::getConnection(BookPeer::DATABASE_NAME);
$sql = "SELECT * FROM book WHERE id NOT IN "
."(SELECT book_review.book_id FROM book_review"
." INNER JOIN author ON (book_review.author_id=author.ID)"
." WHERE author.last_name = :name)";
$stmt = $con->prepare($sql);
$stmt->execute(array(':name' => 'Austen'));
// hydrate Book objects with the result
$formatter = new PropelObjectFormatter();
$formatter->setClass('Book');
$books = $formatter->format($stmt);</pre></div>
</div>
<p>Once again, if you already have a working query and if there is no possible reuse, PDO can be the right tool for the job. Propel can hydrate Model objects based on a PDO resultset, so all you need is a <code>PropelObjectFormatter</code>. Yan can even hydrate objects from several tables in a row (in a similar fashion to what Propel does with <code>with()</code>) using a properly configured Formatter object.</p>
<p>That’s closer to the ORM philosophy, because you eventually deal with objects. But the Query itself is everything but object-oriented.</p>
<h3>Answer #3: You don’t Need A Full Query</h3>
<p>Sometimes the query is just long, and users find it tedious to use Query methods instead of plain SQL. The problem often reveals a bad usage of the Query objects prior to that. For instance, consider the following query:</p>
<div class="CodeRay">
<div class="code"><pre>SELECT * FROM book
LEFT JOIN author ON (book.AUTHOR_ID=author.ID)
WHERE book.TITLE like '%war%'
AND book.PRICE < 10
AND book.PUBLISHED_AT < now()
AND author.FAME > 10;</pre></div>
</div>
<p>If someones asks for a Propel query version of the SQL query, it’s probably because the job of adding simple methods to the Query class wasn’t executed before. It’s very likely that a previous query in the same project looked like:</p>
<div class="CodeRay">
<div class="code"><pre>// find cheap books
SELECT * FROM book
WHERE book.PRICE < 10;</pre></div>
</div>
<p>And another one looked like:</p>
<div class="CodeRay">
<div class="code"><pre>// find published books
SELECT * FROM book
WHERE book.PUBLISHED_AT < now();</pre></div>
</div>
<p>And maybe even one like:</p>
<div class="CodeRay">
<div class="code"><pre>// find books by famous authors
SELECT * FROM book
LEFT JOIN author ON (book.AUTHOR_ID=author.ID)
WHERE author.FAME > 10;</pre></div>
</div>
<p>You get the point: little pieces of the query are reusable, and may even have been written previously. The proper way to handle these cases would be to improve the <code>BookQuery</code> class little by little, as follows:</p>
<div class="CodeRay">
<div class="code"><pre><?php
class BookQuery extends BaseBookQuery
{
public function cheap($maxPrice = 10)
{
return $this->filterByPrice(array('max' => $maxPrice));
}
public function published()
{
return $this->filterByPublishedAt(array('max' => time()));
}
public function writtenByFamousAuthors($fameTreshold = 10)
{
return $this
->leftJoin('Book.Author')
->where('Author.Fame > ?', $fameTreshold);
}
}</pre></div>
</div>
<p>Now writing the query becomes trivial:</p>
<div class="CodeRay">
<div class="code"><pre><?php
$books = BookQuery::create()
->filterByTitle('%war%')
->cheap()
->published()
->writtenByFamousAuthors();</pre></div>
</div>
<p>And since filtering on a word in the book title may be a common need, this ability should be added to the <code>BookQuery</code> class:</p>
<div class="CodeRay">
<div class="code"><pre><?php
class BookQuery extends BaseBookQuery
{
// ...
public function titleContainsWord($word)
{
return $this->filterByTitle('%' . $word . '%');
}</pre></div>
</div>
<p>Now the query is even easier to write, and more readable as well:</p>
<div class="CodeRay">
<div class="code"><pre><?php
$books = BookQuery::create()
->titleContainsWord('war')
->cheap()
->published()
->writtenByFamousAuthors();</pre></div>
</div>
<p>The idea is to add meaningful methods to the Query class piece by piece, so you never have to bake complex SQL. By doing so, you will realize that the Query classes contains more and more of your business logic, while the database only contains data. That’s a step further in the ORM paradigm.</p>
<h3>Answer #4: You Need More Than One Query</h3>
<p>Computer Science taught you to minimize queries, so if you were a good student, you might end up with queries looking like the following:</p>
<div class="CodeRay">
<div class="code"><pre>// find all books written by Alexandre Dumas, fils, and Alexandre Dumas, père
SELECT * FROM book
LEFT JOIN author ON book.AUTHOR_ID=author.ID
WHERE author.LAST_NAME = 'Dumas';</pre></div>
</div>
<p>But in the Object-Oriented world, it’s not a Bad Thing to execute several queries in a row. It may even make your code a lot clearer:</p>
<div class="CodeRay">
<div class="code"><pre><?php
$dumasAuthors = AuthorQuery::create()
->filterByLastName('Dumas');
->find();
$books = BookQuery::create()
->filterByAuthor($dumasAuthors) // ok, it's only possible in Propel 1.6 :)
->find();</pre></div>
</div>
<p>By doing so, you move some logic away from the database (the join) and back to the PHP code (filtering by objects). You may pay the expense of an additional trip to the database, but in the end your model logic is more decoupled, and fully object-oriented. And depending on the indices present in the tables, some PHP logic and two SQL queries may be faster to execute than an single SQL query with all the logic.</p>
<p>Propel makes it even better: you can keep the single SQL query while actually using two query objects by <a href="http://www.propelorm.org/wiki/Documentation/1.5/Relationships#UsingRelationshipsInAQuery">embedding queries</a>. That’s exactly what the <code>useXXXQuery()</code> methods allow:</p>
<div class="CodeRay">
<div class="code"><pre><?php
$books = BookQuery::create()
->useAuthorQuery()
->filterByLastName('Dumas')
->endUse()
->find();</pre></div>
</div>
<p>Combining several query objects allows for very complex queries in a very reusable way.</p>
<h3>Answer #5: You Don’t Use The Right Query</h3>
<p>While we are at separating queries, maybe part of the logic of a complex query can be moved to another <em>write</em> query. Let’s see an example:</p>
<div class="CodeRay">
<div class="code"><pre>// show all Dumas authors, together with the number of books they wrote
SELECT author.*, count(book.ID) as nb_books
FROM author LEFT JOIN book ON (author.ID = book.AUTHOR_ID)
WHERE author.LAST_NAME = 'Dumas'
GROUP BY author.ID;</pre></div>
</div>
<p>The <code>count()</code> might be expensive, especially on a large <code>book</code> table. It may be a better idea to denormalize the <code>author</code> table to add a <code>nb_books</code> column, updated each time a book is added or removed for a given author. Once again, this might sound counterintuitive to serious Computer Science students, but it’s a very common technique in the ORM world.</p>
<p>Propel makes this kind of denormalization a piece of cake thanks to the <a href="http://www.propelorm.org/wiki/Documentation/1.5/Behaviors/aggregate_column"><code>aggregate_column</code> behavior</a>. In fact, you don’t even have to worry about keeping the column up to date. Just set it up in the schema, and you’re good to go:</p>
<div class="CodeRay">
<div class="code"><pre><table name="author">
<column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" />
<column name="first_name" type="VARCHAR" />
<column name="last_name" type="VARCHAR" required="true" primaryString="true" />
<behavior name="aggregate_column">
<parameter name="name" value="nb_books" />
<parameter name="foreign_table" value="book" />
<parameter name="expression" value="COUNT(id)" />
</behavior>
</table>
<table name="book">
<column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" />
<column name="title" type="VARCHAR" required="true" primaryString="true" />
<column name="author_id" type="INTEGER" />
<foreign-key foreignTable="author" onDelete="cascade">
<reference local="author_id" foreign="id" />
</foreign-key>
</table></pre></div>
</div>
<p>Now use the Query object to retrieve <code>Author</code> objects, and the number of books comes free of charge:</p>
<div class="CodeRay">
<div class="code"><pre><?php
$authors = AuthorQuery::create()
->filterByLastName('Dumas')
->find();
foreach ($authors as $author) {
echo $author->getFirstName(), ': ', $author->getNbBooks(), "\n";
}</pre></div>
</div>
<p>Web applications often execute much more <em>read</em> queries than <em>write</em> queries. If a read query is complex and expensive in execution time, then you might consider simplifying it by adding more data at write time.</p>
<h3>Conclusion</h3>
<p>Propel offers a lot of ways to deal with complex queries. But if there is one thing to remember, it’s that in an ORM world you should think about <strong>objects</strong>, not <strong>SQL</strong>. If you come up with a complex SQL query to translate, it means you’ve probably taken the problem upside down. Put your business logic in the right place (in ActiveRecord or Query classes), and you’ll quickly forget about the pain of complex SQL queries.</p>
sfPropel16Plugin Is Already There, Didn't You Know?2011-01-27T00:00:00+00:00http://www.propelorm.org/blog/2011/01/27/sfpropel16plugin-is-already-there-didn-t-you-know-<p>The question arose several times in the past weeks: When will sfPropel16Plugin be published? Well, it's been published for a long time already, it's just that it was called "sfPropel15Plugin/1.6 branch". You can find it on GitHub: <p /><div><a href="https://github.com/fzaninotto/sfPropel15Plugin">https://github.com/fzaninotto/sfPropel15Plugin</a></div><p /><div>This repository used to default to the 'master' branch, which is a clone of t<a href="http://www.symfony-project.org/plugins/sfPropel15Plugin">he current 1.5 branch of sfPropel15Plugin</a>. Enhancements were added to the 1.6 branch, which recently became the default branch. </div> <p /><div>The repository contains an 'INSTALL' file explaining the few steps to get the plugin running on a symfony 1.4 project. </div><p /><div>Compared to the 1.5 version, this plugin features new tasks to deal with migrations: <code class="language-plaintext highlighter-rouge">diff</code>, <code class="language-plaintext highlighter-rouge">migrate</code>, <code class="language-plaintext highlighter-rouge">up</code>, and <code class="language-plaintext highlighter-rouge">down</code>. Use the <code class="language-plaintext highlighter-rouge">symfony help</code> command to get usage information on these tasks, and refer to the Propel documentation for a <a href="http://www.propelorm.org/wiki/Documentation/1.6/Migrations">complete explanation of Propel migrations</a>. There is also support for master-slave configuration right in the <code class="language-plaintext highlighter-rouge">databases.yml</code>, the ability to filter embedded forms with a <code class="language-plaintext highlighter-rouge">Criteria</code>, and <a href="https://github.com/fzaninotto/sfPropel15Plugin/commits/1.6">several bug fixes</a>.</div> <p /><div>It's a drop-in replacement for the previous sfPropel15Plugin from the symfony plugins page.</div><p /><div>Last question: Why is it called sfPropel15Plugin if it embeds Propel 1.6? Because we won't force you to use different class names for each minor release of Propel. Backwards compatibility has always been a central concern, and changing the plugin name (and the names of the classes it embeds) wouldn't allow that. That wouldn't be so surprising if Propel 1.5 was named Propel 2, except that wasn't the case ;)</div></p>
The Propel Website Gets a Better Search2011-01-26T00:00:00+00:00http://www.propelorm.org/blog/2011/01/26/the-propel-website-gets-a-better-search<p>The Propel pages now offer a search box capable of searching terms in the whole Propel site, including the blog and the documentation for later versions (1.4 to 1.6).<p /><div>Courtesy of <a href="http://www.google.com/cse">Google Custom search</a>, this basic feature improves the previous search engine provided by Trac (and still available at <a href="http://www.propelorm.org/search">http://www.propelorm.org/search</a>), which wasn't able to search in wiki pages embedding content from the SVN repository (that's the case of the 1.4 to 1.6 documentation pages), or in other domains (that's the case of the Propel blog). The Trac search engine often failed to return relevant results on terms like "migrations" and "enum", even if those features are fully documented already.</div> <p /><div>It's not a big deal, but it should make your life easier.</div></p>
Get Ready For Propel 1.6: The Beta 1 Is Released.2011-01-19T00:00:00+00:00http://www.propelorm.org/blog/2011/01/19/get-ready-for-propel-1-6-the-beta-1-is-released-<p>Propel 1.6 has been in development for several months, and yet it has always been ready for real life use. Each added feature is backwards compatible, unit tested, and documented. But until now, only a handfull of developers dared to install it due to its “under development” status.</p>
<p>Guess what? Propel 1.6 is now in <strong>beta</strong> state. This means that most of the features of the final 1.6 are already there, and that it’s almost ready for your production application. It also means that we need more feedback from beta testers to help us find and fix the last bugs before a stable release that should come within a couple months.</p>
<p>And to motivate you to upgrade, here is a brief list of highlights from the 1.6 version:</p>
<ul>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Migrations">Migrations</a></li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Behaviors/versionable">Versionable Behavior</a></li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Behaviors/i18n">I18n Behavior</a></li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/WhatsNew#XMLYAMLJSONCSVParsingandDumping">XML/YAML/JSON/CSV Parsing and Dumping</a></li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Using-SQL-Schemas">Support For SQL Schemas</a></li>
<li><a href="http://www.propelorm.org/wiki/Documentation/1.6/Advanced-Column-Types">Advanced Column Types (ENUM, OBJECT, ARRAY)</a></li>
</ul>
<p>The updated Propel 1.6 documentation offers a more detailed <a href="http://www.propelorm.org/wiki/Documentation/1.6/WhatsNew">list of new features in Propel 1.6</a>.</p>
<p>To upgrade, use your favorite distribution:</p>
<h3>Subversion tag</h3>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.6.0BETA1</pre></div>
</div>
<h3>PEAR</h3>
<div class="CodeRay">
<div class="code"><pre>> sudo pear config-set preferred_state beta
> sudo pear upgrade propel/propel_generator
> sudo pear upgrade propel/propel_runtime</pre></div>
</div>
<h3>Download</h3>
<ul>
<li><a href="http://files.propelorm.org/propel-1.6.0BETA1.tar.gz">http://files.propelorm.org/propel-1.6.0BETA1.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.6.0BETA1.zip">http://files.propelorm.org/propel-1.6.0BETA1.zip</a> (Windows)</li>
</ul>
<p>As usual, don’t forget to rebuild your object model classes using the <code>om</code> task. And don’t hesitate to send us feedback on the developers mailing-list, or by opening tickets on the <a href="http://www.propelorm.org/timeline">Propel Trac</a>.</p>
Propel 1.5.6 Is There, Upgrade To Get The Bugfixes2011-01-12T00:00:00+00:00http://www.propelorm.org/blog/2011/01/12/propel-1-5-6-is-there-upgrade-to-get-the-bugfixes<p>Propel 1.5.6 was just released. It is yet another (almost) monthly bugfix release, closing about a dozen issues. You can find the <a href="http://www.propelorm.org/wiki/Documentation/1.5/CHANGELOG">full changelog</a> in the Propel 1.5 documentation.</p>
<p>To upgrade, use your favorite distribution:</p>
<h3>Subversion tag</h3>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.5.6</pre></div>
</div>
<h3>PEAR</h3>
<div class="CodeRay">
<div class="code"><pre>> sudo pear upgrade propel/propel-generator
> sudo pear upgrade propel/propel-runtime</pre></div>
</div>
<h3>Download</h3>
<ul>
<li><a href="http://files.propelorm.org/propel-1.5.6.tar.gz">http://files.propelorm.org/propel-1.5.6.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.5.6.zip">http://files.propelorm.org/propel-1.5.6.zip</a> (Windows)</li>
</ul>
Propel Gets I18n Behavior, And Why It Matters2011-01-11T00:00:00+00:00http://www.propelorm.org/blog/2011/01/11/propel-gets-i18n-behavior-and-why-it-matters<p>Propel recently got yet another behavior: the Internationalization behavior, also named <strong>i18n behavior</strong> (the numeronym is a <a href="http://en.wikipedia.org/wiki/Internationalization_and_localization">frequent abbreviation</a>). It allows Propel model objects to get translations, and is useful in multilingual applications.</p>
<p>Not only is it intuitive and dead easy to setup, it also replaces the <a href="http://www.symfony-project.org/jobeet/1_4/Propel/en/19#chapter_19_sub_propel_objects">existing symfony i18n behavior</a> without any change to the application code. And why the Symfony i18n behavior implementation has an important flaw, the new native i18n behavior does things the proper way.<!--more--></p>
<h3>Usage</h3>
<p>Consider as an e-commerce website selling home appliances across the world. This website should keep the name and description of each item separated from the other details, and keep one version for each supported language.</p>
<p>Starting with Propel 1.6, this is possible by adding a simple <code><behavior></code> tag to the table that needs internationalization:</p>
<div class="CodeRay">
<div class="code"><pre><table name="item">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="name" type="VARCHAR" required="true" />
<column name="description" type="LONGVARCHAR" />
<column name="price" type="FLOAT" />
<column name="is_in_store" type="BOOLEAN" />
<behavior name="i18n">
<parameter name="i18n_columns" value="name, description" />
</behavior>
</table></pre></div>
</div>
<p>In this example, the <code>name</code> and <code>description</code> columns are moved to a new table, called <code>item_i18n</code>, which shares a many-to-one relationship with Item - one Item has many Item translations. But all this happens in the background; for the end user, everything happens as if there were only one main <code>Item</code> object:</p>
<div class="CodeRay">
<div class="code"><pre>$item = new Item();
$item->setPrice('12.99');
$item->setName('Microwave oven');
$item->save();</pre></div>
</div>
<p>This creates one record in the <code>item</code> table with the price, and another in the <code>item_i18n</code> table with the English (default language) translation for the name. Of course, you can add more translations:</p>
<div class="CodeRay">
<div class="code"><pre>$item->setLocale('fr_FR');
$item->setName('Four micro-ondes');
$item->setLocale('es_ES');
$item->setName('Microondas');
$item->save();</pre></div>
</div>
<p>This works both for setting AND for getting internationalized columns:</p>
<div class="CodeRay">
<div class="code"><pre>$item->setLocale('en_EN');
echo $item->getName(); //'Microwave oven'
$item->setLocale('fr_FR');
echo $item->getName(); // 'Four micro-ondes'</pre></div>
</div>
<p><strong>Tip</strong>: The big advantage of Propel behaviors is that they use code generation. Even though it’s only a proxy method to the <code>ItemI18n</code> class, <code>Item::getName()</code> has all the phpDoc required to make your IDE happy.</p>
<h3>Combined Hydration</h3>
<p>This new behavior also adds special capabiliies to the Query objects. The most interesting allows you to execute less queries when you need to query for an Item and one of its translations - which is common to display a list of items in the locale of the user:</p>
<div class="CodeRay">
<div class="code"><pre>$items = ItemQuery::create()->find(); // one query to retrieve all items
$locale = 'en_EN';
foreach ($items as $item) {
echo $item->getPrice();
$item->setLocale($locale);
echo $item->getName(); // one query to retrieve the English translation
}</pre></div>
</div>
<p>This code snippet requires 1+n queries, n being the number of items. But just add one more method call to the query, and the SQL query count drops to 1:</p>
<div class="CodeRay">
<div class="code"><pre>$items = ItemQuery::create()
->joinWithI18n('en_EN')
->find(); // one query to retrieve both all items and their translations
foreach ($items as $item) {
echo $item->getPrice();
echo $item->getName(); // no additional query
}</pre></div>
</div>
<p>In addition to hydrating translations, <code>joinWithI18n()</code> sets the correct locale on results, so you don’t need to call <code>setLocale()</code> for each result.</p>
<h3>Symfony Compatibility</h3>
<p>This behavior is entirely compatible with the i18n behavior for symfony. That means that it can generate <code>setCulture()</code> and <code>getCulture()</code> methods as aliases to <code>setLocale()</code> and <code>getLocale()</code>, provided that you add a <code>locale_alias</code> parameter. That also means that if you add the behavior to a table without translated columns, and that the translation table is present in the schema, the behavior recognizes them.</p>
<p>So the following schema is exactly equivalent to the first one in this article:</p>
<div class="CodeRay">
<div class="code"><pre><table name="item">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="price" type="FLOAT" />
<column name="is_in_store" type="BOOLEAN" />
<behavior name="i18n">
<parameter name="locale_alias" value="culture" />
</behavior>
</table>
<table name="item_i18n">
<column name="id" type="INTEGER" required="true" primaryKey="true" />
<column name="name" type="VARCHAR" required="true" />
<column name="description" type="LONGVARCHAR" />
</table></pre></div>
</div>
<p>Such a schema is almost similar to a schema built for symfony; that means that the Propel i18n behavior is a drop-in replacement for symfony’s i18n behavior, keeping BC but improving performance and usability.</p>
<h3>Why It Matters</h3>
<p>The SQL generated by the previous query looks like the following (in MySQL):</p>
<div class="CodeRay">
<div class="code"><pre>SELECT item.*, item_i18n.*
FROM item LEFT JOIN item_i18n ON (item.id = item_i18n.id AND item_i18n.locale = 'en_EN');</pre></div>
</div>
<p>It does NOT generate the following query:</p>
<div class="CodeRay">
<div class="code"><pre>SELECT item.*, item_i18n.*
FROM item LEFT JOIN item_i18n ON (item.id = item_i18n.id)
WHERE item_i18n.locale = 'en_EN';</pre></div>
</div>
<p>Can you see the difference? In the last SQL query, the LEFT JOIN actually behaves like an INNER JOIN because of the WHERE clause. That means that <code>item</code> records with no <code>item_i18n</code> translation won’t appear in the result. In the first query, even items with no translations are returned.</p>
<p>This difference is important for two reasons:</p>
<ul>
<li>Propel couldn’t create joins with two conditions properly in version 1.5 and below. Only Propel 1.6 allows it (see <a href="http://www.propelorm.org/wiki/Documentation/1.6/WhatsNew#JoinWithSeveralConditions">What’s New In Propel 1.6?</a>). </li>
<li>The previous i18n behavior implementation for symfony did it the wrong way, and applied the locale condition using WHERE instead of ON. That made results incomplete.</li>
</ul>
<p><strong>Tip</strong>: If you need to return only objects having translations, add <code>Criteria::INNER_JOIN</code> as second parameter to <code>joinWithI18n()</code>.</p>
<h3>Get It</h3>
<p>Just like the recently added <code>versionable</code> behavior, the <code>i18n</code> behavior is thoroughly unit-tested and <a href="http://propelorm.org/behaviors/i18n">fully documented</a>. It is ready to use in the Propel 1.6 branch, and your multilingual applications will love it.</p>
Propel 1.6 Gets Versionable Behavior - With A Twist2010-12-22T00:00:00+00:00http://www.propelorm.org/blog/2010/12/22/propel-1-6-gets-versionable-behavior-with-a-twist<p><a href="http://www.propelorm.org/wiki/Documentation/1.6/WhatsNew">Propel 1.6</a> ships with a great new behavior. Once enabled on a table, the <code>versionable</code> behavior stores a copy of the ActiveRecord object in a separate table each time it is saved. This allows to keep track of the changes made on an object, whether to review modifications, or revert to a previous state.</p>
<p>The classic Wiki example is a good illustration:</p>
<div class="CodeRay">
<div class="code"><pre><table name="wiki_page">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="title" type="VARCHAR" required="true" />
<column name="body" type="LONGVARCHAR" />
<behavior name="versionable" />
</table></pre></div>
</div>
<p>After rebuild, the <code>WikiPage</code> model has versioning abilities:<!--more--></p>
<div class="CodeRay">
<div class="code"><pre>$page = new WikiPage();
// automatic version increment
$page->setTitle('Propel');
$page->setBody('Propel is a CRM built in PHP');
$page->save();
echo $page->getVersion(); // 1
$page->setBody('Propel is an ORM built in PHP5');
$page->save();
echo $page->getVersion(); // 2
// reverting to a previous version
$page->toVersion(1);
echo $page->getBody(); // 'Propel is a CRM built in PHP'
// saving a previous version creates a new one
$page->save();
echo $page->getVersion(); // 3
// checking differences between versions
print_r($page->compareVersions(1, 2));
// array(
// 'Body' => array(
// 1 => 'Propel is a CRM built in PHP',
// 2 => 'Propel is an ORM built in PHP5'
// ),
// );
// deleting an object also deletes all its versions
$page->delete();</pre></div>
</div>
<p>The <code>versionable</code> behavior offers audit log functionality, so you can track who made a modification, when, and why:</p>
<div class="CodeRay">
<div class="code"><pre>$page = new WikiPage();
$page->setTitle('PEAR');
$page->setBody('PEAR is a framework and distribution system for reusable PHP components');
$page->setVersionCreatedBy('John Doe');
$page->setVersionComment('First draft');
$page->save();
// do more modifications...
// list all modifications
foreach ($page->getAllVersions() as $pageVersion) {
echo sprintf("'%s', Version %d, updated by %s on %s (%s)\n",
$pageVersion->getTitle(),
$pageVersion->getVersion(),
$pageVersion->getVersionCreatedBy(),
$pageVersion->getVersionCreatedAt(),
$pageVersion->getVersionComment(),
);
}
// 'PEAR', Version 1, updated by John Doe on 2010-12-21 22:53:02 (First draft)
// 'PEAR', Version 2, updated by ...</pre></div>
</div>
<p>If it was just for that, the <code>versionable</code> behavior would already be awesome. Versioning is a very common feature, and there is no doubt that this behavior will replace lots of boilerplate code. Consider the fact that it’s very configurable, <a href="http://www.propelorm.org/wiki/Documentation/1.6/Behaviors/versionable">fully documented</a>, and unit tested, and there is no reason to develop your own versioning layer.</p>
<p>But there is more.</p>
<p>The <code>versionable</code> behavior also works on <strong>relationships</strong>.</p>
<p>If the <code>WikiPage</code> has one <code>Category</code>, and if the <code>Category</code> model also uses the <code>versionable</code> behavior, then each time a <code>WikiPage</code> is saved, it saves the version of the related <code>Category</code> it is related to, and it is able to restore it:</p>
<div class="CodeRay">
<div class="code"><pre>$category = new Category();
$category->setName('Libraries');
$page = new WikiPage();
$page->setTitle('PEAR');
$page->setBody('PEAR is a framework and distribution system for reusable PHP components');
$page->setCategory($category);
$page->save(); // version 1
$page->setTitle('PEAR - PHP Extension and Application Repository');
$page->save(); // version 2
$category->setName('PHP Libraries');
$page->save(); // version 3
$page->toVersion(1);
echo $page->getTitle(); // 'PEAR'
echo $page->getCategory()->getName(); // 'Libraries'
$page->toVersion(3);
echo $page->getTitle(); // 'PEAR - PHP Extension and Application Repository'
echo $page->getCategory()->getName(); // 'PHP Libraries'</pre></div>
</div>
<p>Now the versioning is not limited to a single class anymore. You can even design a fully versionable "application" - it all depends on your imagination.</p>
<p>This feature is unique to Propel, and that’s our very Christmas gift to you.</p>
Propel Adds Support For Database Schemas in Version 1.62010-12-15T00:00:00+00:00http://www.propelorm.org/blog/2010/12/15/propel-adds-support-for-database-schemas-in-version-1-6<p>For complex models showing a large number of tables, database administrators often like to group tables into “SQL schemas”, which are namespaces in the SQL server. Starting with Propel 1.6, it is now possible to assign tables to SQL schemas using the <code>schema</code> attribute in the <code><database></code> of the <code><table></code> tag:</p>
<div class="CodeRay">
<div class="code"><pre><database name="my_connection">
<table name="book" schema="bookstore">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="title" type="VARCHAR" required="true" />
<column name="author_id" type="INTGER" />
<foreign-key foreignTable="author" foreignSchema="people" onDelete="setnull" onUpdate="cascade">
<reference local="author_id" foreign="id" />
</foreign-key>
</table>
<table name="author" schema="people">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="name" type="VARCHAR" required="true" />
</table>
</database></pre></div>
</div>
<p><strong>Tip</strong>: This feature is only available in PostgreSQL, MSSQL, and MySQL. The <code>schema</code> attribute is simply ignored in Oracle and SQLite.</p>
<p>Propel also supports foreign keys between tables assigned to two different schemas. For MySQL, where “SQL schema” is a synonym for “database”, this allows for cross-database queries.</p>
<p>The Propel documentation contains a new tutorial about the SQL schema attributes and usage, called <a href="http://www.propelorm.org/wiki/Documentation/1.6/Using-SQL-Schemas">Using SQL Schemas</a>.</p>
Propel Gets Better at Naming Things2010-12-13T00:00:00+00:00http://www.propelorm.org/blog/2010/12/13/propel-gets-better-at-naming-things<p>Have you ever considered Propel as a lame English speaker? Due to its poor pluralizer, Propel used to be unable to create proper getter methods for one-to-many relationships when dealing with foreign objects named ‘Child’, ‘Category’, ‘Wife’, or ‘Sheep’.</p>
<p>Starting with Propel 1.6, Propel adds a new pluralizer class named <code>StandardEnglishPluralizer</code>, which should take care of most of the irregular plural forms of your domain class names. This new pluralizer is disabled by default for backwards compatibility reasons, but it’s very easy to turn it on in your <code>build.properties</code> file:</p>
<div class="CodeRay">
<div class="code"><pre>propel.builder.pluralizer.class = builder.util.StandardEnglishPluralizer</pre></div>
</div>
<p>Rebuild your model, and voila: ActiveRecord objects can now retrieve foreign objects using <code>getChildren()</code>, <code>getCategories()</code>, <code>getWives()</code>, and… <code>getSheep()</code>.</p>
<p>Thanks to Paul Hanssen for the patch. </p>
Propel 1.6 gets ENUM, ARRAY, and OBJECT Column Types2010-12-09T00:00:00+00:00http://www.propelorm.org/blog/2010/12/09/propel-1-6-gets-enum-array-and-object-column-types<p>Dealing with complex data will become easier with Propel 1.6. Complex column types have landed in the 1.6 branch, and they offer a clean interface to store and retrieve ENUM, ARRAY, and OBJECT values. Here is a quick example:</p>
<div class="CodeRay">
<div class="code"><pre><table name="book">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true" />
<column name="title" type="VARCHAR" />
<column name="style" type="ENUM" valueSet="novel, essay, poetry" />
<column name="tags" type="ARRAY" />
</table></pre></div>
</div>
<p>The getters and setters for the style and tags columns make it easy to work with a predefined value set, or a list of values:<!--more--></p>
<div class="CodeRay">
<div class="code"><pre>$book = new Book();
$book->setTitle('Pride and Prejudice');
// ENUM columns only accept values from the valueSet - other values throw an Exception
$book->setStyle('novel');
// ARRAY columns accept an array of scalar values
$book->setTags(array('satire', '19th century'));
$book->addTag('England');
// These properties are persisted to the database through serialization
$book->save();
// And of course, Propel restores them seamlessly through hydration
$book = BookQuery::create()->findOneByTitle('Pride and Prejudice');
echo $book->getStyle(); // novel
echo $book->hasTag('satire'); // true
print_r($book->getTags()); // array('satire', '19th century', 'England');</pre></div>
</div>
<p>To be honest, this is a common feature for other ORMs, and Propel is quite late to support these column types. But no other ORM supports <strong>searching</strong> of records based on complex column values. Thanks to the generated <code>filterByXXX()</code> methods, this is a piece of cake for Propel:</p>
<div class="CodeRay">
<div class="code"><pre>// find books using an ENUM column value
$books = BookQuery::create()
->filterByStyle('novel')
->find();
// find books using an ARRAY column value
$books = BookQuery::create()
->filterByTag('England')
->find();
// find books using an ARRAY column values - ALL, SOME or NONE
$books = BookQuery::create()
->filterByTags(array('England', 'satire'), Criteria::CONTAINS_SOME)
->find();</pre></div>
</div>
<p>And this is not restricted to database platforms that actually support these column types. With Propel, ENUM, ARRAY and OBJECT column types work on MySQL, PostgreSQL, MSSQL, SQLite, and Oracle!</p>
<p>The Propel Documentation already contains a <a href="http://www.propelorm.org/wiki/Documentation/1.6/Advanced-Column-Types">full chapter</a> describing this feature, together with example usage. And of course, these features are fully unit tested, so you can use them right away.</p>
New Documentation Chapter: Propel Active Record reference2010-11-30T00:00:00+00:00http://www.propelorm.org/blog/2010/11/30/new-documentation-chapter-propel-active-record-reference<p>Lots of details about the Propel Active Record objects used to be scattered among the multiple chapters of the Propel documentation. Since yesterday, the Propel 1.6 documentation has a new reference chapter: the <a href="http://www.propelorm.org/wiki/Documentation/1.6/ActiveRecord">Active Record reference</a>.</p>
<p>If you're a long time Propel user, and if you know your generated Active Record classes by heart, you may not learn much there - although Propel has many lesser known features buried in the BaseObject class that can be of great help. On the other hand, if you're discovering Propel, or if you are interested in the upcoming Propel 1.6 runtime features, head to this new piece of documentation for a complete list of features of one of the main central elements of the Propel runtime API.</p>
<p> </p>
<p> </p>
Propel 1.5.5 released2010-11-17T00:00:00+00:00http://www.propelorm.org/blog/2010/11/17/propel-1-5-5-released<div style="background-color: #ffffff; margin: 8px;">
<p>Propel 1.5.5 was just released. It is a bugfix release, closing about 20 issues. You can find the <a href="http://www.propelorm.org/wiki/Documentation/1.5/CHANGELOG">full changelog</a> in the Propel 1.5 documentation.</p>
<p>To upgrade, use your favorite distribution:</p>
<ul>
<li style="color: #000000; font-family: Arial, Helvetica, sans-serif; font-size: 13px;">
<p>Subversion tag</p>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.5.5</pre></div>
</div>
</li>
<li>
<p>Download</p>
<ul style="color: #000000; font-family: Arial, Helvetica, sans-serif; font-size: 13px;">
<li><a href="http://files.propelorm.org/propel-1.5.5.tar.gz">http://files.propelorm.org/propel-1.5.5.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.5.5.zip">http://files.propelorm.org/propel-1.5.5.zip</a> (Windows)</li>
</ul>
</li>
</ul>
</div>
<div style="background-color: #ffffff; margin: 8px;">We are experiencing a few issues with the PEAR server at the moment, so it stills offers only the 1.5.4 version. We'll post an update as soon as the PEAR server offers the latest packages.</div>
<p><strong>Update</strong>: And now that the PEAR channel is fine again, you can even grab the new version via PEAR:</p>
<div class="CodeRay">
<div class="code"><pre>> sudo pear upgrade propel/propel-generator
> sudo pear upgrade propel/propel-runtime</pre></div>
</div>
<p> </p>
Slide of my Presentation for the PHP Forum 2010 in Paris2010-11-10T00:00:00+00:00http://www.propelorm.org/blog/2010/11/10/slide-of-my-presentation-for-the-php-forum-2010-in-paris<p>No, the title has no typo - there is only one slide. But one can say a lot with a single slide. This presentation explains how developers progress, and what they should learn, using data persistance as an example. It's in French, but most of the code is in English. The presentation was given yesterday during the <a href="http://afup.org/pages/forumphp2010/">PHP Forum 2010</a> Conference in Paris.<!--more--></p>
<div class="prezi-player">
<object height="400" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="500">
<param name="movie" value="http://prezi.com/bin/preziloader.swf" />
<param name="allowfullscreen" value="true" />
<param name="allowscriptaccess" value="always" />
<param name="bgcolor" value="#ffffff" />
<param name="flashvars" value="prezi_id=20zx62inia2y&lock_to_path=0&color=ffffff&autoplay=no&autohide_ctrls=0" /><embed src="http://prezi.com/bin/preziloader.swf" type="application/x-shockwave-flash" height="400" flashvars="prezi_id=20zx62inia2y&lock_to_path=0&color=ffffff&autoplay=no&autohide_ctrls=0" width="500" /></embed>
</object>
<div class="prezi-player-links">
<p><a href="http://prezi.com/20zx62inia2y/apprendre-en-persistant-propel-php-forum-2010/">Apprendre en persistant - Propel - Php Forum 2010</a> on <a href="http://prezi.com">Prezi</a></p>
</div>
</div>
<p>Since I was sick, I couldn't do the presentation myself. Fortunately, Arlo Borras, the chief Architect at eTF1, agreed to present it himself. And according to the feedback I got, he did it very well. I want to thank him a lot for this, and let me say how a truly amazing guy he is to be able to do that with such a short notice.</p>
<p>The whole code of the presentation is available at <a href="http://github.com/fzaninotto/forumphp/">http://github.com/fzaninotto/forumphp/</a>. The presentation was made using <a href="http://prezi.com">Prezi</a> - an awesome presentation tool that I recommend.</p>
<p>If you attended the presentation, don't hesitate to give your feedback by commenting this post, or the related <a href="http://joind.in/talk/view/2069">joind.in</a> page.</p>
Video: IDE Autocompletion with Propel2010-10-31T00:00:00+00:00http://www.propelorm.org/blog/2010/10/31/video-ide-autocompletion-with-propel<p>Propel has always claimed to be IDE-friendly. But have you ever witnessed it? If, like me, you prefer to use a text editor rather than an IDE, you may ignore how much autocompletion (also named "intellisense") and auto-documentation can improve your workflow.</p>
<p />
<div>Here is a brief screencast recording a 3 minutes coding session on runtime code, using the NetBeans IDE:</div>
<p />
<div><iframe src="http://player.vimeo.com/video/16358906?portrait=0" frameborder="0" height="283" width="500"></iframe></div>
<p />
<div>As you can see, autocompletion works for generated queries (it autocompletes "filterBy()" methods), ActiveRecord objects, and even collections. And you don't even need to tell your IDE the class of the variables you're dealing with: the return types for "findOne()" and "find()" are enough for NetBeans to figure out what $book and $books are.</div>
<p />
<div>Propel helps to reduce the time you spend using Model objects, so that you can focus on more important things - like designing and improving this very model.</div>
Propel at Forum PHP Paris2010-10-25T00:00:00+00:00http://www.propelorm.org/blog/2010/10/25/propel-at-forum-php-paris<div><img title="illbethere_bandeau2010.png" src="http://www.afup.org/templates/forumphp2010/images/illbethere_bandeau2010.png" alt="illbethere_bandeau2010.png" /><br /></div>
<p />
<p>I'll have the pleasure to be giving a <a href="http://www.afup.org/pages/forumphp2010/sessions.php#374">presentation about Propel</a> during next Forum PHP in Paris, on November 9th. Yep, that's in France, in only two weeks from now, and the presentation will be in French. <p /><div>This conference is also a unique occasion to listen to some of the most famous lecturers in the PHP world, including Rasmus Lerdorf, Zeev Suraski, Jonathan Wage, and <span style="font-family: arial, sans-serif; font-size: 12px;">Michael Widenius aka Monty. </span></div> <p /><div><span style="font-family: arial, sans-serif; font-size: 12px;"></span>If you want to discover Propel, meet me and other Propel developers in real life, make sure to grab a ticket for the event. </div></p>
Propel 1.6 + phpsh = Awesome CLI to your Database2010-09-20T00:00:00+00:00http://www.propelorm.org/blog/2010/09/20/propel-1-6-phpsh-awesome-cli-to-your-database<p>Propel 1.6, the next iteration of the PHP ORM, is still under heavy development. But since it’s backwards compatible with previous versions, you can easily test it on an existing application. One good reason to try it on is to take advantage of <a href="http://www.propelorm.org/wiki/Documentation/1.6/WhatsNew#XMLYAMLJSONCSVParsingandDumping">the new ability of ActiveRecord and Query classes to be dumped to a YAML string</a>.</p>
<p>And if you combine this ability with the power of <a href="http://www.phpsh.org/">phpsh</a> - an interactive PHP shell utility which is to PHP what IRB is to Ruby - you’ve got a new way to interact with your domain model. Just feed phpsh with a bootstrap script that initializes Propel and autoload your model classes, and you're good to go. Check the following screencast for an example:</p>
<p><iframe src="http://player.vimeo.com/video/15140218?portrait=0" frameborder="0" height="283" width="500"></iframe></p>
<p>ActiveRecord and Query classes can also be dumped to XML, JSON, and CSV. You can visualize joined hydration. And you can use your custom filters, too!</p>
<p>No need to write a custom script to check the persisted objects of your domain model anymore. Combined with phpsh, Propel now almost feels like object-oriented database.</p>
Propel 1.5.4 Released2010-09-14T00:00:00+00:00http://www.propelorm.org/blog/2010/09/14/propel-1-5-4-released<p>Here comes our monthly bugfix release. KRavEN and ddalmais helped improving the MSSQL and Oracle adapters - PDO is really not doing things properly alone. In addition to a few other bugfixes, an important improvement made it trivial to hydrate Propel objects based on an arbitrary SQL statement:</p>
<div class="CodeRay">
<div class="code"><pre>// prepare and execute an arbitrary SQL statement
$con = Propel::getConnection(BookPeer::DATABASE_NAME);
$sql = "SELECT * FROM book WHERE id NOT IN "
."(SELECT book_review.book_id FROM book_review"
." INNER JOIN author ON (book_review.author_id=author.ID)"
." WHERE author.last_name = :name)";
$stmt = $con->prepare($sql);
$stmt->execute(array(':name' => 'Austen'));
// hydrate Book objects with the result
$formatter = new PropelObjectFormatter();
$formatter->setClass('Book');
$books = $formatter->format($stmt);</pre></div>
</div>
<p>You can find the <a href="http://www.propelorm.org/wiki/Documentation/1.5/CHANGELOG">full changelog</a> in the Propel 1.5 documentation.</p>
<p>To upgrade, use your favorite distribution:</p>
<ul>
<li>
<p>Subversion tag</p>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.5.4</pre></div>
</div>
</li>
<li>
<p>PEAR package</p>
<div class="CodeRay">
<div class="code"><pre>> sudo pear upgrade propel/propel-generator
> sudo pear upgrade propel/propel-runtime</pre></div>
</div>
</li>
<li>
<p>Download</p>
<ul>
<li><a href="http://files.propelorm.org/propel-1.5.4.tar.gz">http://files.propelorm.org/propel-1.5.4.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.5.4.zip">http://files.propelorm.org/propel-1.5.4.zip</a> (Windows)</li>
</ul>
</li>
</ul>
Propel2 Will Be an ActiveRecord Implementation Based On Doctrine22010-09-10T00:00:00+00:00http://www.propelorm.org/blog/2010/09/10/propel2-will-be-an-activerecord-implementation-based-on-doctrine2<p>Yesterday's <a href="http://propel.posterous.com/propel-2-what-could-it-be-and-do-you-want-to">IRC meeting</a> was the occasion to design the shape Propel2, the next major iteration of the Propel ORM. The most important points resulting from the discussion are:</p>
<ul>
<li>Propel2 will require PHP 5.3</li>
<li>It will be an ActiveRecord implementation (like Propel 1)</li>
<li>It will be (mostly) backwards-compatible with Propel 1 - that means that the <a href="http://www.propelorm.org/wiki/Documentation/1.5/WhatsNew#NewQueryAPI">Query API</a> introduced in Propel 1.5 is here to stay</li>
<li>It will use code generation for better performance and IDE integration (like Propel 1)</li>
<li>It will use <a href="http://www.doctrine-project.org/projects/dbal/2.0/docs/en">Doctrine2 DBAL</a>, and some parts of the <a href="http://www.doctrine-project.org/projects/orm/2.0/docs/en">Doctrine2 ORM</a>, for internals</li>
<li>The source code will be <a href="http://github.com/fzaninotto">hosted on github</a></li>
<li>I (François Zaninotto) will bootstrap and lead the project</li>
<li>Everyone is welcome to contribute</li>
</ul>
<p>I'm very happy with how things turned out. It seems that most people concerned with Propel saw the duplicated efforts in Propel and Doctrine as a waste of time. Propel2 will focus on the usability layer of the ORM, without the need to reimplement a DBAL, or an object-relational mapping layer.</p>
<p>See the complete discussion log after the break.</p>
<p><!--more--></p>
<p>22:00:26 <francois> Ok everyone, it's 10pm on my watch, I guess it's time to begin<br /> 22:00:38 <Phenix> let's go<br /> 22:00:41 <francois> Letme first say (write) a few words<br /> 22:01:01 <francois> It's only been a year since I took over Propel, and I didn't expect to dedicate so much time to it<br /> 22:01:13 <francois> I didn't expect to make it move so much either<br /> 22:01:24 <grEvenX> :)<br /> 22:01:37 <grEvenX> (but oh has it moved!)<br /> 22:01:44 <francois> To be honest, I took Propel as a corpse, and gave myself the mission to help it have a soft death<br /> 22:01:54 <francois> today, that's not the question anymore<br /> 22:02:27 <francois> Propel is very much alive and has found (again) a community of users and developers<br /> 22:02:28 <b00giZm> (they call him the steve jobs of propel ;))<br /> 22:02:48 <francois> We could go on like this past year for a long time<br /> 22:03:33 <francois> adding killer feature every two days, refactoring the core without breaking BC, simplifying the life of developers<br /> 22:03:35 <francois> but<br /> 22:03:48 <francois> the future is not Propel 1.x<br /> 22:04:16 <francois> the future is probably Symfony2, Zend Framework2, Doctrine2, and all those Project2<br /> 22:04:27 <francois> So basically, we need a Propel 2<br /> 22:04:33 <francois> just for the sake of the number<br /> 22:04:37 <francois> (just kidding)<br /> 22:04:38 <StephenMelrose> :)<br /> 22:04:41 <avalanche123> :)<br /> 22:04:45 <LvanderRee> agree I can create a branch ;)<br /> 22:04:50 <grEvenX> hehe<br /> 22:05:10 <francois> When I took over the 1.x branch, someone else took over the 2.x branch<br /> 22:05:20 <francois> no work has been done on this branch since<br /> 22:05:33 <francois> So we are gathered here tonight<br /> 22:05:44 <francois> to discuss the future of Propel<br /> 22:05:58 <francois> what it should be, how it should be called, and who should take care of it<br /> 22:06:25 <francois> I suggest that we all talk together now to give our thoughts on the matter<br /> 22:06:32 <francois> because I'm feeling lonely :)<br /> 22:06:42 <francois> now shoot<br /> 22:06:50 <Jarda> I'm not completely sure if we really need a 2.0<br /> 22:06:57 <Jarda> you have done magical stuff with the 1.x branch<br /> 22:07:14 <grEvenX> there were some "bold" ideas a year ago, and even from earlier than that about what features should wait for Propel 2<br /> 22:07:19 <Jarda> there must be a killer vision for 2.0<br /> 22:07:26 <grEvenX> does anyone remember any of those suggested features?<br /> 22:07:28 <LvanderRee> I think we should cooperate more with doctrine in a new version<br /> 22:07:29 <StephenMelrose> But with more additions and BC 1.x becomes bloated. Doctrine2 is already amazing performance wise. Propel needs to step up to the place<br /> 22:07:46 <francois> Jarda: I'm sure we need it. People starting projects today, with PHP 5.3, namespaces, closures and late static binding will find Propel dated<br /> 22:07:54 <v-dogg> Criteria2 was meant to be the killer thing for P2 but current Query stuff is superior<br /> 22:07:56 <annismckenzie> I can only attest to the quality and thoughtfulness put into Propel 1.4 and up<br /> 22:08:01 <LvanderRee> using its dbal and other abstraction layers while still generating models<br /> 22:08:18 <tobiassjosten> francois: You have probably been asked the question before, but I'm relatively new to this scene, so... Why not join forces with the Doctrine crew? What does Propel bring to the table that Doctrine can't?<br /> 22:08:23 <b00giZm> francois, we already had a little email exchange about it. it's a real PITA to do unit testing with propel 1.x<br /> 22:08:48 <francois> ok, a few words about my view on Doctrine<br /> 22:08:55 <StephenMelrose> It's important to keep Propel and Doctrine seperate. Both push each other to innovate, imho<br /> 22:08:57 <lsmith> afaik the main thing differentiating doctrine from propel is code generation and the criteria API<br /> 22:09:02 <b00giZm> with doctrine it's really easy to use in-memory (sqllite) dbs, but with propel it's rally hard because of the generators<br /> 22:09:23 <francois> Doctrine 1 is fun the first time you use it, but it's too magical, too slow, and too hard to debug as compared to Propel<br /> 22:10:00 <LvanderRee> agree<br /> 22:10:02 <francois> Doctrine 1 gave a good boost to Propel, but now I think we shouldn't blush in comparison<br /> 22:10:03 <grEvenX> isn't Doctrine a "complete" framework? quite a few actually prefers to stay away from framework (or develop new ones)<br /> 22:10:07 <francois> As for Doctrine 2<br /> 22:10:13 <francois> It's a great library<br /> 22:10:32 <francois> that fills most of the holes and fixes most of the mistakes of Doctrine2<br /> 22:10:35 <tobiassjosten> Right, the magic is kind of scary. But speaking as a newcomer (honestly with no experience in Propel) I just feel there's wasted resources with these two awesome, but seperate, efforts.<br /> 22:10:41 <francois> but it has one very big difference with Propel<br /> 22:10:48 <francois> And with Doctrine 1, for that matter<br /> 22:10:57 <francois> It's not an ActiveRecord implementation<br /> 22:11:16 <StephenMelrose> And I like the AR implementation, which is why I use it<br /> 22:11:23 <nsolsen> me too<br /> 22:11:28 <francois> but the work done on the Doctrine DBAL is awesome<br /> 22:11:37 <francois> and SHOULD NOT be done a second time<br /> 22:11:42 <lsmith> francois: i am sure you read <a href="http://www.doctrine-project.org/blog/your-own-orm-doctrine2">http://www.doctrine-project.org/blog/your-own-orm-doctrine2</a> ?<br /> 22:11:50 <francois> lsmith: yep<br /> 22:11:58 <b00giZm> oh... <br /> 22:12:02 <b00giZm> :)<br /> 22:12:20 <LvanderRee> I agree with tobiassjosten about wasting resources, and agree with francois about the magic and hard debugging and not active record, but as far as I know, doctrine2 is being very well designed, and we can build a active-record generator on top of doctrine's dbal<br /> 22:12:22 <francois> but ActiveRecord can be a master class all your entities extend<br /> 22:12:28 <LvanderRee> to share resources and effort<br /> 22:12:42 <francois> or abilities added by way of code generation<br /> 22:13:15 <LvanderRee> francois I think we are thinking the same ;)<br /> 22:13:24 <StephenMelrose> Why not build propel2 on top of doctrine2's DBAL then? If possible?<br /> 22:13:26 <lsmith> code generation approach could be made future proof with traits (<a href="http://wiki.php.net/rfc/horizontalreuse)">http://wiki.php.net/rfc/horizontalreuse)</a><br /> 22:13:43 <francois> lsmith: when it's available<br /> 22:14:02 <Jarda> But if propel is going to switch to syntax like Doctrine_Query, I'll be out. It sucks donkeys balls..<br /> 22:14:06 <lsmith> right .. its already in trunk .. but no decision has been made on what the next php version will be<br /> 22:14:11 <francois> I know it's in the trunk/whatever the name of this PHP verion<br /> 22:14:25 <francois> I'm just not willing to wait for it<br /> 22:14:33 <francois> and it doesn't solve all problems<br /> 22:14:56 <lsmith> francois: oh i wasnt suggesting that .. i was saying .. care could be taken that once traits our out .. the code generation could switch to using traits<br /> 22:14:57 <francois> Jarda: That's a strong point<br /> 22:15:29 <francois> There is a strong difference between both Doctrine versions and Propel: the way to make queries<br /> 22:15:43 <Jarda> using Doctrine_Query is like writing raw sql, only it's much more PITA<br /> 22:15:53 <XpLo^bn> doctrine query api is very bad from my point of view<br /> 22:16:02 <XpLo^bn> well same as Jarda ~<br /> 22:16:07 <francois> I strongly believe that the Propel Query API provides a great architecture for model logic<br /> 22:16:19 <Jarda> francois: and an awesome IDE experience<br /> 22:16:27 <StephenMelrose> Seconded<br /> 22:16:30 <francois> Jarda: indeed, second strong point<br /> 22:16:33 <v-dogg> +1<br /> 22:16:44 <StephenMelrose> I adore that all the columns are constants. I hate using strings in Doctrine<br /> 22:16:49 <tobiassjosten> Right, there's some major differences in ORM implementation. :) But is there any difference in goal or approach (non-technical) between the two?<br /> 22:16:56 <Phenix> StephenMelrose: +1<br /> 22:17:01 <francois> tobiassjosten: I guess so<br /> 22:17:02 <lsmith> just FYI Doctrine 1.x and 2.x also provide a fluent interface, but the criteria API is definately a lot more complete<br /> 22:17:22 <francois> lsmith: Propel's Query API is more similar to rails' ARel<br /> 22:17:55 <francois> It's a real way to package your repository logic in a reusable and embeddable way<br /> 22:18:10 <francois> it's not just a magic interface to SQL<br /> 22:18:44 <francois> I was talking about the major difference in approach with Doctrine<br /> 22:19:32 <francois> I think Doctrine2 is very committed to following design patterns<br /> 22:19:39 <francois> sometimes too committed<br /> 22:19:53 <francois> And I also think that this can be counterproductive for the developer<br /> 22:20:02 <francois> IDE friendliness is an example<br /> 22:20:14 <francois> You can't achieve that with DQL<br /> 22:20:34 <francois> Propel's main goal is to make the life of developers easier<br /> 22:20:49 <francois> even if that means having not-so-perfect code insoide<br /> 22:21:01 <francois> At least that's the way I see it<br /> 22:21:11 <Jarda> +1<br /> 22:21:17 <francois> which is a good thing, since I'm quite a poor developer myself ;)<br /> 22:21:23 <grEvenX> haha<br /> 22:21:33 <LvanderRee> still ?<br /> 22:21:34 <LvanderRee> ;)<br /> 22:21:36 <annismckenzie> I think we all know that the main difference between Doctrine and Propel is code generation vs. no code generation. :)<br /> 22:21:36 <grEvenX> IDE sure helps me thorugh the day<br /> 22:21:39 <grEvenX> (and coffee)<br /> 22:21:40 <francois> That's also why the Doctrine2 guys seem to loathe ActiveRecord<br /> 22:21:44 <lsmith> using the Doctrine DBAL could already allow sharing a connection across plugins<br /> 22:21:56 <francois> lsmith: I'm all for it<br /> 22:21:57 <StephenMelrose> It should make life easier for the developer, yes, as long as the "not-so-perfect" code doesn't hinder performance<br /> 22:22:04 <lsmith> this would mean that it would be quite possible to mix and patch inside a single project<br /> 22:22:14 <francois> StephenMelrose: as of now, Propel is still faster than Doctrine<br /> 22:22:15 <lsmith> which would be of course a huge leap forward<br /> 22:22:18 <tobiassjosten> grEvenX: I can't stand IDEs. Been trying for the last decade but I can't come to odds with them.<br /> 22:22:47 <francois> tobiassjosten: Propel is also a lot faster with just a text editor<br /> 22:22:47 <annismckenzie> And that you can only provide good IDE support with generated code, especially with filterBy* methods and such.<br /> 22:23:00 <francois> It's one of the most concise ORMs<br /> 22:23:03 <grEvenX> tobiassjosten: I know your type! :P I just converted a long-time vim user to Eclipse :P<br /> 22:23:22 <francois> So, to sum up my vision<br /> 22:23:30 <francois> (and after that I let you guys talk)<br /> 22:23:30 <tobiassjosten> francois: Right, I was speaking more on the magic argument. Though I'm all for ditching magic still.<br /> 22:23:37 <b00giZm> i still prefer textmate over any kind of IDE, but as long as i can debug my code well, i'm fine. and that's really one thing i can't achieve with doctrine. so, you're right ;)<br /> 22:23:45 <StephenMelrose> I hate magic<br /> 22:24:04 <jwage> Sorry I missed things<br /> 22:24:10 <francois> Propel2 should be an ActiveRecord implementation built on top of Doctrine2 DBAL, using code generation to provide the same Query API<br /> 22:24:17 <francois> and one more thing<br /> 22:24:32 <francois> Propel2 should be backwards compatible with Propel 1<br /> 22:24:40 <LvanderRee> I don't think runtime speed is the MOST important issue (even for an ORM), development speed is.<br /> 22:24:42 <annismckenzie> Wait, what? :)<br /> 22:24:51 <StephenMelrose> IN what sense BC?<br /> 22:24:53 <annismckenzie> Awesome!<br /> 22:24:56 <b00giZm> whoaah<br /> 22:24:58 <b00giZm> :)<br /> 22:25:01 <LvanderRee> hi john nice to see you here as well<br /> 22:25:20 <lsmith> francois: hmm what would be the implication of using the EntityManager?<br /> 22:25:24 <francois> BC in the sense that code written for Propel 1 could still work with Propel 2<br /> 22:25:26 <StephenMelrose> LvanderRee: Erm, no. Runtime >>>>>>>>> Development<br /> 22:25:38 <StephenMelrose> francois: AH, you mean in the generated model?<br /> 22:25:41 <francois> lsmith: using the metadata classes as well<br /> 22:25:42 <StephenMelrose> I'm down for that<br /> 22:26:14 <francois> We've spent a long time defining a very usable API for both the ActiveRecord and the (Active)Query classes<br /> 22:26:18 <francois> let's not ditch that<br /> 22:26:25 <StephenMelrose> +1<br /> 22:26:30 <francois> BC is not an absolute goal<br /> 22:26:50 <LvanderRee> I don' t know about BC either. There are some serieus issues with Propel <1.3 regarding security (with the complex joins)<br /> 22:26:52 <greg1> francois: you mean «easy to upgrade to» <br /> 22:26:56 <annismckenzie> Now, francois could you tell us what the next killer feature you're constantly twittering about is in Propel 1.6? :)<br /> 22:26:59 <LvanderRee> breaking BC can fix those issues<br /> 22:27:03 <francois> it's just a guideline to say: we already have a good end user interface. Just make the thing better inside<br /> 22:27:23 <francois> LvanderRee: that could justify a BC break<br /> 22:27:34 <LvanderRee> OK than I agree<br /> 22:27:35 <francois> annismckenzie: Migrations<br /> 22:27:38 <StephenMelrose> Ah yes, joins. This is something we seriously need to visit.<br /> 22:27:45 <grEvenX> the only thing I currently don't see as "trivial" in the ModelCriteria is relatively simple queries with "and/or" (e.g. WHERE colA != 'B' OR colA = 'C')<br /> 22:27:46 <StephenMelrose> It's a pain in the add doing complex joins<br /> 22:27:51 <LvanderRee> stick as close as possible to the current API, which indeed is very nice!<br /> 22:27:59 <grEvenX> but I don't have any suggestions for improvement either<br /> 22:28:08 <annismckenzie> Yes!!! :) Thanks, francois!<br /> 22:28:14 <lsmith> francois: like i said basing things on the Doctrine DBAL is a great move, but i am not yet sure if it doesnt make sense to base the active record on the Doctrine ORM<br /> 22:28:20 <grEvenX> (and improvements could still be done there I guess without breaking BC)<br /> 22:28:22 <b00giZm> the query api is great, but BC with the old criteria crap? oh lord... <br /> 22:28:27 <LvanderRee> We aren't it calling 1.x for reason, BC can be broken (although preferably not of course, to keep upgrading easy)<br /> 22:28:36 <francois> lsmith: we can use some parts<br /> 22:28:44 <francois> b00giZm: no way<br /> 22:28:48 <greg1> LvanderRee: +1<br /> 22:28:53 <b00giZm> THANKS! :)<br /> 22:29:03 <lsmith> but i have another point to bring up .. unit testing .. i dont have that much experience with propel, but in the code i have seen .. there are a lot of static method calls<br /> 22:29:12 <greg1> otherwise, what would be differences between Propel 1.6 and Propel 2.0 ?<br /> 22:29:13 <francois> lsmith: no anymore<br /> 22:29:16 <lsmith> which can make unit testing harder .. is that addressed via code generation?<br /> 22:29:25 <lsmith> ah ok<br /> 22:29:46 <francois> Propel 1.6 currently has 3785 unit tests<br /> 22:29:56 <francois> it's not hard to test at all<br /> 22:30:01 <annismckenzie> :O<br /> 22:30:10 <lsmith> well i am more talking about unit testing my model business logic<br /> 22:30:16 <francois> 'static' is so Propel 1.3 ;)<br /> 22:30:26 <lsmith> without having to have a db running<br /> 22:30:32 <francois> lsmith: well, you need a storage for that<br /> 22:30:38 <LvanderRee> lsmith this was the case with peer caslles, now there are queries <br /> 22:30:41 <francois> even if it is a test storage<br /> 22:30:44 <lsmith> which requires introducing mock query instances for example<br /> 22:30:44 <grEvenX> lsmith: setup sqlite storage?<br /> 22:30:57 <francois> lsmith: if you use Symfony, you can have different storages for prod and dev<br /> 22:31:02 <lsmith> but if the static calls are gone .. its all good :)<br /> 22:31:03 <b00giZm> @grEvenX that's not trivial for propel 1.x<br /> 22:31:22 <b00giZm> i've been through this ^^ <br /> 22:31:23 <francois> b00giZm: it is within symfony<br /> 22:31:41 <grEvenX> b00giZm: what's the issues with it? ( I never tried )<br /> 22:31:43 <francois> lsmith: about your question on reusing Doctrine ORM<br /> 22:32:08 <Jarda> b00giZm: I've used propel with sqlite, no problems at all<br /> 22:32:29 <francois> I think we could reuse the XML/YAML/Annotation drivers and metadata classes<br /> 22:32:38 <b00giZm> @Jarda - mysql for dev/prod AND sqlite for testing?<br /> 22:32:42 <b00giZm> tell me about it :)<br /> 22:33:03 <francois> As for persisters, Proxies, and Queries, Propel would have a different approach<br /> 22:33:09 <Jarda> b00giZm: I hate mysql from the bottom of my heart :)<br /> 22:33:14 <annismckenzie> francois: Where do you want to start with the change to the Doctrine DBAL? And when — and who? Those are a few things you wanted to talk about today.<br /> 22:33:34 <francois> eventually, code generation should lead to SQL code generation, and the speed that comes with it<br /> 22:33:48 <XpLo^bn> we use sqlite with propel 1.3 since 1 year in prod with embeded java program transfering the sqlite file to the php server without any prob (something like 5000 user)<br /> 22:33:54 <grEvenX> well, it for sure would have to be someone allready familiar with the Doctrine stuff I would say :P<br /> 22:34:06 <grEvenX> at least, that's a big +<br /> 22:34:07 <francois> annismckenzie: indeed<br /> 22:34:15 <XpLo^bn> so no problem there<br /> 22:34:34 <lsmith> i agree that with the approach you layed down for Propel2 .. you want to do the SELECT SQL generation yourself<br /> 22:34:38 <francois> but first, does anybody feels not comfortable with the directions I wrote earlier?<br /> 22:34:46 <Jarda> but as I see it, starting to write propel 2.0, would mean development on 1.x should be at least slown down a bit. You can't rewrite and keep up at the same time :)<br /> 22:34:54 <francois> lsmith: indeed. No DQL in Propel<br /> 22:34:58 <lsmith> and Doctrine ORM allows you to use plain SQL and map back to Entities<br /> 22:35:19 <francois> lsmith: That's what we would keep<br /> 22:35:47 <LvanderRee> francois I had the same idea, so very comfortable +++<br /> 22:35:49 <jwage> Have things in Doctrine ORM been properly evaluated?<br /> 22:35:52 <lsmith> but i dont see any reason at first sight .. why the Entities themselves cannot be Doctrine ORM Entities .. simply with code added to them during the code generation<br /> 22:36:06 <jwage> I mean, are you sure that the things you want to do cannot be accomplished using the ORM components?<br /> 22:36:15 <lsmith> which would also allow you to prevent DQL to be triggered for lazy loading properties etc<br /> 22:36:15 <grEvenX> francois: could you pinpoint some of the benefits of using parts of Doctrine 2 (for those not familiar with the framework)<br /> 22:36:23 <tobiassjosten> francois: I still feel it's a shame that all these brilliant people are working in two different projects. But I digress, I have too little experience in Propel.<br /> 22:36:25 <Jarda> so the decision on starting with 2.0 must be something people are commited to. We can't risk on stopping to develop 1.x if nothing happens in 2.0<br /> 22:36:29 <jwage> Because I've been reading through the logs here and all the things francois wants to do is totally capable on top of the ORM components.<br /> 22:36:59 <francois> jwage: It's kind of what we are talking about :)<br /> 22:37:04 <lsmith> jwage: i think its clear that they really dont want to go through the DQL layer .. but i think thats totally supported by the ORM<br /> 22:37:07 <jwage> you're not forced to use DQL so the code completion and such is not an issue<br /> 22:37:40 <jwage> it would be possible to have the Propel Query API generate Raw SQL<br /> 22:37:44 <jwage> and a ResultSetMapping<br /> 22:37:45 <LvanderRee> jwage good point<br /> 22:37:55 <LvanderRee> and you should probably know ;)<br /> 22:37:59 <francois> jwage: that's what we've been saying earlier<br /> 22:38:00 <lsmith> to me Propel would basically enrich Doctrine 2.x models with the code necessary to generate SQL straight<br /> 22:38:02 <jwage> and this can be used to let Doctrine DBAL fetch the data and Doctirne hydrate the data and entities<br /> 22:38:07 <jwage> lsmith: exactly<br /> 22:38:11 <jwage> it is just yet another abstraction<br /> 22:38:23 <jwage> Doctrine ORM is a much lower level abstraction to object persistence than something like active record<br /> 22:38:32 <jwage> so instead of trying to fit both of these levels of abstraction in one library<br /> 22:38:37 <annismckenzie> I smell a new project added to the Doctrine Jira named "PropelExtension".<br /> 22:38:38 <jwage> it should be layers, AR goes on top of this<br /> 22:38:59 <francois> jwage: yep<br /> 22:39:06 <b00giZm> weren't we kinda talking about that all the time? i'm confused...<br /> 22:39:10 <jwage> so Propel can give a very rich and deep UI for development<br /> 22:39:10 <francois> except that sometimes, a layer on top should go under<br /> 22:39:16 <francois> letme explain<br /> 22:39:29 <lsmith> francois: with the code generation i presume you also get around having to use a common base class, which is an annoying side effect of many PHP AR implementations?<br /> 22:39:43 <francois> lsmith: not necessarily<br /> 22:39:53 <francois> actually, the Propel BseClass is quite small<br /> 22:39:55 <jwage> i don't uderstand why it has to be ActiveRecord.<br /> 22:40:00 <jwage> you can give the same rich Query API and everything<br /> 22:40:03 <jwage> without it being ActiveRecord<br /> 22:40:10 <XpLo^bn> one of the reason i didnt use doctrine was the unsupported composite foreign key in 1.x, it seems it s still not supported in 2.x, so i wouldnt really like a propel on top of doctrine ~<br /> 22:40:12 <jwage> you can maintain transparency and still offer the rich UI goodies<br /> 22:40:32 <StephenMelrose> Please don't ditch ACtiveRecord!<br /> 22:40:42 <StephenMelrose> It's the main reason I use Propel<br /> 22:40:43 <lsmith> XpLo^bn: yeah .. its still an open issue .. to be addressed in 2.1 .. but its sure is important!<br /> 22:40:44 <francois> jwage: Propel2 will be an ActiveRecord<br /> 22:40:53 <francois> some people, like me, prefer that<br /> 22:40:57 <jwage> the only difference is where you call methods to persist an object right?<br /> 22:40:58 <Jarda> jwage: I like my $model->save(), $model->delete() etc :)<br /> 22:41:01 <jwage> or what am I missing?<br /> 22:41:12 <jwage> it is a bottle neck too for batch operations<br /> 22:41:18 <Jarda> I hate doing "$manager->delete($model);"<br /> 22:41:25 <jwage> multiple ->save(), ->save() versus one ->flush() call<br /> 22:41:39 <jwage> one flush is faster for lots of objects than multiple ->save calls<br /> 22:42:12 <francois> ActiveRecord provides sevral features directly to the Entity classes<br /> 22:42:23 <jwage> you have some other methods? like toArray()<br /> 22:42:25 <jwage> and some other stuff?<br /> 22:42:45 <lsmith> jwage: well i do think that Propel probably should stick with AR, but of course underneath it could use the Doctrine EntityManager<br /> 22:42:47 <greg1> Model::save(array($obj1, $obj2 ...))<br /> 22:42:48 <jwage> so the difference is calling the methods on the objects instead of passing the objects to methods on another object<br /> 22:42:50 <francois> like the ability to persist/delete/detach themselves, validation, the ability to be transformed into XML or JSON<br /> 22:42:54 <francois> and most of all<br /> 22:43:01 <francois> ActiveRecord allows for behaviors<br /> 22:43:17 <jwage> hmm its not necessary if you have code generation though<br /> 22:43:19 <grEvenX> +1 for not ditching AR<br /> 22:43:33 <francois> jwage: that's what I was trying to explain<br /> 22:43:39 <francois> but you keep stopping me<br /> 22:43:43 <francois> please<br /> 22:43:57 <francois> What we could do with code generation<br /> 22:43:59 <lsmith> having the call on the model itself simplifies some things .. of course it comes with its own bag of limitations .. especially when one needs to do multiple things inside a transaction<br /> 22:44:06 <francois> is to generate base classes for Entities<br /> 22:44:16 <francois> not extending a single BaseClass<br /> 22:44:26 <lsmith> +1<br /> 22:44:30 <francois> but carrying intelligence<br /> 22:44:31 <jwage> agreed<br /> 22:44:42 <ZaraXVI> uh<br /> 22:44:55 <francois> the BaseEntity could itself extend another class, allowing for real class inheritance<br /> 22:45:17 <ZaraXVI> you're going to go into runtime introspection like doctrine rather than code generation ?<br /> 22:45:20 <francois> the BaseEntity would be regenerated with each storage/model change<br /> 22:45:32 <francois> ZaraXVI: nope, quite the opposite<br /> 22:45:38 <ZaraXVI> ah<br /> 22:45:42 <ZaraXVI> j'ai eu peur<br /> 22:45:43 <francois> Propel has a generation phase, that's what makes it fast<br /> 22:45:48 <ZaraXVI> yea<br /> 22:45:51 <francois> let me explain how I see things<br /> 22:45:57 <ZaraXVI> that's why i choose propel ^^ <br /> 22:46:02 <lsmith> ZaraXVIII: its similar to what traits its doing on the engine level, but copying code into the model class<br /> 22:46:14 <francois> 1 - you create an entity class that extends nothing. A POPO, as they say<br /> 22:46:20 <lsmith> into each model class<br /> 22:46:21 <francois> lsmith: exactly<br /> 22:46:34 <francois> 2 - you add mapping with whatever driver you want<br /> 22:46:48 <francois> 3 you launch AR generation, and it does two things:<br /> 22:47:08 <francois> 3.1 : it generates a BaseEntity for your Entity, extending nothing<br /> 22:47:21 <francois> 3.2 it modifies your entity to make it extend BaseEntity<br /> 22:47:43 <francois> Now you have a usability layer on top of your Entity, but in fact it's underneath<br /> 22:47:57 <francois> It's Doctrine's Proxy classes taken from the other way around<br /> 22:48:06 <francois> Is it clear?<br /> 22:48:16 <grEvenX> sounds good to me<br /> 22:48:21 <b00giZm> sounds smart :)<br /> 22:48:25 <francois> Now take the same idea<br /> 22:48:33 <francois> and apply it to Doctrine Repositories<br /> 22:48:39 <lsmith> so the BaseEntity is something the user will never touch with his own hands<br /> 22:48:42 <francois> and you have PropelQueries<br /> 22:48:57 <francois> lsmith: indeed, the code still goes to the entity class<br /> 22:49:21 <annismckenzie> Hmm, I'm probably a bit slow today so if someone could explain that to me again, I'd be very glad (what francois just said).<br /> 22:49:27 <jwage> francois: would it be possible to have a sort of traits/multiple inheritance feature in the code generation<br /> 22:49:43 <francois> jwage: that's what we already have in Propel 1 with behaviors<br /> 22:49:47 <francois> and sure, that's a must<br /> 22:49:54 <jwage> but i mean literally write another class<br /> 22:49:59 <jwage> and have a syntax to include that class<br /> 22:50:01 <jwage> and it copies the code<br /> 22:50:04 <lsmith> jwage: i suggested that the code generation should probably be made forwards compatible to be partially replaced by traits once they are available<br /> 22:50:13 <jwage> is that what you mean?<br /> 22:50:16 <francois> jwage: not sure I understand<br /> 22:50:39 <francois> annismckenzie: it's just like Propel 1, except that instead of starting with a schema, you start with the stub classes<br /> 22:50:52 <jwage> imagine in your mapping for a propel entity you could say i want to import the code from another class that is mapped as a trait<br /> 22:51:03 <francois> annismckenzie: in the end, you still manipulate ActiveRecord and ActiveQuery instances the same way<br /> 22:51:19 <jwage> during the code generation phase it would literally open the class and copy the contents of the class in to the generated base entity for that model<br /> 22:51:20 <francois> annismckenzie: we're just discussing the implementation details<br /> 22:51:34 <jwage> so it would be exactly like what traits are just done at code generation time<br /> 22:51:36 <Yrwein> francois__ Would be possible then do add (through configuration) class that will be extended by generated class (generated class will have "extends MyBaseEntityClass")? If I understand that well... -> F. e. for adding features common to all entities.<br /> 22:51:42 <weaverryan> you all started a bit early didn't you ;)<br /> 22:51:45 <francois> jwage: Behaviors already do that, but they do a lot more<br /> 22:51:53 <annismckenzie> weaverryan: You're late! :)<br /> 22:52:04 <francois> jwage: like generating directly the right method names, to avoid relying on magic methods<br /> 22:52:29 <lsmith> Yrwein: i think what francois is explaining here a way to be able to add any functionality you want into a class without having to use a common base class<br /> 22:52:33 <annismckenzie> Thanks francois, I think I got it now. :)<br /> 22:52:34 <lsmith> since the code is copied<br /> 22:52:49 <lsmith> but of course you are free to still force a common base class<br /> 22:52:53 <francois> Yrwein: it would be possible, yes, but lsmith wouldn't be happy ;)<br /> 22:53:39 <grEvenX> jwage: sounds smart<br /> 22:53:42 <francois> One small detail: I think the discussion focuses too much on the AR classes, while Propel's most distinctive features come from the Query API<br /> 22:54:12 <lsmith> francois: by using the EntityManager underneath you could use Doctrine ORM for all writes<br /> 22:54:23 <Yrwein> lsmith, fancois__: Ok then. .)<br /> 22:54:27 <francois> And using the same code generation principles, we can make Doctrine Repository classes extend a BaseRepository class, with all the smart filetrs and so on<br /> 22:54:42 <francois> lsmith: yes, I agree<br /> 22:54:51 <lsmith> which would be kind of cool .. because then i could easily switch to using the Doctrine ORM API in case i need to do something complex in terms of transactions<br /> 22:55:01 <francois> lsmith: indeed<br /> 22:55:16 <lsmith> ok sounds awesome to me! :)<br /> 22:55:18 <LvanderRee> weaverryan, you missed the new feature for 1.6: migrations, but the real discussion is about the future of prope, (propel 2.0) the idea is to use Doctrine 2.0 but add generators on top of it to allow fast development (IDE completion), and fast runtime <br /> 22:55:34 <LvanderRee> weaverryan: details are currently being discussed, with jwage<br /> 22:55:34 <weaverryan> great, thanks LvanderRee :)<br /> 22:55:52 <jwage> francois lsmith: <a href="http://pastebin.com/wWFpUSDP">http://pastebin.com/wWFpUSDP</a><br /> 22:55:59 <jwage> to make sure we're saying the same thing this is what I am thinking<br /> 22:56:01 <jwage> pseudo code<br /> 22:56:07 <Jarda> francois: but I'm actually against defining schemas via stub classes.. I like my xml configuration..<br /> 22:56:20 <lsmith> i like DQL, but i think this is sounding like it really brings what makes Propel 1.x unique into a system that makes it easy for users to mix and match Propel/Doctrine code<br /> 22:56:24 <weaverryan> if propel were built on top of Doctrine ORM, wouldn't that mean that bundles could be made to work with Doctrine or Propel just by being sure to only rely on the lower-level Doctrine ORM functionality?<br /> 22:56:29 <jwage> Jarda: using Doctrine you would be able to do it either way<br /> 22:56:37 <Jarda> jwage: ok, great<br /> 22:56:42 <jwage> weaverryan: that is correct<br /> 22:57:18 <jwage> so you can map some entities, and say this entity uses this other mapped trait<br /> 22:57:24 <jwage> and during code generation it copies the code<br /> 22:57:28 <jwage> is that what propel behaviors does currently?<br /> 22:57:31 <weaverryan> woh<br /> 22:57:59 <lsmith> francois: i guess going by the code generation to enable IDE mantra .. are you considering annotations?<br /> 22:58:07 <lsmith> or do you prefer explict API calls<br /> 22:58:19 <jwage> of course just like traits or multiple inheritance you can have conflicts but that is normal<br /> 22:58:28 <francois> jwage: see my version <a href="http://pastebin.com/eQ0XaBSV">http://pastebin.com/eQ0XaBSV</a><br /> 22:58:51 <annismckenzie> Ah, weaverryan, one thing you really did miss: Propel 2.0 will try to be BC with Propel 1.x!<br /> 22:58:54 <jwage> what does the BaseA provide?<br /> 22:59:14 <jwage> the AR implementation?<br /> 22:59:21 <francois> jwage: yep<br /> 22:59:27 <francois> a lot of code not in the example<br /> 22:59:36 <jwage> ya<br /> 22:59:37 <weaverryan> annismckenzie: oh wow - that's definitely important<br /> 22:59:49 <ZaraXVI> i don't get what all those ideas will bring<br /> 22:59:52 <francois> lsmith: I don't understand<br /> 23:00:00 <jwage> i cant wait until we have traits<br /> 23:00:03 <jwage> and this wont be an issue anymore<br /> 23:00:09 <jwage> hehe<br /> 23:00:09 <francois> jwage: and same for the ARepository class<br /> 23:00:34 <francois> jwage: traits won't solve all the problems Propel behaviors solves<br /> 23:00:41 <lsmith> francois: so you are planning to adopt annotatins for propel 2.0?<br /> 23:00:54 <lsmith> francois: hmm your code looks a lot like <a href="http://wiki.php.net/rfc/horizontalreuse#grafts_-_class_composition_not_implemented">http://wiki.php.net/rfc/horizontalreuse#grafts_-_class_composition_not_implem...</a><br /> 23:01:01 <francois> lsmith: I think the user can define the mapping the way she wants<br /> 23:01:13 <francois> whether using annotations, or any other supported format<br /> 23:01:23 <lsmith> which is the alternate approach discussed for horizontal reuse .. in the end php.net went with traits .. which is more like what jwage's pseudo code is<br /> 23:01:25 <annismckenzie> lsmith: It doesn't matter with all the mapping converters available for Doctrine 2.<br /> 23:01:46 <jwage> ya you can specify mapping in anything<br /> 23:01:50 <francois> lsmith: grafts provide more features that traits, yes<br /> 23:01:56 <jwage> so you can looad the mapping whether it is via xml, yaml or annotations<br /> 23:02:01 <jwage> and use the mapping info to do the code generation<br /> 23:02:46 <lsmith> i obviously agree that you cannot wait for trunk to be released, but i am not sure it makes sense going a different route ..<br /> 23:02:54 <francois> ok, seems that we agree on the main principles. It's maybe not the time to decide the final implementation, though :)<br /> 23:03:12 <lsmith> yeah<br /> 23:03:25 <francois> lsmith: look at Propel behaviors and tell me if you can do the same things with traits<br /> 23:03:43 <francois> I'm sure you'd end up with a lot of __call() and runtime overhead<br /> 23:03:53 <ZaraXVI> much more down to earth, is their a graphical editor that can generate xml or yml schemas for propel ?<br /> 23:03:59 <francois> And no IDE completion<br /> 23:04:08 <arlo> correct me if i'm wrong, we can have the domain model pattern of Doctrine, with some of the characteristics of an Active Record implementation (persistance directly available in the object), and with a fluid query interface on both objects and repository ?<br /> 23:04:09 <francois> ZaraXVI: yes<br /> 23:04:19 <ZaraXVI> francois: which one ?<br /> 23:04:42 <francois> ZaraXVI: <a href="http://www.orm-designer.com">http://www.orm-designer.com</a><br /> 23:05:12 <francois> arlo: what do you mean fluid query interface on object?<br /> 23:05:20 <ZaraXVI> uhuh<br /> 23:05:23 <ZaraXVI> "buy now"<br /> 23:05:30 <annismckenzie> :)<br /> 23:05:42 <arlo> (Active)Query : right, it's not really on object<br /> 23:05:46 <v-dogg> (not everything can be free)<br /> 23:05:56 <lsmith> francois: well i am sure once traits are released they will get IDE support<br /> 23:05:58 <francois> arlo: that's the principles, yes<br /> 23:06:10 <lsmith> but yes traits as they are in trunk are essentially just engine level cut&paste<br /> 23:06:13 <lsmith> and they are stateless<br /> 23:06:16 <lsmith> <a href="http://wiki.php.net/rfc/horizontalreuse#traits_vs">http://wiki.php.net/rfc/horizontalreuse#traits_vs</a>._grafts<br /> 23:06:25 <francois> lsmith: which is their limit<br /> 23:06:28 <ZaraXVI> well, i'd like a free one :)<br /> 23:06:28 <LvanderRee> I think I want to see/implement some things based on the ideas we agree on and then look further about how to specifically solve things. But for now the idea seems to be to extend not only the Doctrine2 Dbal, but also the ORM and add a Propel layer on top of that, isn' t it<br /> 23:06:43 <francois> LvanderRee: yes<br /> 23:06:43 <LvanderRee> so use Doctrine2 with its entities, and entity manager<br /> 23:06:46 <lsmith> so sure in a lot of cases a trait method will have to make an instance of something to be able to implement what you need<br /> 23:07:12 <francois> lsmith: let's discuss this some other time<br /> 23:07:27 <lsmith> francois: yeah .. btw the guy who wrote the RFC is nice and approachable<br /> 23:07:31 <lsmith> but tends to be busy at times<br /> 23:07:43 <Jarda> hey, it's getting very late<br /> 23:07:43 <lsmith> but i would recommend also asking him for advice<br /> 23:07:53 <Jarda> we are in too much detail talk atm<br /> 23:08:15 <lftl> ZaraXVI: there are a couple of option in the free department that just use XSL to transform XML output from other GUI DB design programs<br /> 23:08:25 <francois> So now that we (kinda) agree on the face of Propel2, there are more questions arising<br /> 23:08:26 <francois> Who is interesting in contribiuting?<br /> 23:08:33 <Jarda> couldn't we maybe start talking whether we start doing propel 2.0 and who will commit to developing it?<br /> 23:08:48 <francois> Jarda: :)<br /> 23:09:03 <francois> Who wants to contribute?<br /> 23:09:17 <Phenix> francois: me<br /> 23:09:25 <b00giZm> i smell something big ;) i guess i'll give it a try, francois :)<br /> 23:09:46 <ZaraXVI> hmmm<br /> 23:09:58 <LvanderRee> francois I am as long as I am still developping in php ... ;)<br /> 23:10:03 <Jarda> as I said earlier (it might have drowned in the noise) we can't do heave development in two completely different branches if we don't want to loose features in 2.0<br /> 23:10:06 <grEvenX> I still haven't got this, can somebody please explain the main benefits from me as a Propel user to use Propel 2 ? And the benefits of using Doctrine 2 in this, is it for the Propel devs or for the user (or both)<br /> 23:10:18 <Phenix> I am interested in propel<br /> 23:10:29 <Jarda> *heavy<br /> 23:10:42 <annismckenzie> Please francois, get Propel2 on<br /> 23:10:43 <grEvenX> I can contribute, and get specific task, but I will not be able to take on managing this project<br /> 23:10:52 <francois> grEvenX: access to all PHP5.3 features<br /> 23:11:05 <francois> grEvenX: ability to use Doctrine bundles/projects<br /> 23:11:16 <LvanderRee> francois I have some time tomorrow already, I can see what I can do then, but I am thinking about switching to Python as my main development language....<br /> 23:11:16 <francois> grEvenX: Even more speed<br /> 23:11:22 <grEvenX> (that we like)<br /> 23:11:30 <francois> LvanderRee: great language, bad idea<br /> 23:11:58 <annismckenzie> Damn. Again. francois, could you put development of Propel2 onto GitHub? That would make contributing easier for all of us.<br /> 23:12:10 <grEvenX> francois: thanks for making those things clear :)<br /> 23:12:11 <francois> annismckenzie: definitely<br /> 23:12:17 <weaverryan> excellent :)<br /> 23:12:21 <b00giZm> great :)<br /> 23:12:37 <lsmith> francois: i have my hands in all sorts of other stuff already .. but you can always ask me in case there are questions about RDBMS differences and other nitty gritty SQL details<br /> 23:12:52 <francois> lsmith: great, thanks<br /> 23:12:56 <grEvenX> I guess I'd have to read francois tip on that git "tutorial" then<br /> 23:12:57 <grEvenX> :P<br /> 23:13:09 <annismckenzie> :D<br /> 23:13:23 <lsmith> <a href="http://progit.org/book/">http://progit.org/book/</a> !<br /> 23:13:28 <francois> oh, one last thing<br /> 23:13:33 <b00giZm> you'll like it <br /> 23:13:51 <lsmith> b00giZm: he is really the steve jobs of propel :p<br /> 23:13:51 <francois> What name shall we give to Propel2?<br /> 23:14:05 <b00giZm> Proptrine :D<br /> 23:14:05 <b00giZm> just kiddin<br /> 23:14:08 <annismckenzie> Definitely! If you take a look at how contributions are handled in Symfony2 you'll notice how much more open development becomes with git.<br /> 23:14:41 <Jarda> francois: are you going to take the initiative with Propel2? Or should there be a vote of some kind of the lead?<br /> 23:14:47 <annismckenzie> Doctrel? :D<br /> 23:15:05 <francois> Jarda: If someone feels strong enough to take the mead, I'd be delighted<br /> 23:15:09 <francois> s/mead/lead<br /> 23:15:25 <francois> I already have too much work with Propel 1<br /> 23:15:29 <Jarda> (I can't think of anyone else leading the development, but well.. I'm happy with 1.x and don't feel the need of P2)<br /> 23:15:41 <ZaraXVI> git *blurp*<br /> 23:15:56 <francois> Anyone here wants to lead Propel2?<br /> 23:16:18 <grEvenX> people will have to step up for them selves if they think they can take the lean on Propel 2 (because no-one here know who you are and can "promote" you anyway)<br /> 23:16:18 <grEvenX> :P<br /> 23:16:32 <annismckenzie> Uh oh, silence.<br /> 23:16:36 <Jarda> +1 for changing to git also for 1.x branch, but I'd like to have an up-to-date svn mirror also for externals (the svn:externals from github doesn't work well..)<br /> 23:17:12 <francois> Jarda: We'll update the git mirror, but I think Propel 1.x will keep SVN<br /> 23:17:36 <ZaraXVI> what's with git<br /> 23:17:47 <annismckenzie> Jarda: it works pretty well but you're right in that there can only be a master branch accessible through svn.<br /> 23:17:48 <francois> ok, so I'll boostrap Propel2 in my github account<br /> 23:17:50 <grEvenX> Jar da: I think that is very nice too, but I hope someone has experience with doing svn mirroring from other project... #freeswitch just changed to GIT and they had to spend a _lot_ of time to get it right<br /> 23:17:52 <ZaraXVI> switching to git sounds like masturbation to me<br /> 23:18:01 <LvanderRee> I don't know if I am capable, if I have enough time, and if I will be using Php for a long time anymore... But I very much do like the idea. Collaboration and ease of use for all (plugin)developers <br /> 23:18:08 <lsmith> francois: you can create an organization on github these days<br /> 23:18:27 <francois> lsmith: whoa, an organization!<br /> 23:18:33 <jwage> francois I want to help with the development for sure<br /> 23:18:35 <francois> is there name squatting on Propel ?<br /> 23:18:40 <jwage> francois: why can you not lead it?<br /> 23:19:02 <francois> jwage: well, it seems I have no choice<br /> 23:19:08 <francois> jwage: noone else will<br /> 23:19:13 <lsmith> choice if overrated :)<br /> 23:19:18 <lsmith> is<br /> 23:19:24 <francois> lsmith: good one<br /> 23:19:25 <jwage> you should want to :)<br /> 23:19:31 <Hope-Work> When I try to load a new propel object via TablePeer::fromArray($dataFromCustomSQLQuery), it adds every query to the [modifiedColumns:protected] array, increasing the size of the results *dramatically*. How do i keep this from happening?<br /> 23:19:46 <francois> ah! Hope-Work !<br /> 23:19:50 <francois> welcome<br /> 23:19:58 <francois> We were all expecting you<br /> 23:20:03 <Hope-Work> francois, huh?<br /> 23:20:11 <grEvenX> lol<br /> 23:20:12 <francois> could you all welcome Hope-Work please?<br /> 23:20:13 <IvoAz> :D<br /> 23:20:18 <Hope-Work> is francois a bot?<br /> 23:20:21 <IvoAz> welcome, Hope-Work!<br /> 23:20:22 <grEvenX> hi H<br /> 23:20:22 <grEvenX> Hi Hope-Work<br /> 23:20:23 <dvj> wb Hope-Work! missed you!<br /> 23:20:25 <grEvenX> ;)<br /> 23:20:35 <Hope-Work> !seen Hope-Work<br /> 23:20:54 <Yrwein> Welcome Hope-Work. .))<br /> 23:20:58 <lsmith> Hope-Work: you just asked the lead developer of Propel if he is a bot :P<br /> 23:21:04 <IvoAz> what is the new feature we hear on the twitter? ;)<br /> 23:21:08 <Hope-Work> bah i'm sorry<br /> 23:21:12 <b00giZm> :D<br /> 23:21:20 <Hope-Work> i'm not used to being welcomed so cordially by humans<br /> 23:21:21 <annismckenzie> I guess that was an inside joke. ;-)<br /> 23:21:39 <LvanderRee> haha great!<br /> 23:21:48 <grEvenX> I'd feel very happy with francois taking the lead, he has shown great comitment to the Propel project and great skills<br /> 23:21:53 <francois> Hope-Work: sorry, it's just funny 'coz we've been taling for an hour about the future of Propel, and you come here and ask a question about the past of Propel<br /> 23:22:05 <LvanderRee> a real user ;)<br /> 23:22:17 <francois> LvanderRee: inded<br /> 23:22:22 <b00giZm> how many people are on the core team of propel 1.x? is it just you, francois?<br /> 23:22:41 <francois> Hope-Work: try $obj->clearModifiedColumns()<br /> 23:22:44 <grEvenX> who is KrAken?<br /> 23:22:49 <LvanderRee> what is core <br /> 23:22:52 <LvanderRee> KRavEN<br /> 23:22:58 <Hope-Work> francois, i am glad it is the past. My company is stuck forever w/ a horribly ancient v of propel i can find no documentation on.<br /> 23:23:01 <francois> b00giZm: There is no core team per se<br /> 23:23:03 <grEvenX> KRaVEN even<br /> 23:23:09 <LvanderRee> KRavEN is an american, helping out especially with MSsql<br /> 23:23:38 <grEvenX> i've seen there has been some commits from him<br /> 23:23:39 <Hope-Work> francois, from one PHP pro to another, I would love an ORM that actually implemented ArrayAccess.<br /> 23:23:48 <Hope-Work> i use that in my own simplistic ORM<br /> 23:23:51 <francois> there are five people with commit access if that's what you want to know<br /> 23:23:58 <lsmith> ok ... i was already dead 30min before the meeting .. time to go into coma for me<br /> 23:24:06 <francois> Hope-Work: Use Doctrine1<br /> 23:24:33 <lsmith> good night all!<br /> 23:24:34 <francois> lsmith: thanks for your time<br /> 23:24:37 <LvanderRee> byebye<br /> 23:24:41 <grEvenX> lsmith: thanks for the your povs, good night :)<br /> 23:24:44 <Phenix> lsmith: bye<br /> 23:24:48 <Jarda> ok, well, I'm off to bed. I can send the log to you francois tomorrow<br /> 23:24:55 <Hope-Work> Call to undefined method Hierarchy::clearModifiedColumns() <br /> 23:25:00 <Hope-Work> sigh<br /> 23:25:01 <francois> Are there any more subjects we need to discuss tonight?<br /> 23:25:01 <b00giZm> so there's someone who could take the lead in the propel 1.x branch if you take the lead in 2.x<br /> 23:25:11 <IvoAz> the new feature?<br /> 23:25:18 <IvoAz> :)<br /> 23:25:20 <Jarda> IvoAz: migrations<br /> 23:25:24 <annismckenzie> Migrations!<br /> 23:25:26 <Jarda> IvoAz: follow the tickets!<br /> 23:25:37 <grEvenX> :P<br /> 23:25:41 <francois> Hope-Work: sorry, try resetModified()<br /> 23:25:51 <LvanderRee> francois when will you create the base for Propel2 on github?<br /> 23:25:56 <francois> b00giZm: not really<br /> 23:26:05 <francois> LvanderRee: not tonight<br /> 23:26:17 <LvanderRee> :D need some sleep as well ;)<br /> 23:26:33 <XpLo^bn> will propel 1.x stop providing new feature when 2.x start ?<br /> 23:26:51 <Hope-Work> francois, thank you, sir, for your help and time and the project.<br /> 23:26:56 <annismckenzie> Will 1.6 be the last?<br /> 23:27:01 <francois> XpLo^bn: My company (and others) use Propel 1.X and will keep on using it for a long time<br /> 23:27:15 <francois> XpLo^bn: that's a good insurnce that the 1.x branch will live<br /> 23:27:18 <LvanderRee> As said, I have some time tomorrow, but will not start before a base has been created on git<br /> 23:27:25 <annismckenzie> In Propel 1.x I mean.<br /> 23:27:34 <francois> although I can't guarantee the same pace in improvements/bugfixes<br /> 23:27:49 <IvoAz> migrations is great one, GJ :)<br /> 23:27:51 <francois> If any of you wants to give a hand or take the lead on Propel 1.x, tell me<br /> 23:28:01 <francois> annismckenzie: I guess so<br /> 23:28:12 <XpLo^bn> of course that would be crazy to keep the same pace with 2 branch:)<br /> 23:28:39 <LvanderRee> francois Ow and one other question, have got some time to take a look at the complex-join branch? It is compatible, but do you like it? can it be merged in 1.6<br /> 23:28:54 <francois> LvanderRee: I need to dedicate a few hours to it<br /> 23:29:05 <francois> I like it, but I still have to validate a few stuff<br /> 23:29:15 <francois> Propel 1.6 definitely needs this<br /> 23:29:19 <francois> so don't be afraid<br /> 23:29:35 <francois> (I've been kind of occupied with the migrations stuff lately)<br /> 23:29:46 <LvanderRee> francois no problem, thing is that I don' t like it to be for nothing, and me requiring to keep on patching propel for my work to be able to perform complex joins <br /> 23:30:03 <annismckenzie> Complex-join branch — care to explain a little bit, LvanderRee?<br /> 23:30:54 <b00giZm> having joins with ... JOIN t ON t.foo_id = f.id AND ... ?<br /> 23:30:54 <francois> Ok guys, time to finish the "Future of Propel2 meeting"<br /> 23:30:58 <b00giZm> ok<br /> 23:31:07 <annismckenzie> sounds good<br /> 23:31:09 <francois> It was nice talking to you all<br /> 23:31:25 <francois> don't hesitate to continue the discussion on the Propel-devs mailing-list<br /> 23:31:39 <annismckenzie> Thanks for the awesome work done in Propel 1.4 and up!<br /> 23:31:44 <francois> and watch my github acount for the Propel2 bootstrap<br /> 23:31:56 <LvanderRee> annismckenzie I am one of those who has commit rights, and created a branch to solve some issues, see ticket: <a href="http://www.propelorm.org/ticket/878">http://www.propelorm.org/ticket/878</a><br /> 23:32:09 <francois> <a href="http://github.com/fzaninotto">http://github.com/fzaninotto</a><br /> 23:32:13 <Phenix> francois: thank for this meeting<br /> 23:32:28 <weaverryan> very good to listen in guys - I look forward to seeing things get rolling - lot's of excellent ideas<br /> 23:32:33 <weaverryan> I feel smarter now :)<br /> 23:32:39 <annismckenzie> b00giZm: That already works with addMultipleJoin() (actually had to use it today)<br /> 23:33:09 <francois> annismckenzie: addMultipleJoin() is flawed<br /> 23:33:20 <francois> it has an SQL injection vulnerability<br /> 23:33:45 <XpLo^bn> definitly awesome work on propel francois , you re so good that no one dare take the lead :D<br /> 23:33:54 <francois> annismckenzie: that's what LvanderRee's branch is for<br /> 23:34:18 <francois> XpLo^bn: I'm really not attached to it. Do you want it?<br /> 23:34:26 <b00giZm> yeah i know, i wrote a blog posting about it... i would post the link but unfortunately, posterous screwed up their markdown-support and my blog looks like crap ;)<br /> 23:34:27 <XpLo^bn> lol no way haha<br /> 23:35:03 <XpLo^bn> i couldnt follow any of your talk about doctrine <=> propel, i dont understand anything in doctrine blog ~<br /> 23:35:06 <Yrwein> Thanks for this meeting too and Francois and others for developing propel. See ya later. <br /> 23:35:37 <francois> XpLo^bn: from an end user's point of view, Propel2 won't change much<br /> 23:35:46 <francois> The difference will be on the inside<br /> 23:35:56 <XpLo^bn> i ll keep helping ppl on forum/maling list, that s all i can do<br /> 23:36:10 <francois> XpLo^bn: please continue :)<br /> 23:36:32 <francois> ok, I'm off<br /> 23:36:42 <francois> good night/day everybody!<br /> 23:36:48 <b00giZm> see you ;) good night!<br /> 23:36:54 <XpLo^bn> night ~<br /> 23:36:58 <grEvenX> nice talking to you all<br /> 23:36:58 <Phenix> francois: bye<br /> 23:37:02 <arlo> bye<br /> 23:37:03 <LvanderRee> good night<br /> 23:37:05 <grEvenX> need to hed to bed here as well<br /> 23:37:20 <LvanderRee> I will be off too guys, see you online next time ;)<br /> 23:37:44 <grEvenX> and remember, #propel is open 24/7<br /> 23:37:45 <grEvenX> ;)<br /> 23:38:29 <b00giZm> bye ;) nice talking.</p>
Propel 2 : What could it be, and do you want to be part of it?2010-09-06T00:00:00+00:00http://www.propelorm.org/blog/2010/09/06/propel-2-what-could-it-be-and-do-you-want-to-be-part-of-it-<p>It's been only a year since the Propel project changed hands. In the meantime, the 1.x branch saw two major releases (1.4 and 1.5), and keeps evolving at a good pace.</p>
<p />
<div>However, the 2.x branch can now officially be considered abandoned. In a year, not a single line was committed to the trunk, and the discussion about the architecture and the features of Propel 2 never really kicked off.</div>
<p />
<div>That's why I'd like to start again the discussion about Propel 2. I have a few ideas, but we need a lead developer for this branch - and it can't be me. I'm sure many of you have ideas as well. Hopefully, some of you may have a few spare hours to contribute.</div>
<p />
<div>Come and talk about Propel 2 on the #propel channel in freenode (IRC) on Thursday 9th, 22pm Paris time (CEST). That's 8pm in Reykjavik (UTC), or 4pm in New York (EDT). </div>
If your model classes are empty, you didn't get the best of ORM2010-08-27T00:00:00+00:00http://www.propelorm.org/blog/2010/08/27/if-your-model-classes-are-empty-you-didn-t-get-the-best-of-orm<p>I see a lot of projects carrying very lightweight model classes: the stub ActiveRecord and Query classes generated by Propel remain empty after a while. This is not only a sign that the developers put the code in the wrong place, but also that they still don’t get a grasp of the ORM paradigm. Let’s see a few examples that illustrate that.</p>
<h3>The ActiveRecord Classes Are Where The Record Manipulation Should Be</h3>
<p>In a phone book application, a Person has a first name, a last name, a gender, and a marital status. In the view layer of the application, the developer wrote a helper function that displays a Person’s identity, which applies a few presentation rules according to the available data:</p>
<div class="CodeRay">
<div class="code"><pre>function getPersonIdentity($person)
{
if ($gender = $person->getGender()) {
if (strtolower($gender) == 'male') {
$title = 'Mr.';
} else {
if ($person->getMaritalStatus() == 'married') {
$title = 'Mrs.';
} else {
$title = 'Miss';
}
}
} else {
$title = '';
}
if ($person->getFirstName() && $person->getLastName()) {
return $title . $person->getFirstName() . ' ' . $person->getLastName();
} elseif ($person->getLastName()) {
return $title . $person->getLastName();
} elseif ($person->getFirstName()) {
return $title . $person->getFirstName();
} else {
return 'Mr. Nobody';
}
}</pre></div>
</div>
<p>This helper function could be broken down into several smaller functions to increase reusability. For instance, the line that determines the title of a person could be turned into a standalone <code>getTitle()</code> function:</p>
<div class="CodeRay">
<div class="code"><pre>function getTitle($person)
{
if(!$gender = $person->getGender()) {
return false;
}
if (strtolower($gender) == 'male') {
return 'Mr.';
} else {
if ($person->getMaritalStatus() == 'married') {
return 'Mrs.';
} else {
return 'Miss';
}
}
}</pre></div>
</div>
<p>In this last function, the line that determines if a woman is married could also be isolated:</p>
<div class="CodeRay">
<div class="code"><pre>function isMarried($person)
{
return $person->getMaritalStatus() == 'married';
}</pre></div>
</div>
<p>Note that this piece of code can now be reused for men, while it was only used to determine if a woman was married or not.</p>
<p>The process of isolating functions is good for reusability, but quite bad for code maintenance. All these standalone helper functions pollute the global namespace, and even if they are in a common helper file, they don’t share anything in common with each other. Or do they?</p>
<p>They actually share one important thing: their parameter, a Person instance. This should ring a bell and draw your attention to the ActiveRecord model classes. With a little refactoring, the first helper function can be entirely moved into the Person class, to make it fully reusable – including in parts of the application that don’t have access to the helper functions of the view layer:</p>
<div class="CodeRay">
<div class="code"><pre>class Person extends BasePerson
{
const SINGLE = 'single';
const MARRIED = 'married';
public function hasMaritalStatus()
{
return null !== $this->getMaritalStatus();
}
public function isMarried()
{
return $this->getMaritalStatus() == self::MARRIED;
}
public function isSingle()
{
return !$this->isMarried();
}
const FEMALE = 'female';
const MALE = 'male';
public function hasGender()
{
return null !== $this->getGender();
}
public function isFemale()
{
return strtolower($this->getGender()) == self::FEMALE;
}
public function isMale()
{
return !$this->isFemale();
}
public function getTitle()
{
if(!$this->hasGender()) {
return false;
}
if ($this->isMale()) {
return 'Mr.';
} else {
if (!$this->hasMaritalStatus() || $this->isMarried()) {
return 'Mrs.';
} else {
return 'Miss';
}
}
}
public function getFullName()
{
if ($this->getFirstName() && $this->getLastName()) {
return $this->getFirstName() . ' ' . $this->getLastName();
} elseif ($this->getLastName()) {
return $this->getLastName();
} elseif ($this->getFirstName()) {
return $false->getFirstName();
} else {
return false;
}
}
const UNKOWN_NAME = 'Mr. Nobody';
public function getIdentity()
{
if (!$fullName = $this->getFullName()) {
return self::UNKOWN_NAME;
}
if ($title = $person->getTitle()) {
return $title . ' ' . $fullName;
} else {
return $fullName;
}
}
}</pre></div>
</div>
<p>That’s a lot of new methods, but now they are bundled together into a single place, and this is where they belong. The developer can unit test them, and reuse them very easily across all the application.</p>
<p>These new methods make the code much easier to read. Even if you don’t know how the gender is stored in the database, you can use the <code>isMarried()</code> method. In practice, these methods abstract the storage structure, and offer an easy-to-use interface to the stored data.</p>
<p>This refactoring to the ActiveRecord class is a pretty basic OOP technique, but many developers tend to oversee it. Some of them come to ORMs with a simple PHP background, and they are not used to spotting which code is part of the model. Some others take the ORM classes as a place to put database queries and nothing else.</p>
<h3>The Query Classes Are Where The Queries Should Be</h3>
<p>In a CMS application, a Section has many Articles. In order to display the list of latest articles, the developer wrote a <code>getPublishedArticles()</code> in the <code>Section</code> ActiveRecord class:</p>
<div class="CodeRay">
<div class="code"><pre>class Section extends BaseSection
{
public function getPublishedArticles()
{
return ArticleQuery::create()
->filterBySection($this)
->filterByPublishedAt(array('max' => time())
->orderByPublishedAt('desc')
->find();
}
}</pre></div>
</div>
<p>But the piece of logic that determines if an article is published or not must be repeated to <em>count</em> the published articles. Or, it could be required in another model, for instance to find the published articles <em>by an author</em>. Therefore, this piece of logic should be written in the <code>ArticleQuery</code> class. After all, it’s an Article filter:</p>
<div class="CodeRay">
<div class="code"><pre>class ArticleQuery extends BaseArticleQuery
{
public function published()
{
return $this->filterByPublishedAt(array('max' => time());
}
}</pre></div>
</div>
<p>This new method already shows a great virtue : you can unit test it. Also, it has a meaningful name, that expresses domain logic rather than storage logic. Imagine if the <code>published_at</code> column was named <code>art_pub_date</code>, and you will get a better idea of the benefit of a meaningful name.</p>
<p>Now the ActiveRecord method is easier to write and read:</p>
<div class="CodeRay">
<div class="code"><pre>class Section extends BaseSection
{
public function getPublishedArticles()
{
return ArticleQuery::create()
->filterBySection($this)
->published()
->orderByPublishedAt('desc')
->find();
}
}</pre></div>
</div>
<p>The developer should even go further and package all the filtering logic into the Query class:</p>
<div class="CodeRay">
<div class="code"><pre>class ArticleQuery extends BaseArticleQuery
{
public function published()
{
return $this->filterByPublishedAt(array('max' => time());
}
public function recent()
{
return $this->orderByPublishedAt('desc');
}
public function recentlyPublished()
{
return $this->recent()->published();
}
}</pre></div>
</div>
<p>Also, the generated <code>BaseSection::getArticles()</code> method already filters by the current Section, and terminates the query: these pieces of code should be reused rather than rewritten. And since the generated Foreign Key getters accept a Query object as parameter, you could write the <code>getPublishedArticles()</code> method in a single line:</p>
<div class="CodeRay">
<div class="code"><pre>class Section extends BaseSection
{
public function getPublishedArticles()
{
return $this->getArticles(ArticleQuery::create()->recentlyPublished());
}
}</pre></div>
</div>
<p>So what happened here? The ActiveRecord class got stripped of most of its code in favor of the Query class. And the ActiveRecord class no longer manipulates columns. It deals with expressive filters rather than database conditions.</p>
<p>Here is a good rule of thumb: If you’re using <code>filterByXXX()</code>, <code>orderByXXX()</code>, <code>useXXXQuery()</code>, or <code>find()</code> in an ActiveRecord class, you should probably move some code to the Query class. Your ActiveRecord classes should only use meaningful filters, and let the Query classes offer reusability and testability to filtering logic.</p>
<h3>The Model Classes Are Where The Model Logic Should Be</h3>
<p>Breaking down large methods is just an OOP technique that favors reusability. Moving methods to the model classes is just an OOP technique to package code in a logical way. But there is more to ORMs than simple programming techniques.</p>
<p>The result of the refactorings illustrated in this post is a set of classes that <em>carry domain logic</em>. They translate a set of rules – how to form a title, how to extract published articles – into simple methods with expressive names. Names that even the final customer can understand.</p>
<p>The refactorings actually ended up into an API to the project’s domain logic. This is what make a true Domain Model.</p>
<p>After a while, developers who repeat this kind of refactoring change their coding habits. They don’t start with data in a table with a PHP interface ; instead, they start by designing an object model to the customer’s domain. They see the database storage of domain objects as a simple consequence of a need for persistence.</p>
<p>An ORM is just a set of tools helping developers to write their domain logic more easily. Don’t let the relational databases get in your way; think Object-Oriented Programming, and embrace the Domain-Driven Design paradigms.</p>
Propel 1.5.3 Released2010-08-16T00:00:00+00:00http://www.propelorm.org/blog/2010/08/16/propel-1-5-3-released<p>Even during the summer, the Propel team is at work for improving your favorite ORM. With 19 bug fixes and 7 enhancements, here comes the latest minor release of the 1.5 branch. Propel 1.5.3 has an impressive <a href="http://www.propelorm.org/wiki/Documentation/1.5/CHANGELOG">changelog</a>, but it’s still the same story: it’s backwards compatible, more robust, performs better, and offers a handful of new features.</p>
<p>What’s new in this release is that more and more people actively contribute to the Propel core. Whether by opening bug reports, writing unit tests, patches or documentation, they all helped to deliver a great release in a very short timeframe. Don’t hesitate to thank them all in the comments ; if you don’t already follow the <a href="http://www.propelorm.org/timeline">project timeline</a>, you can find their names in the changelog.</p>
<p>Let’s look closer at some of the enhancements brought by Propel 1.5.3.</p>
<h3><code>ModelCriteria::select()</code></h3>
<p>You’re now used to retrieving ActiveRecord objects or collections using the generated Query classes. But what if you just need one or two calculated columns? In that case, hydrating an entire object is overkill. Propel 1.5.3 introduces the <code>ModelCriteria::select()</code> query modifier. It tells the query to return an array of columns rather than ActiveRecord objects. It’s very intuitive and easy to use: just specify which columns you need in an array passed as argument to <code>select()</code>, terminate the query with <code>find()</code>, and you’re done:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create()
->join('Author')
->select(array('Id', 'Title', 'Author.LastName'))
->find();
// array(
// array('Id' => 123, 'Title' => 'Pride and Prejudice', 'Author.LastName' => 'Austen'),
// array('Id' => 456, 'Title' => 'War and Peace', 'Author.LastName' => 'Tolstoi')
// )</pre></div>
</div>
<p>You can mix columns from the main object and from joined classes – Propel will deal with any column name present in the query. If you need only one row, terminate with <code>findOne()</code> instead of <code>find()</code>, and you’ll get an array instead of an array of arrays:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create()
->join('Author')
->select(array('Id', 'Title', 'Author.LastName'))
->findOne();
// array('Id' => 123, 'Title' => 'Pride and Prejudice', 'Author.LastName' => 'Austen')</pre></div>
</div>
<p>If you need only a single column, use a column name instead of an array of column names as argument to <code>select()</code>. Propel will the be smart enough to return scalars rather than arrays:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create()
->select('Title')
->find();
// array('Pride and Prejudice', 'War and Peace')
$books = BookQuery::create()
->select('Title')
->findOne();
// 'Pride and Prejudice'</pre></div>
</div>
<p>There is more to <code>select()</code> than the few examples illustrated here – fortunately, this new feature is extensively documented. Head to the <a href="http://www.propelorm.org/wiki/Documentation/1.5/ModelCriteria#GettingColumnsInsteadOfObjects">ModelCriteria reference</a> for details.</p>
<h3><code>ModelCriteria::groupByClass()</code></h3>
<p>If you need to add all the columns of a model in a GROUP BY clause (PostgreSQL forces you to do that when you use aggregate functions in addition to a model object), use the new <code>groupByClass()</code> shortcut: with a model class name as sole argument, it will expand into one GROUP BY for each column:</p>
<div class="CodeRay">
<div class="code"><pre>$authors = AuthorQuery::create()
->join('Author.Book')
->withColumn('COUNT(Book.Id)', 'NbBooks')
->groupByClass('Author')
->find();
// groupByClass('Author') translates to SQL as:
// GROUP BY author.ID, author.FIRST_NAME, author.LAST_NAME</pre></div>
</div>
<p>Check the documentation for this new feature in the <a href="http://www.propelorm.org/wiki/Documentation/1.5/ModelCriteria#AddingColumns">ModelCriteria reference</a>.</p>
<h3>Behaviors Can Now Create Classes</h3>
<p>Behaviors are no longer limited to the existing model classes (ActiveRecord, Peer, Query and TableMap classes). Starting with Propel 1.5.3, you can let a behavior write an entire new class based on the model. This can be very useful to create proxy classes for your model classes, for instance to integrate Propel with Zend_AMF in order to build a Flex front-end.</p>
<p>Check the how-to for this feature in the <a href="http://www.propelorm.org/wiki/Documentation/1.5/Behaviors#AddingNewClasses">behaviors documentation</a>.</p>
<h3>Various Optimizations</h3>
<p>Propel 1.5.3 benefits from various optimizations that should make your apps a little snappier at runtime, including:</p>
<ul>
<li>Nested Sets Optimization: If you added crafted an index for the left column, queries for the branch or the descendants of a given node will now take advantage of it (cf. <a href="http://www.propelorm.org/ticket/1034">#1034</a>).</li>
<li>Getters for related objects using a composite foreign key now take advantage of the instance pooling (cf. <a href="http://www.propelorm.org/ticket/1011">#1011</a>)</li>
</ul>
<h3>Upgrade</h3>
<p>How to upgrade? You have three choices – It’s your call:</p>
<ul>
<li><p>Subversion tag</p>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.5.3</pre></div>
</div>
</li>
<li><p>PEAR package</p>
<div class="CodeRay">
<div class="code"><pre>> sudo pear upgrade propel/propel-generator
> sudo pear upgrade propel/propel-runtime</pre></div>
</div>
</li>
<li><p>Download</p>
<ul>
<li><a href="http://files.propelorm.org/propel-1.5.3.tar.gz">http://files.propelorm.org/propel-1.5.3.tar.gz</a> (Linux)</li>
<li><a href="http://files.propelorm.org/propel-1.5.3.zip">http://files.propelorm.org/propel-1.5.3.zip</a> (Windows)</li>
</ul>
</li>
</ul>
Refactoring to Propel 1.5: From Peer classes to Query classes2010-08-03T00:00:00+00:00http://www.propelorm.org/blog/2010/08/03/refactoring-to-propel-1-5-from-peer-classes-to-query-classes<p>One of the most powerful features of Propel 1.5 lies in the <a href="http://propel.posterous.com/propel-query-by-example">generated Query classes</a>. But to get the most out of them, developers must change their habits and learn to use these new classes instead of the Peer classes.</p>
<p>This tutorial shows how to refactor an existing model code to take advantage of Propel 1.5 features. The code comes from a Forum plugin for symfony, called <a href="http://www.symfony-project.org/plugins/sfSimpleForumPlugin">sfSimpleForumPlugin</a>, initially written for Propel 1.2. You can find the <a href="http://trac.symfony-project.org/browser/plugins/sfSimpleForumPlugin/trunk/">plugin source code</a> in the symfony Subversion repository.</p>
<h3>General Philosophy</h3>
<p>Static methods are bad. They are hard to reuse, hard to test, and they cannot be chained.</p>
<p>On the other hand, Query methods are good. They are testable, chainable, embeddable, offer IDE completion, and they are as fast as static methods. Besides, they allow for a much more expressive syntax. Here is an example:<!--more--></p>
<div class="CodeRay">
<div class="code"><pre>// Find the cheapest book by Tolsoi
// with Peer constants and static methods
$c = new Criteria();
$c->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID);
$c->add(AuthorPeer::LAST_NAME, 'Tolstoi')
$c->addAscendingOrderByColumn(BookPeer::PRICE)
$book = BookPeer::doSelectOne($c);
// with Query classes
$book = BookQuery::create()
->useAuthorQuery()
->filterByLastName('Tolstoi')
->endUse()
->orderByPrice()
->findOne();</pre></div>
</div>
<p>So the general guideline for converting a Propel < 1.5 application should be to avoid static methods at all costs. That means that every time you fell like writing a static method in a Peer class, you should write a non-static method in the corresponding query class instead.</p>
<h3>Initial Static Model Code</h3>
<p>Let’s first look at how the controllers interact with the model in the sfSimpleForumPlugin. Two controllers allow to display the list of latest posts written by a given user: one for the web page, the second for the RSS feed:</p>
<div class="CodeRay">
<div class="code"><pre>// in modules/sfSimpleForum/lib/BasesfSimpleForumActions.class.php
class BasesfSimpleForumActions extends sfActions
{
// ...
public function executeUserLatestTopics()
{
$this->topics_pager = sfSimpleForumTopicPeer::getForUserPager(
$this->user->getId(),
$this->getRequestParameter('page', 1),
sfConfig::get('app_sfSimpleForumPlugin_max_per_page', 10)
);
// ...
}
public function executeUserLatestTopicsFeed()
{
$this->topics = sfSimpleForumTopicPeer::getForUser(
$this->user->getId(),
sfConfig::get('app_sfSimpleForumPlugin_feed_max', 10)
);
// ...
}
}</pre></div>
</div>
<p>In this example, two Peer methods are used: <code>sfSimpleForumTopicPeer::getForUserPager()</code>, and <code>sfSimpleForumTopicPeer::getForUser()</code>. Let’s check the source code of these methods:</p>
<div class="CodeRay">
<div class="code"><pre>// in lib/model/plugin/PluginsfSimpleForumTopicPeer.php
class PluginsfSimpleForumTopicPeer extends BasesfSimpleForumTopicPeer
{
// ...
public static function getForUserPager($user_id, $page = 1, $max_per_page = 10)
{
$c = self::getForUserCriteria($user_id);
$pager = new sfPropelPager('sfSimpleForumTopic', $max_per_page);
$pager->setPage($page);
$pager->setCriteria($c);
$pager->setPeerMethod('doSelectJoinAll');
$pager->init();
return $pager;
}
public static function getForUser($user_id, $max = 10)
{
$c = self::getForUserCriteria($user_id);
$c->setLimit($max);
return self::doSelectJoinAll($c);
}
protected static function getForUserCriteria($user_id)
{
$c = new Criteria();
$c->add(self::USER_ID, $user_id);
$c->addDescendingOrderByColumn(self::UPDATED_AT);
return $c;
}
}</pre></div>
</div>
<p>In order to avoid repetition of code, the piece of logic that restricts the query to a single user was refactored into a <code>getForUserCriteria()</code> method. Also, both <code>getForUserPager()</code> and <code>getForUser()</code> eventually use <code>sfSimpleForumTopicPeer::doSelectJoinAll()</code> to hydrate topics together with forums.</p>
<h3>Remove Termination Methods</h3>
<p>It appears that <code>getForUserPager()</code> and <code>getForUser()</code>, apart from reusing the common method <code>getForUserCriteria()</code>, only terminate the query with no special added value. They can be seen as <em>termination methods</em> in the Propel Query terminology, since they don’t return a <code>Criteria</code> object.</p>
<p>But <code>ModelCriteria</code> already offers most of the termination methods that you need (<code>find()</code>, <code>count()</code>, <code>paginate()</code>, etc.). So the right thing to do here is to keep only the code that adds logic to your model (the <code>getForUserCriteria()</code> method). This code is easy to move to a Propel Query class. And since a Propel Query is a <code>Criteria</code>, no need to create one in the method - just use <code>$this</code> instead.</p>
<div class="CodeRay">
<div class="code"><pre>// in lib/model/plugin/PluginsfSimpleForumTopicQuery.php
class PluginsfSimpleForumTopicQuery extends BasesfSimpleForumTopicQuery
{
// ...
public function getForUserCriteria($user_id)
{
$this->add(self::USER_ID, $user_id);
$this->addDescendingOrderByColumn(self::UPDATED_AT);
return $this;
}
}</pre></div>
</div>
<p>The termination can be left to the controllers, which must be refactored a little:</p>
<div class="CodeRay">
<div class="code"><pre>// in modules/sfSimpleForum/lib/BasesfSimpleForumActions.class.php
class BasesfSimpleForumActions extends sfActions
{
// ...
public function executeUserLatestTopics()
{
$this->topics_pager = sfSimpleForumTopicQuery::create()
->getForUserCriteria($this->user->getId())
->paginate($this->getRequestParameter('page', 1), sfConfig::get('app_sfSimpleForumPlugin_max_per_page', 10)
);
// ...
}
public function executeUserLatestTopicsFeed()
{
$this->topics = sfSimpleForumTopicQuery::create()
->getForUserCriteria($this->user->getId())
->limit(sfConfig::get('app_sfSimpleForumPlugin_feed_max', 10))
->find();
// ...
}
}</pre></div>
</div>
<p>It’s a good guideline to let the controllers do the termination themselves, and keep in the model classes only <em>filter</em> methods, which return the current query object. It will make your model code much more reusable.</p>
<p>There is one thing missing from this refactoring: the joined hydration that used to be offered by <code>doSelectJoinAll()</code>. Let’s add it back to the model code using the new syntax offered by Propel Queries - the <code>joinWith()</code> method:</p>
<div class="CodeRay">
<div class="code"><pre>// in lib/model/plugin/PluginsfSimpleForumTopicQuery.php
class PluginsfSimpleForumTopicQuery extends BasesfSimpleForumTopicQuery
{
// ...
public function getForUserCriteria($user_id)
{
$this->add(self::USER_ID, $user_id);
$this->addDescendingOrderByColumn(self::UPDATED_AT);
$this->joinWith('sfSimpleForumForum');
return $this;
}
}</pre></div>
</div>
<h3>Use Generated Filter Methods</h3>
<p>From a Propel Query point of view, the <code>getForUserCriteria()</code> method <em>filters</em> and <em>orders</em> the query. The Propel Query API has a faster way of doing so, using the generated filter methods:</p>
<div class="CodeRay">
<div class="code"><pre>// in lib/model/plugin/PluginsfSimpleForumTopicQuery.php
class PluginsfSimpleForumTopicQuery extends BasesfSimpleForumTopicQuery
{
// ...
public function getForUserCriteria($user_id)
{
return $this
->filterByUserId($user_id)
->orderByUpdatedAt('desc')
->joinWith('sfSimpleForumForum');
}
}</pre></div>
</div>
<p><code>filterByUserId()</code> replaces the call to <code>Criteria::add()</code>, and <code>orderByUpdatedAt()</code> replaces the longish <code>addDescendingOrderByColumn()</code>. All the Propel Query methods that are not termination methods return the current Query object, so the fluid interface was used to avoid the repetition of <code>$this</code> on each line.</p>
<h3>Use Objects Whenever Possible</h3>
<p>The controller calls the <code>filterByUserId()</code> method, but it has access to the whole User object. Why not keep objects for this filter? The generated Query class offer an object filter for each foreign key, including a more convenient <code>filterByUser($user)</code> method:</p>
<div class="CodeRay">
<div class="code"><pre>// in modules/sfSimpleForum/lib/BasesfSimpleForumActions.class.php
class BasesfSimpleForumActions extends sfActions
{
// ...
public function executeUserLatestTopics()
{
$this->topics_pager = sfSimpleForumTopicQuery::create()
->getForUserCriteria($this->user)
->paginate($this->getRequestParameter('page', 1), sfConfig::get('app_sfSimpleForumPlugin_max_per_page', 10)
);
// ...
}
public function executeUserLatestTopicsFeed()
{
$this->topics = sfSimpleForumTopicQuery::create()
->getForUserCriteria($this->user)
->limit(sfConfig::get('app_sfSimpleForumPlugin_feed_max', 10))
->find();
// ...
}
}</pre></div>
</div>
<p>The model code should be modified accordingly:</p>
<div class="CodeRay">
<div class="code"><pre><?php
// in lib/model/plugin/PluginsfSimpleForumTopicQuery.php
class PluginsfSimpleForumTopicQuery extends BasesfSimpleForumTopicQuery
{
// ...
public function getForUserCriteria($user)
{
return $this
->filterByUser($user)
->orderByUpdatedAt('desc')
->joinWith('sfSimpleForumForum');
}
}</pre></div>
</div>
<p>Keep objects as long as you can in your model queries - the code will be clearer, and you will be able to achieve more elaborate queries. Propel encourages the use of objects over columns and foreign keys.</p>
<p>The new API has already allowed to dramatically reduce the Model and the Controller code, but it’s only the beginning.</p>
<h3>Use Meaningful Names</h3>
<p>The middle piece of the method, which orders results by update date, could be refactored to be more expressive. Actually, tt returns the latest updated books first, so let’s write it this way:</p>
<div class="CodeRay">
<div class="code"><pre>// in lib/model/plugin/PluginsfSimpleForumTopicQuery.php
class PluginsfSimpleForumTopicQuery extends BasesfSimpleForumTopicQuery
{
// ...
public function getForUserCriteria($user)
{
return $this
->filterByUser($user)
->lastUpdatedFirst()
->joinWith('sfSimpleForumForum');
}
public function lastUpdatedFirst()
{
return $this->orderByUpdatedAt('desc');
}
}</pre></div>
</div>
<p>This new method can then be reused in other queries easily.</p>
<p>Now it’s time to wonder about the main method name. <code>getForUserCriteria()</code> was good for a Peer static method, but now that it’s in a query class, it should be named differently. Something that like <code>latestForUser()</code> should fit:</p>
<div class="CodeRay">
<div class="code"><pre>// in lib/model/plugin/PluginsfSimpleForumTopicQuery.php
class PluginsfSimpleForumTopicQuery extends BasesfSimpleForumTopicQuery
{
// ...
public function latestForUser($user)
{
return $this
->filterByUser($user)
->lastUpdatedFirst()
->joinWith('sfSimpleForumForum');
}
public function lastUpdatedFirst()
{
return $this->orderByUpdatedAt('desc');
}
}</pre></div>
</div>
<p>Now the model code is expressive and reusable, and the controller code is very simple and readable:</p>
<div class="CodeRay">
<div class="code"><pre>// in modules/sfSimpleForum/lib/BasesfSimpleForumActions.class.php
class BasesfSimpleForumActions extends sfActions
{
// ...
public function executeUserLatestTopics()
{
$this->topics_pager = sfSimpleForumTopicQuery::create()
->latestForUser($this->user)
->paginate($this->getRequestParameter('page', 1), sfConfig::get('app_sfSimpleForumPlugin_max_per_page', 10)
);
// ...
}
public function executeUserLatestTopicsFeed()
{
$this->topics = sfSimpleForumTopicQuery::create()
->latestForUser($this->user)
->limit(sfConfig::get('app_sfSimpleForumPlugin_feed_max', 10))
->find();
// ...
}
}</pre></div>
</div>
<p>You should keep the amount of model code inside controller to a minimum. A good rule of thumb is to allow one creation method (<code>create()</code>), one termination method (<code>paginate()</code>, <code>find()</code>), and one logic method (like <code>latestForUser()</code>).</p>
<h3>Remove Things That Propel Can Do On Its Own</h3>
<p>The model code of the sfSimpleForum plugin contains several more examples of <code>getXXXCriteria()</code> methods in Peer classes that can benefit from a similar refactoring. It also contains a lot of custom code that can easily be replaced by native Propel Query features. For instance:</p>
<div class="CodeRay">
<div class="code"><pre>// in lib/model/plugin/PluginsfSimpleForumForumPeer.php
class PluginsfSimpleForumForumPeer extends BasesfSimpleForumForumPeer
{
public static function retrieveByStrippedName($stripped_name)
{
$c = new Criteria();
$c->add(self::STRIPPED_NAME, $stripped_name);
return self::doSelectOne($c);
}
public static function getAllAsArray()
{
$forums = self::doSelect(new Criteria());
$res = array();
foreach ($forums as $forum)
{
$res[$forum->getStrippedName()] = $forum->getName();
}
return $res;
}
}</pre></div>
</div>
<p>The first method, <code>retrieveByStrippedName()</code> has a Model Query counterpart, out of the box:</p>
<div class="CodeRay">
<div class="code"><pre>// retrieve one forum by stripped name
$forum = sfSimpleForumForumQuery::create()
->findOneByStrippedName($stripped_name);</pre></div>
</div>
<p>The second method, <code>getAllAsArray()</code>, is of no use since Propel naturally returns collections, which are one line away from arrays:</p>
<div class="CodeRay">
<div class="code"><pre>// get all forum names as an array indexed by stripped name
$forums = sfSimpleForumForumQuery::create()
->find()
->toKeyValue('StrippedName', 'Name');</pre></div>
</div>
<p>Additionally, all the methods implementing a custom join hydration (like <code>PluginsfSimpleForumForumPeer ::doSelectJoinCategoryLeftJoinPost()</code>, or <code>PluginsfSimpleForumPostPeer::doSelectJoinTopicAndForum()</code>) become useless since you can choose the joined objects directly in the query using <code>joinWith()</code>.</p>
<p>All in all, more than 75% of the Peer code of the current sfSimpleForum plugin doesn’t need to be ported to a Query object - new Propel 1.5 features do the job out of the box.</p>
<h3>Conclusion</h3>
<p>Moving existing model code written for Propel 1.4 to Propel 1.5 Query classes is fast, easy, and it will make your application better. Reusability comes by moving code from the controller to the model. Expressivity comes by using objects as arguments, and meaningful method names. And ease of maintenance comes by keeping the number of Model methods low.</p>
<p>Applications written for Propel 1.3 or 1.4 work out of the box with Propel 1.5, which is a backwards compatible release. Since it’s so easy to replace old code by new code optimized for Propel 1.5, don’t wait, and upgrade now.</p>
Propel Featured in July Issue of php|architect2010-08-02T00:00:00+00:00http://www.propelorm.org/blog/2010/08/02/propel-featured-in-july-issue-of-php-architect<p>If you're a regular reader of <a href="http://www.phparch.com/">the php|architect magazine</a>, you've probably already read it. Today, in its <a href="http://www.phparch.com/magazine/2010/july/">July issue</a>, the PHP magazine published an article about Propel that I wrote a few months ago. Titled "Good Old Propel", it's a general presentation of Propel and the new features introduced by Propel 1.5.</p>
<p>If you happen to write an article about Propel that gets published in any PHP magazine, don't hesitate to send us a notice: we will advertise it in this very blog.</p>
Slides for "The State of Symfony2" Conference2010-06-23T00:00:00+00:00http://www.propelorm.org/blog/2010/06/23/slides-for-the-state-of-symfony2-conference<p>Yesterday and today, the Symfony2 framework showcased its ongoing development during the <a href="http://www.symfony-live.com/">first Symfony online conference</a>. I had the pleasure to present the state of the integration of Propel in Symfony2.</p>
<div>
<p />
<div><a href="http://www.slideshare.net/francoisz/symfony2-meets-propel-15-4">http://www.slideshare.net/francoisz/symfony2-meets-propel-15-4</a></div>
<p />
<p />
</div>
Propel 1.5.2 Released2010-06-17T00:00:00+00:00http://www.propelorm.org/blog/2010/06/17/propel-1-5-2-released<p>A little more than a month after the previous minor release, the Propel team is proud to announce version 1.5.2. This version is backwards compatible with the 1.5 branch, fixes more than 20 bugs, and adds a handful of features. The <a href="http://www.propelorm.org/wiki/Documentation/1.5/CHANGELOG">detailed changelog</a> is available in the Propel 1.5 documentation, so let’s focus on the enhancements.<!--more--></p>
<h3>Namespace support</h3>
<p>Propel can now generate Model classes using Namespaces, so your Propel Models will integrate smoothly into any PHP 5.3 application. This feature is optional, so there will be no change for your PHP 5.2 applications. Propel can even use the namespace to distribute model classes into subdirectories, so PHP5.3-flavored autoloaders will play well with your Propel classes.</p>
<p>This feature is documented in a new tutorial called <a href="http://www.propelorm.org/wiki/Documentation/1.5/Namespaces">“How to Use PHP 5.3 Namespaces”</a>, available in the ‘Docs’ tab of the Propel website.</p>
<div class="CodeRay">
<div class="code"><pre><?phpuse Bookstore\AuthorQuery;$author = AuthorQuery::create() ->useBookQuery() ->filterByPrice(array('max' => 10)) ->endUse() ->findOne();</pre></div>
</div>
<h3>Aggregate Column Behavior</h3>
<p>If you follow this blog, you may have discovered <a href="http://propel.posterous.com/getting-to-know-propel-15-keeping-an-aggregat">How to keep an aggregate column up-to-date</a> and <a href="http://propel.posterous.com/getting-to-know-propel-15-writing-a-behavior">How to write a behavior</a> based on the previous example. Well, it turns out that the code showcased in these tutorials is very useful, so we’ve added to the Propel core behaviors, together with unit tests and <a href="http://www.propelorm.org/wiki/Documentation/1.5/Behaviors/aggregate_column">complete documentation</a>. Now you won’t ever have to worry about denormalizing your model for better performance and simpler queries.</p>
<div class="CodeRay">
<div class="code"><pre><table name="post"> <column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" /> <column name="title" type="VARCHAR" required="true" primaryString="true" /> <behavior name="aggregate_column"> <parameter name="name" value="nb_comments" /> <parameter name="foreign_table" value="comment" /> <parameter name="expression" value="COUNT(id)" /> </behavior></table></pre></div>
</div>
<h3><code>ModelCriteria::findOneOrCreate()</code></h3>
<p>This new method does exactly what its name says: it issues a <code>findOne()</code> query, and if the database returns no result, then it creates a record and populates it using the filters/conditions applied to the query. This is particularly useful for cross-reference tables in many-to-many relationships. This method is documented in the <a href="http://www.propelorm.org/wiki/Documentation/1.5/ModelCriteria#CreatingAnObjectBasedonaQuery">ModelCriteria Reference</a>.</p>
<div class="CodeRay">
<div class="code"><pre><?php$bookTag = BookTagQuery::create() ->filterByBook($book) ->filterByTag('crime') ->findOneOrCreate();</pre></div>
</div>
<h3>Simple Templating Engine For Behaviors</h3>
<p>If you’ve dug into core behaviors, you may have found the code hard to read and debug. Thanks to a simple templating engine, future behaviors will offer a much cleaner syntax, without the need to worry about escaping dollars and quotes. The new <code>aggregate_column</code> is <a href="http://www.propelorm.org/browser/branches/1.5/generator/lib/behavior/aggregate_column/templates">built using this technique</a>, which you can learn in the new <a href="http://www.propelorm.org/wiki/Documentation/1.5/Writing-Behavior#UsingaTemplateForGeneratedCode">How to Write a Behavior Tutorial</a>.</p>
<div class="CodeRay">
<div class="code"><pre>/** * Computes the value of the aggregate column <?php echo $column->getName() ?> * * @param PropelPDO $con A connection object * * @return mixed The scalar result from the aggregate query */public function compute<?php echo $column->getPhpName() ?>(PropelPDO $con){ $stmt = $con->prepare('<?php echo $sql ?>');<?php foreach ($bindings as $key => $binding): ?> $stmt->bindValue(':p<?php echo $key ?>', $this->get<?php echo $binding ?>());<?php endforeach; ?> $stmt->execute(); return $stmt->fetchColumn();}</pre></div>
</div>
<h3>Query Comments</h3>
<p>If you need to ‘tag’ a query in order to be able to find it later in your logs, the best way is to add an SQL comment to it. It’s now easy thanks to the <code>ModelCriteria::setComment()</code> method. It works for SELECT queries as well as DELETE and UPDATE queries. You will find an example in the <a href="http://www.propelorm.org/wiki/Documentation/1.5/ModelCriteria#AddingAComment">ModelCriteria Reference</a>.</p>
<div class="CodeRay">
<div class="code"><pre><?phpAuthorQuery::create() ->setComment('Author Deletion') ->filterByName('Leo Tolstoy') ->delete($con);// The comment ends up in the generated SQL query// DELETE /* Author Deletion */ FROM `author` WHERE author.NAME = 'Leo Tolstoy'</pre></div>
</div>
<h3>Miscellaneous</h3>
<p>The Model autoloader has been refactored to separate the autoloading of core Propel classes and Model classes. That should produce a small speed bump, and an easier integration of Propel into third-party libraries.</p>
<p>Also, exceptions thrown while executing a query should now be more useful, since the complete SQL query is now included in the <code>PropelException</code> message.</p>
<p>The XSD for the <code>schema.xml</code> has been greatly completed; it makes <a href="http://propel.posterous.com/easy-xml-autocompletion-in-propel-schemas">writing a schema with an IDE</a> extremely fast an error-proof.</p>
<h3>Upgrade</h3>
<p>You can see the enhancements of the 1.5.2 release as an incentive to upgrade quickly. Propel keeps getting better every day, and we hope that this short release cycle will help you to motivate your fellow developers to adopt Propel.</p>
<p><strong>Subversion tag</strong></p>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.propelorm.org/tags/1.5.2</pre></div>
</div>
<p><strong>PEAR package</strong></p>
<div class="CodeRay">
<div class="code"><pre>> sudo pear upgrade propel/propel-generator> sudo pear upgrade propel/propel-runtime</pre></div>
</div>
<p><strong>Download</strong></p>
<p><a href="http://files.propelorm.org/propel-1.5.2.tar.gz">http://files.propelorm.org/propel-1.5.2.tar.gz</a></p>
<p><a href="http://files.propelorm.org/propel-1.5.2.zip">http://files.propelorm.org/propel-1.5.2.zip</a></p>
Propel, PHP 5.3, and Namespaces: They Love Each Other2010-06-09T00:00:00+00:00http://www.propelorm.org/blog/2010/06/09/propel-php-5-3-and-namespaces-they-love-each-other<p>Propel was originally a PHP4 port of the Java Torque library - that was <a href="http://propel.tigris.org/source/browse/propel/www/index.html?revision=1.2&view=markup">a long time ago</a>. It was ported to PHP5 quite early - about <a href="http://www.propelorm.org/browser/tags/1.1.0">five years ago</a>. Since then, Propel has been compatible with every stable PHP release. Propel 1.5 requires PHP 5.2, but also <strong>works seamlessly with PHP 5.3</strong>. That means that if PHP 5.3 is your version of choice, you can use Propel right now - no need to wait for a new version.</p>
<p>But “working” doesn’t mean “taking the best advantage of”. By maintaining backwards compatibility with PHP versions less than 5.3, Propel can’t take advantage of the greatest advances of PHP 5.3 - late static binding, anonymous functions, namespaces, closures, phar, to name only a few. Or can it?</p>
<p><!--more-->Code generation allows to modify the model classes code based on configuration. If a developer wants to use PHP 5.3 only, then why not enable some of the PHP 5.3 features in the model classes?</p>
<p>This is getting real with the recent <strong>introduction of namespaces</strong> in Propel 1.5. That’s right, even though Propel 1.5 requires only PHP 5.2, it can generate model classes using namespaces only supported since PHP 5.3. And this is very easy to enable: just add a <code>namespace</code> attribute in the <code><database></code> tag of the XML schema, rebuild the object model, and the generated classes now use the namespace:</p>
<div class="CodeRay">
<div class="code"><pre><span class="preprocessor"><?xml version="1.0" encoding="UTF-8" standalone="no"?></span>
<span class="tag"><database</span> <span class="attribute-name">name</span>=<span class="string"><span class="delimiter">"</span><span class="content">bookstore</span><span class="delimiter">"</span></span> <span class="attribute-name">namespace</span>=<span class="string"><span class="delimiter">"</span><span class="content">Bookstore</span><span class="delimiter">"</span></span> <span class="attribute-name">defaultIdMethod</span>=<span class="string"><span class="delimiter">"</span><span class="content">native</span><span class="delimiter">"</span></span><span class="tag">></span>
<span class="tag"><table</span> <span class="attribute-name">name</span>=<span class="string"><span class="delimiter">"</span><span class="content">book</span><span class="delimiter">"</span></span><span class="tag">></span>
<span class="comment"><!-- --></span>
<span class="tag"></table></span>
<span class="tag"><table</span> <span class="attribute-name">name</span>=<span class="string"><span class="delimiter">"</span><span class="content">author</span><span class="delimiter">"</span></span><span class="tag">></span>
<span class="comment"><!-- --></span>
<span class="tag"></table></span>
<span class="tag"></database></span></pre></div>
</div>
<p>Now you can use the namespaced model classes by using the fully qualified name, or by aliasing the class name. The Propel runtime autoloading works as usual:</p>
<div class="CodeRay">
<div class="code"><pre><span class="inline-delimiter"><?php</span>
<span class="comment">// use fully qualified name</span>
<span class="local-variable">$book</span> = <span class="keyword">new</span> \<span class="constant">Bookstore</span>\<span class="constant">Book</span>();
<span class="comment">// or use an alias</span>
<span class="keyword">use</span> <span class="constant">Bookstore</span>\<span class="constant">Book</span>;
<span class="local-variable">$book</span> = <span class="keyword">new</span> <span class="constant">Book</span>();
<span class="comment">// remember to use the \ namespace for core Propel classes in this case</span>
<span class="local-variable">$con</span> = \<span class="constant">Propel</span>::getConnection();
<span class="local-variable">$book</span>->save(<span class="local-variable">$con</span>);</pre></div>
</div>
<p>The namespace is used for the ActiveRecord class, but also for the Query and Peer classes. Just remember that when you use relation names in a query, the namespace should not appear:</p>
<div class="CodeRay">
<div class="code"><pre><span class="inline-delimiter"><?php</span>
<span class="local-variable">$author</span> = \<span class="constant">Bookstore</span>\<span class="constant">AuthorQuery</span>::create()
->useBookQuery()
->filterByPrice(<span class="predefined">array</span>(<span class="string"><span class="delimiter">'</span><span class="content">max</span><span class="delimiter">'</span></span> => <span class="integer">10</span>))
->endUse()
->findOne();</pre></div>
</div>
<p>You can extend the database namespace in a given table by setting a <code>namespace</code> attribute of the <code><table></code> tag:</p>
<div class="CodeRay">
<div class="code"><pre><span class="preprocessor"><?xml version="1.0" encoding="UTF-8" standalone="no"?></span>
<span class="tag"><database</span> <span class="attribute-name">name</span>=<span class="string"><span class="delimiter">"</span><span class="content">bookstore</span><span class="delimiter">"</span></span> <span class="attribute-name">namespace</span>=<span class="string"><span class="delimiter">"</span><span class="content">Bookstore</span><span class="delimiter">"</span></span> <span class="attribute-name">defaultIdMethod</span>=<span class="string"><span class="delimiter">"</span><span class="content">native</span><span class="delimiter">"</span></span><span class="tag">></span>
<span class="comment"><!-- --></span>
<span class="tag"><table</span> <span class="attribute-name">name</span>=<span class="string"><span class="delimiter">"</span><span class="content">publisher</span><span class="delimiter">"</span></span> <span class="attribute-name">namespace</span>=<span class="string"><span class="delimiter">"</span><span class="content">Book</span><span class="delimiter">"</span></span><span class="tag">></span>
<span class="comment"><!-- --></span>
<span class="tag"></table></span>
<span class="tag"></database></span></pre></div>
</div>
<p>The corresponding object model objects will use the table namespace as a subnamespace of the database namespace:</p>
<div class="CodeRay">
<div class="code"><pre><span class="inline-delimiter"><?php</span>
<span class="local-variable">$publisher</span> = <span class="keyword">new</span> \<span class="constant">Bookstore</span>\<span class="constant">Book</span>\<span class="constant">Publisher</span>();
<span class="local-variable">$publisher</span>->save();</pre></div>
</div>
<p>That’s as simple as this. You can mix classes with various namespaces in the same schema, and these classes can share relations regardless of their namespaces. All the features you love and use in Propel 1.5 - behaviors, many-to-many relationships, joined hydration - work the same for namespaced models.</p>
<p>The new namespace feature is available in the 1.5 branch, and soon in the stable 1.5.2 release. It is <a href="http://www.propelorm.org/wiki/Documentation/1.5/Namespaces">already documented</a>, as usual - and ready for you to use it.</p>
Getting To Know Propel 1.5: Writing A Behavior2010-06-03T00:00:00+00:00http://www.propelorm.org/blog/2010/06/03/getting-to-know-propel-1-5-writing-a-behavior<p>A <a href="http://propel.posterous.com/getting-to-know-propel-15-keeping-an-aggregat">recent post</a> published in this very blog illustrated the power of the hooks offered by the ActiveRecord and Query classes. In this previous post, an aggregate column on a `PollQuestion` class was kept up to date every time a related `PollAnswer` object was saved, edited, or deleted. Now it's time to make this code really reusable across models, and the best way to do so is to move the code from the model to a behavior.</p>
<p><a href="http://www.propelorm.org/wiki/Documentation/1.5/Writing-Behavior" title="How to Write a Behavior">Read the rest of this post >></a></p>
Embed Relation Forms in One Line with sfPropel15Plugin (Video)2010-06-01T00:00:00+00:00http://www.propelorm.org/blog/2010/06/01/embed-relation-forms-in-one-line-with-sfpropel15plugin-video-<p>One line can do a lot of things...</p>
<p><iframe src="http://player.vimeo.com/video/12215820?portrait=0" frameborder="0" height="283" width="500"></iframe></p>
<p>sfPropel15Plugin features two new tools, sfFormPropel::embedRelation(), and sfFormPropel::mergeRelation(). These will be great time-savers, as they allow you to add, edit, and remove related objects in the same form as the main object.</p>
<p>This feature is already available, and fully documented at <a href="http://trac.symfony-project.org/browser/plugins/sfPropel15Plugin/trunk/doc/form.txt">http://trac.symfony-project.org/browser/plugins/sfPropel15Plugin/trunk/doc/fo...</a></p>
sfPropel15Plugin: It's like a new version of symfony2010-05-25T00:00:00+00:00http://www.propelorm.org/blog/2010/05/25/sfpropel15plugin-it-s-like-a-new-version-of-symfony<p>The Propel core team has just released the <a href="http://www.symfony-project.org/plugins/sfPropel15Plugin/1_0_0">first stable version of the sfPropel15Plugin</a>. This plugin for the <a href="http://www.symfony-project.org/">symfony framework</a> is a drop-in and backwards-compatible replacement for the core sfPropelPlugin bundled with symfony 1.3/1.4. That’s right: your existing Propel applications built with symfony can benefit from all the Propel 1.5 goodness right now, just by installing a plugin.</p>
<h3>Installation</h3>
<p>The plugin offers a <a href="http://trac.symfony-project.org/browser/plugins/sfPropel15Plugin/tags/1.0.0/README">step-by-step guide</a> to upgrading an existing application. Basically, it’s a no-brainer, except that you mustn’t forget to replace 5 paths in the <code>propel.ini</code> file with paths using the plugin. And also, rebuild your model after the upgrade, so that Propel can create the new Query classes.</p>
<h3>What Hasn’t Changed</h3>
<p>sfPropel15Plugin offers all the features supported by the core Propel plugin, from the YAML schema to the integration of the Propel tasks into the symfony command line. Forms and filter forms still get generated based on your model schema, ad-hoc widgets and generators are still available for foreign keys, the web debug toolbar still lists the SQL queries executed for the current request, the routing and mailer classes still work just the same as before, and the admin generator still makes it very easy to build a backend application based on a model.</p>
<p>In fact, using the exact same application code, sfPropel15Plugin works flawlessly, except… a little faster.</p>
<h3>What has changed</h3>
<p>A lot has changed under the hood, in order to take advantage of the new Query API, and to give better access to it. In fact, the list of additions is so long that it feels like a new symfony version.<!--more--> The plugin README lists them all, and this blog will highlight some of the new features in the near future, so for now let’s just focus on three major changes:</p>
<ul>
<li>
<p><strong>Admin Generator on steroids</strong>: Customize widgets and validators right from the <code>generator.yml</code>, display “plain” fields like it’s symfony 1.2 again, make all columns sortable (including foreign keys and virtual columns), reduce the query count without cumbersome Peer methods, add custom filters in minutes… In short, the new <code>admin15</code> theme is a fantastic productivity tool. And since it’s <a href="http://trac.symfony-project.org/browser/plugins/sfPropel15Plugin/trunk/doc/admin_generator.txt">fully documented</a>, you have no excuse to use the old generator theme.</p>
</li>
<li>
<p><strong>Easy Relation Form Embed</strong>: Editing related objects together with the main objects (e.g., editing <code>Comments</code> in a <code>Post</code> form) is now a piece of cake. The new <code>sfFormPropel::embedRelation()</code> method does all the work to fetch related objects, build the forms for each of them, and embed the related object forms into the main form. Embdeded relation forms allow to edit, add, and delete a related objects with no additional code. As a bonus, the <a href="http://trac.symfony-project.org/browser/plugins/sfPropel15Plugin/trunk/doc/form.txt">Propel Forms documentation</a> was rewritten from scratch, and provides many examples on how to get the best out of the Form subframework.</p>
</li>
<li>
<p><strong>Core Propel Behaviors</strong>: If you rely on Propel behaviors for nested sets, slugs, soft delete, or sortable lists, then you will welcome the new core Propel behaviors, offering better performance and more features, and most of all, maintained by the Propel core team. And you will learn to love the new <a href="http://www.propelorm.org/wiki/Documentation/1.5/Inheritance#ConcreteTableInheritance">Concrete Inheritance behavior</a>, which is a unique and killer feature of Propel 1.5.</p>
</li>
</ul>
<h3>Upgrade</h3>
<p>It’s not just a new version of the ORM, it’s almost a new way to develop with symfony that becomes possible with sfPropel15Plugin. Say goodbye to the hard-to-learn <code>Criteria</code> object and the hard-to-reuse <code>Peer</code> methods, and please welcome the new generated query classes.</p>
<p>Built by the Propel core team with usability in mind, the plugin integrates seamlessly with the MVC framework and your existing applications. Faster, more secure, more powerful, better documented, what you’ve been expecting for a long time from Propel is finally there: a first-class ORM, fully integrated into a first-class framework.</p>
<p>Upgrade now, and start enjoying Propel 1.5.</p>
Easy XML autocompletion in Propel schemas2010-05-20T00:00:00+00:00http://www.propelorm.org/blog/2010/05/20/easy-xml-autocompletion-in-propel-schemas<p>Propel offers a rich XML schema to describe and validate the <code>schema.xml</code> syntax. In IDEs offering autocompletion (like NetBeans or Eclipse), this can be a great time saver, because the IDE can suggest the possible elements and attributes at the right time, and validate the syntax progressively.</p>
<p>To enable XML autocompletion, you must add two attributes to the <code><database></code> tag of your schema:</p>
<p><code><database name="my_connection_name" defaultIdMethod="native"<br /> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br /> xsi:noNamespaceSchemaLocation="http://www.propelorm.org/xsd/1.5/database.xsd" ><br /></code></p>
<p>This works for all versions of Propel down to 1.3.</p>
<p>Propel is definitely the most IDE-friendly ORM out there, and its helps you to write good quality code at lightspeed.</p>
Getting To Know Propel 1.5: When You Really Need Arrays2010-05-17T00:00:00+00:00http://www.propelorm.org/blog/2010/05/17/getting-to-know-propel-1-5-when-you-really-need-arrays<p>Web applications spend a large share of their code transforming arrays of data. PHP is a wonderful language for that, because it offers a lot of array manipulation functions. But web developers are actually required to translate business logic into a program - not to mess up with arrays. In fact, web developers should spend the least possible amount of time dealing with arrays, because this time is lost. A piece of code transforming an array is usually not very reusable, it doesn’t carry any logic, and it’s a pain to test and maintain.</p>
<p>Propel, as other ORMs, advocates the use of objects rather than arrays. But it turns out that you sometimes need an array representation of your model objects. Propel makes these situations painless by offering the shorcuts you need at the right time.<!--more--></p>
<h3>From ActiveRecord Objects To Arrays</h3>
<p>One of the first goals of Propel is to replace the data structure of a database record, represented as an array by PDO, with an ActiveRecord object. Instead of accessing the columns of a record using an array interface, ActiveRecord objects offer getter and setter methods. The objects promote encapsulation, hide some columns from the end user, and can offer new, ‘virtual’ columns, calculated from other columns or even data from other tables. The Propel Guide <a href="http://www.propelorm.org/wiki/Documentation/1.5/BasicCRUD">explains this concept in detail</a>, so there should be nothing new there:</p>
<p><script src="https://gist.github.com/352c760b320128ee0871.js"></script></p>
<p>However, it is sometimes useful to get an array representation of an ActiveRecord object. Whether for debugging purposes, or to dump data for later reuse, arrays are sometimes simpler to deal with than objects. The conversion is very straightforward with Propel:</p>
<p><script src="https://gist.github.com/b5d4a679e957a48862c6.js"></script></p>
<p>Note that if you hydrated a model object together with its relations, then <code>toArray()</code> can be even more powerful than that. Just set the third argument to <code>true</code> to also convert relations to arrays:</p>
<p><script src="https://gist.github.com/2f0a465195abb93dfa13.js"></script></p>
<p>You can also populate an empty Model object from an associative array by calling <code>fromArray()</code>. That’s a quick way to set several properties an once; it’s especially useful if you provide a form to edit a model object using phpNames as input names.</p>
<p><script src="https://gist.github.com/dcee9b14b4f2b7d66ff7.js"></script></p>
<h3>PropelCollections Give You The Array You Need</h3>
<p>Propel 1.5 introduces <code>PropelCollections</code>, which are a wonderful way of not messing up with arrays. At first sight, a collection looks like an array, and behaves like an array. It also provides additional abilities, already illustrated in a <a href="http://propel.posterous.com/propel-gets-collections">previous article</a> in this very blog:</p>
<p><script src="https://gist.github.com/c6c77f8b05beead02492.js"></script></p>
<p>But now, what if you actually need an array, for a special purpose? For instance, an <a href="http://docs.jquery.com/Plugins/Autocomplete">autocomplete field using jQuery</a> requires an action returning an associative array of book titles, indexed by primary key. Propel makes it trivial to transform a Collection object into such an array:</p>
<p><script src="https://gist.github.com/4eac73ab13c1b7711070.js"></script></p>
<p>The first argument of <code>toKeyValue()</code> is the name of the column to use for the array index, the second is the name of the column to use for the array value. In fact, <code>toKeyValue()</code> is a little smarter than that. With no argument, it returns an associative array indexed by primary key, and uses the ActiveRecord <code>__toString()</code> method. So, provided the <code>title</code> column is declared as <code>primaryString</code> in the <code>book</code> schema, you can get the same associative array by calling:</p>
<p><script src="https://gist.github.com/3bd3c16f403beff64988.js"></script></p>
<p>If you need more than just a key and a value, then <code>PropelCollection::toArray()</code> is probably the method you need. It turns a collection into an array of associative arrays, much like the ones you get when calling individually <code>toArray()</code> on an ActiveRecord object:</p>
<p><script src="https://gist.github.com/93c455cbfeacfac69046.js"></script></p>
<h3>Reindexing A Collection</h3>
<p>Propel collection are indexed incrementally, for performance reasons. That means that the first element in a collection always uses 0 as index, the second uses 1, and so on. It makes methods like <code>isFirst()</code>, <code>isLast()</code>, or <code>isOdd()</code> fast and efficient, but it forbids the use of a custom index.</p>
<p>Fortunately, <code>PropelCollection::getArrayCopy()</code> accepts a column name as first argument, and returns an array of Model objects indexed by the chosen column:</p>
<p><script src="https://gist.github.com/8190328c961194e7d8e1.js"></script></p>
<p><code>toArray()</code> also accepts a column name as first argument to choose a custom index column:</p>
<p><script src="https://gist.github.com/8314fa7ffe772ee9e6d4.js"></script></p>
<p>Whether you need to sort the results according to one of the columns of a model, or to get a list of ActiveRecord objects indexed by primary key, the ability to reindex an existing collection will save you a lot of coding.</p>
<h3>Query From An Array Of Conditions</h3>
<p>In a previous example, a list of books was created based on a ‘title’ request parameter. But you may want to provide a set of widgets to filter a list of books on several fields. In this case, the request may contain several parameters, each corresponding to a given column.</p>
<p><script src="https://gist.github.com/48a27abc52da4a8392c2.js"></script></p>
<p>It’s easy to create a query using an associative array of filters:</p>
<p><script src="https://gist.github.com/ba90427032d20fa61bbd.js"></script></p>
<p><code>filterByArray()</code> expects an associative array of filter names and values, and turns it into a list of <code>filterByXXX()</code> calls. The previous line is the equivalent to:</p>
<p><script src="https://gist.github.com/e1e364130bf3e4708102.js"></script></p>
<p><code>filterByArray()</code> can even accept additional filters not based on model columns, provided that you added a custom <code>filterByXXX()</code> method. For instance, if you add the following field to the book search form:</p>
<p><script src="https://gist.github.com/00b9126d2e20edaac17f.js"></script></p>
<p>Then all it takes to make it work is to implement a <code>BookQuery::filterByAuthor()</code> method:</p>
<p><script src="https://gist.github.com/7bea12532237a9ca3a2e.js"></script></p>
<h3>Raw Results From A Query</h3>
<p>Even though Propel is fully Object-Oriented, there are times when hydrating an ActiveRecord object is just overkill. In cases you need a single scalar value resulting from a database query, there is often no interest to attach it to a model object. In this case, you can still use the methods of the generated query objects, by using the ‘statement’ formatter to get a raw PDO statement as a result instead of a model object.</p>
<p><script src="https://gist.github.com/3e2b5b535e6b717a53f6.js"></script></p>
<p>If you need several columns and several rows, but still not attached to an ActiveRecord object, you can do the same, and call <code>fetchAll()</code> on the result statement to get an array to iterate on:</p>
<p><script src="https://gist.github.com/2cc2052dd880553f8e55.js"></script></p>
<p>But if you end up doing too much of this kind of query, watch out: you’re probably missing the point of actually using an ORM. The following code sample provides the same functionality as the previous one; it is not more expensive to run, yet it is much more object-oriented, and much easier to write:</p>
<p><script src="https://gist.github.com/a4d91a2be4bede1e8c00.js"></script></p>
<p><span style="font-size: 15px; font-weight: bold;">Conclusion</span></p>
<p>If you come from a PDO background, it probably feels more natural to use arrays instead of objects. Propel makes it easy to use an array as an input for an ActiveRecord object or a Query, or to output results as arrays. But try to reduce the use of arrays to a strict minimum, or you will spend too much time transforming these arrays, instead of dealing with the business logic of your web application. And you may miss some of the best features of Propel, which are only enabled by an Object-Oriented approach.</p>
Propel 1.5.1 Is Already There2010-05-10T00:00:00+00:00http://www.propelorm.org/blog/2010/05/10/propel-1-5-1-is-already-there<p>It's only been two weeks since the stable 1.5.0 release, yet the numerous bugfixes and small enhancements added to the 1.5 branch justify a new minor release. Today, the Propel team is happy to announce the immediate availability of Propel 1.5.1. <p /> This version offers a more robust hydration of related objects, adds missing hooks to the generated query objects, improves runtime performance, and a lot of other small changes. Overall, this milsetone contains more than 40 changes in all areas of the ORM. The <a href="http://www.propelorm.org/wiki/Documentation/1.5/CHANGELOG">CHANGELOG</a> lists them all.<p /> To update, choose your favorite method:<p /><b>Subversion tag</b><p /><span style="font-family: courier new,monospace;"> > svn checkout <a href="http://svn.propelorm.org/tags/1.5.1">http://svn.propelorm.org/tags/1.5.1</a></span><p /> <b>PEAR package</b><p /><span style="font-family: courier new,monospace;"> > sudo pear upgrade propel/propel-generator</span><br style="font-family: courier new,monospace;" /> <span style="font-family: courier new,monospace;"> > sudo pear upgrade propel/propel-runtime</span><p /><b>Download</b><p /><a href="http://files.propelorm.org/propel-1.5.1.tar.gz">http://files.propelorm.org/propel-1.5.1.tar.gz</a><br /> <a href="http://files.propelorm.org/propel-1.5.1.zip">http://files.propelorm.org/propel-1.5.1.zip</a><p />Previous releases are still available at <a href="http://files.propelorm.org">http://files.propelorm.org</a>.</p>
Getting To Know Propel 1.5: A Search Engine In One Line2010-05-05T00:00:00+00:00http://www.propelorm.org/blog/2010/05/05/getting-to-know-propel-1-5-a-search-engine-in-one-line<p>The ability to reuse elements of a query was dramatically improved with Propel 1.5. A great example of this new flexibility is how easy it is to build a full-text search engine. Or, to use the 1.5 vocabulary, a <em>text filter</em>. Let’s see how to allow full-text search to a Bookstore with a single line.</p>
<h3>The Naive Approach</h3>
<p>A full-text search engine for books should return results where the search input appears in the book title, or in the book summary. Databases support simple regular expression comparison, so the fastest way to implement the search engine looks like:</p>
<div class="CodeRay">
<div class="code"><pre>class BookQuery extends BaseBookQuery{ public function filterByText($text) { $pattern = '%' . $text . '%'; return $this ->where('Book.Title like ?', $pattern) ->orWhere('Book.Summary like ?', $pattern); }}</pre></div>
</div>
<p><!--more-->The new <code>filterByText()</code> method uses <code>where()</code> and <code>orWhere()</code>, which are part of the <a href="http://www.propelorm.org/wiki/Documentation/1.5/ModelCriteria#RelationalAPI">relational API</a>, the secondary set of methods offered by <code>ModelCriteria</code>. It also returns the current object, so it can be chained together with other query methods. Now, adding a full-text search to a query is really a one-liner:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create() ->filterByText('pride') ->orderByTitle() ->find();</pre></div>
</div>
<p>The <code>$books</code> collection gets hydrated from the results of the following SQL query:</p>
<div class="CodeRay">
<div class="code"><pre>SELECT book.* from `book`WHERE (book.TITLE LIKE '%pride%' OR book.SUMMARY LIKE '%pride%')ORDER BY book.TITLE ASC;</pre></div>
</div>
<p>You can use the new <code>filterByText()</code> method to <em>count</em> books matching the string, or to look for <em>authors</em> of books matching the string:</p>
<div class="CodeRay">
<div class="code"><pre>$authors = AuthorQuery::create() ->useBookQuery() ->filterByText('pride') ->endUse() ->orderByLastName() ->find();</pre></div>
</div>
<p><strong>Tip</strong>: If you’re a symfony user, you can even use the new filter method as an <em>admin generator filter</em> with <a href="http://www.symfony-project.org/plugins/sfPropel15Plugin">sfPropel15Plugin</a>. Just add the <code>text</code> filter to your list, and you’re done:</p>
<div class="CodeRay">
<div class="code"><pre># in modules/book/config/generator.ymlconfig: filter: display: [text]</pre></div>
</div>
<h3>Using An Index</h3>
<p>The previous approach is naive, because it doesn’t scale. When the book table reaches more than a few thousand rows, a SQL query using <code>LIKE</code> and <code>OR</code> is likely to hit the slow query limit. The usual workaround is to build a table of searchable words, and to use this (indexed) table for full text searches.</p>
<p>So let’s add a <code>book_index</code> table, related to the <code>book</code> table by a <code>book_id</code> foreing key. The index table also features a <code>word</code> column, with an index.</p>
<div class="CodeRay">
<div class="code"><pre><table name="book_index" phpName="BookIndex"> <column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" /> <column name="book_id" required="true" type="INTEGER" /> <foreign-key foreignTable="book" onDelete="cascade"> <reference local="book_id" foreign="id" /> </foreign-key> <column name="word" type="VARCHAR" required="true" primaryString="true" /> <index> <index-column name="word" /> </index></table></pre></div>
</div>
<p>This table should fill up automatically each time a new <code>Book</code> is added. The simplest way to do this is to use a <code>postSave()</code> hook in the <code>Book</code> ActiveRecord class:</p>
<div class="CodeRay">
<div class="code"><pre>class Book extends BaseBook{ public function postSave(PropelPDO $con = null) { // delete previous words from this book BookIndexQuery::create() ->filterByBook($this) ->delete($con); // build the list of words for this book $titleWords = preg_split('/\W/', $this->getTitle(), null, PREG_SPLIT_NO_EMPTY); $summaryWords = preg_split('/\W/', $this->getSummary(), null, PREG_SPLIT_NO_EMPTY); $words = array_unique(array_merge($titleWords, $summaryWords)); // Save the words for this book foreach ($words as $word) { $index = new BookIndex(); $index->setBook($this); $index->setWord($word); $index->save($con); } }}</pre></div>
</div>
<p><strong>Tip</strong>: All the database operations inside <code>postSave()</code> use the connection object (<code>$con</code>), to guarantee <a href="http://www.propelorm.org/wiki/Documentation/1.5/Transactions">transactional integrity</a> and better performance.</p>
<p>Now the <code>BookQuery::filterByText()</code> method is even easier to write:</p>
<div class="CodeRay">
<div class="code"><pre>class BookQuery extends BaseBookQuery{ public function filterByText($text) { return $this ->useBookIndexQuery() ->filterByWord($text) ->endUse(); }}</pre></div>
</div>
<p>And the full-text search engine scales. Besides, the syntax to use the engine didn’t change:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create() ->filterByText('pride') ->orderByTitle() ->find();</pre></div>
</div>
<p>Query methods also bring the benefit of easy refactoring, since the actual implementation of a filter is encapsulated inside a method.</p>
<h3>Using A Search Engine</h3>
<p>The new approach won’t give very accurate results on a text search - don’t use it for a real world application. Most notably, there is no notion of relevancy that could be used to order the results, placing the books most likely to match the query at the top. Also, the index table fills up with lots of useless (because non-discriminating) words like “the”, or “and”. And a search for “man” won’t return any book featuring “men”. And the index soon grows very large, so that even a solid MySQL server can’t handle it with reasonable response time.</p>
<p>This is because there is much more to full-text search than just building an index. From <a href="http://en.wikipedia.org/wiki/Stop_words">stop words</a> to <a href="http://en.wikipedia.org/wiki/Stemming">stemming</a>, the domain of text search is very complex. You can safely assume that <em>you won’t be able to build a good quality search engine on your own</em> - and that means that you should use an existing solution instead.</p>
<p>And there are a lot of cheap search solutions, including many open-source ones. One could use PostreSQL’s excellent <a href="http://www.postgresql.org/docs/8.4/static/textsearch.html">Full Text Search capabilities</a>, or <a href="http://framework.zend.com/manual/en/zend.search.lucene.html">Zend_Search_Lucene</a>, or even <a href="http://www.google.com/cse/">Google</a>, to provide the search feature for a bookstore. It doesn’t really matter for the present exercise. Let’s just assume that the external search engine supports queries through a Web Service.</p>
<p>Such search engines often return results in XML, including references to the unique identifiers of the indexed documents. In case of a bookstore, the search engine result would probably feature book ids, among other data like an excerpt from the matching content, or the matching accuracy. That would make the <code>filterByText()</code> method look like:</p>
<div class="CodeRay">
<div class="code"><pre>class BookQuery extends BaseBookQuery{ protected static $searchUri = 'http://myengine.mydomain.com/search?q='; public function filterByText($text) { $sxe = new SimpleXMLElement(self::$searchUri . $text, NULL, true); $bookIds = array(); foreach ($xse->results->book as $book) { $bookIds []= (int) $book->id; } return $this ->filterById($bookIds); }}</pre></div>
</div>
<p><code>BookQuery::filterById()</code> accepts an array of primary keys, which translates into a SQL IN (). So the <code>filterByText()</code> method still returns a modified <code>BookQuery</code> matching the <code>$text</code> pattern, even though the actual searching took place somewhere else. And the search API for the books didn’t change:</p>
<div class="CodeRay">
<div class="code"><pre>$books = BookQuery::create() ->filterByText('pride') ->orderByTitle() ->find();</pre></div>
</div>
<h3>Conclusion</h3>
<p>Even if the code samples presented here are more a proof-of-concept than real life implementations, this is a good example of the flexibility of the <a href="http://www.propelorm.org/wiki/Documentation/1.5/WhatsNew#NewQueryAPI">new Query API</a> introduced in Propel 1.5. Not only can you build complex logic inside a simple query method, you can also reuse this logic very easily, without adding complexity to the public API of the model. And if you can build a search engine in a few minutes, imagine the wonders that can come out if you spend as much time with Propel as you used to do with SQL…</p>
Getting To Know Propel 1.5: Keeping An Aggregate Column Up To Date2010-04-29T00:00:00+00:00http://www.propelorm.org/blog/2010/04/29/getting-to-know-propel-1-5-keeping-an-aggregate-column-up-to-date<p>Propel 1.5 was released earlier this week, so it’s a good time to learn how to get the most of its new Query objects. Today’s exercise aims to keep an aggregate column up to date, and illustrates the use of ActiveRecord and Query hook methods.</p>
<h3>The Model</h3>
<p>The model is simple: for a poll widget, a <code>PollQuestion</code> and a <code>PollAnswer</code> class share a one-to-many relationship. The <code>PollAnswer</code> class features a <code>NbVotes</code> property, incremented each time a user votes for this question. The <code>PollQuestion</code> also needs a <code>TotalNbVotes</code>, which is the sum of the <code>NbVotes</code> of all the related <code>PollAnswers</code>, in order to display answer ratings as percentages. Let’s see how to manage this <code>TotalNbVotes</code> column automatically.</p>
<p><img src="/images/blog/pollER.png" /></p>
<h3>The Easy Part: Using ActiveRecord Hooks</h3>
<p>Each time the <code>PollAnswer</code>’s <code>NbVotes</code> is incremented, the parent <code>PollQuestion</code>’s <code>TotalNbVotes</code> should be incremented as well. In fact, the use case is larger than than: each time a <code>PollAnswer</code> is added, deleted, or modified, the parent’s <code>TotalNbVotes</code> should be recalculated.</p>
<p>Since version 1.4, Propel offers hooks in the generated ActiveRecord model objects, so this is quite easy to implement:</p>
<div class="CodeRay">
<div class="code"><pre>class PollAnswer extends BasePollAnswer
{
public function postSave(PropelPDO $con)
{
if ($parentQuestion = $this->getPollQuestion()) {
$parentQuestion->updateNbVotes($con);
}
}
public function postDelete(PropelPDO $con) {
if ($parentQuestion = $this->getPollQuestion()) {
$parentQuestion->updateNbVotes($con);
}
}
}
</pre></div>
</div>
<p>Propel calls the <code>postSave()</code> hook each time a PollAnswer object is inserted or updated. Notice that both the <code>postSave()</code> and the <code>postDelete()</code> receive the current connection object, and use it. This is because these methods are called during a transaction, and the connection object should be the same throughout the whole transaction to let Propel and the database revert the transaction is something wrong occurs.</p>
<h3>Good Old PDO To The Rescue</h3>
<p>The task to keep the total vote count up to date is left to the <code>PollQuestion</code> object. This could be done using a <code>ModelCriteria</code>, but since the expected result is a scalar, there is no need to hydrate an ActiveRecord. So let’s use a raw PDO query instead. To keep a minimum of model abstraction, the column and table name should be represented by their class constants:</p>
<div class="CodeRay">
<div class="code"><pre>class PollQuestion extends BasePollQuestion
{
public function updateNbVotes($con = null)
{
$sql = 'SELECT SUM(' . PollAnswerPeer::NB_VOTES . ') AS nb'
. ' FROM ' . PollAnswerPeer::TABLE_NAME
. ' WHERE ' . PollAnswerPeer::QUESTION_ID . ' = ?';
$stmt = $con->prepare($sql);
$stmt->execute(array($this->getId()));
$this->setTotalNbVotes($stmt->fetchColumn());
$this->save($con);
}
}
</pre></div>
</div>
<p>The <code>updateNbVotes()</code> method executes one SELECT query, and if the result differs from the current TotalNbVotes, then the <code>PollQuestion</code> object gets updated.</p>
<p>So when a <code>PollAnswer</code> gets updated, its <code>NbVotes</code> is saved, then the <code>PollAnswer::postSave()</code> method fetches the parent <code>PollQuestion</code>, and calls <code>PollQuestion::updateNbVotes()</code> to calculate and persist the new <code>TotalNbVotes</code>. All in a single transaction.</p>
<p>That’s it for the easy part.</p>
<h3>Using Query Hooks</h3>
<p>There is a use case that hasn’t been addressed yet: What if a set of <code>PollAnswer</code> objects is deleted using <code>PollAnswerQuery::delete()</code>, rather than using individual <code>PollAnswer::delete()</code> calls? The previous changes wouldn’t be enough to update the <code>TotalNbVotes</code> in this case.</p>
<div class="CodeRay">
<div class="code"><pre>PollAnswerQuery::create()
->filterByBody('%TEMP%')
->delete();
</pre></div>
</div>
<p>It is necessary to use the <code>preDelete()</code> and <code>postDelete()</code> hooks of the <code>PollAnswerQuery</code> class for that. The <code>preDelete()</code> code must determine the <code>PollAnswer</code> objects concerned by the deletion, and from then on, keep the related <code>PollQuestion</code> objects. The <code>postUpdate()</code> method should iterate over this collection of <code>PollQuestion</code> objects, and call <code>updateNbVotes()</code> on each of them. Here is a first way to implement this:</p>
<div class="CodeRay">
<div class="code"><pre>class PollAnswerQuery extends BasePollAnswerQuery
{
protected $pollQuestions = array();
public function preDelete(PropelPDO $con) {
$pollAnswerQuery = clone $this;
$pollAnswers = $pollAnswerQuery
->joinWith('PollQuestion')
->find($con);
foreach ($pollAnswers as $pollAnswer) {
$this->pollQuestions[$pollAnswer->getQuestionId()] = $pollAnswer->getPollQuestion();
}
}
public function postDelete($affectedRows, PropelPDO $con) {
foreach ($this->pollQuestions as $pollQuestion) {
$pollQuestion->updateVotesNb($con);
}
$this->pollQuestions = array();
}
}
</pre></div>
</div>
<p>The <code>preDelete()</code> code reuses the current query object, but terminates with a <code>find()</code> rather than a <code>delete()</code>. <code>joinWith()</code> helps to reduce the number of SQL queries to 1, even though <code>getPollQuestion()</code> gets called in a loop afterwards.</p>
<h3>Merging Queries</h3>
<p>The <code>preDelete()</code> code creates a SQL query similar to:</p>
<div class="CodeRay">
<div class="code"><pre>
SELECT poll_answer.*, poll_question.*FROM poll_answer
INNER JOIN poll_question ON poll_answer.question_id = poll_question.id
WHERE poll_answer.body LIKE '%TEMP%';</pre></div>
</div>
<p>The PHP code iterates over the result of this query to retrieve a list of <code>PollQuestion</code> objects with no duplicates. But what is really needed there is a list of <code>PollQuestion</code> objects. There is no real need to pass by an intermediate list of <code>PollAnswers</code>.</p>
<p>But, if you use a <code>PollQuestionQuery</code> to get <code>PollQuestion</code> objects, how can it take the conditions applied to a <code>PollAnswerQuery</code> object? It’s quite simple: just <em>merge</em> the two query objects together. You can simply write the <code>PollAnswerQuery::preDelete()</code> method as follows:</p>
<div class="CodeRay">
<div class="code"><pre>public function preDelete(PropelPDO $con)
{
$this->pollQuestions = PollQuestionQuery::create()
->joinPollAnswer()
->mergeWith($this)
->find($con);
}
</pre></div>
</div>
<p>The resulting SQL query is now:</p>
<div class="CodeRay">
<div class="code"><pre>SELECT poll_question.*FROM poll_question INNER JOIN poll_answerON poll_question.id = poll_answer.question_idWHERE poll_answer.body LIKE '%TEMP%';</pre></div>
</div>
<h3>Conclusion</h3>
<p>Query objects are very reusable: you can clone them and use a different termination method, or merge them with another query. It brings reusability to queries the same way the ActiveRecord pattern brings reusability to row manipulation.</p>
<p>The example is not finished: the <code>PollAnswerQuery</code> method should also implement the <code>preUpdate()</code> and <code>postUpdate()</code> hooks to deal with <code>update()</code> queries that may alter the vote count of a list of <code>PollAnswer</code> objects.</p>
<p>And, besides polls, what was demonstrated here is a very common need: keep an column calculated with an aggregate function on a related table up to date. From the number of books of an author to the latest edition of the articles of a website, the requirement is very generic. To make the above code even more reusable, it must be packaged as a behavior. You will learn how to create such a <code>aggregate_column</code> behavior in a future session of "<em>Getting To Know Propel 1.5</em>".</p>
Propel 1.5 Stable Released2010-04-26T00:00:00+00:00http://www.propelorm.org/blog/2010/04/26/propel-1-5-stable-released<p>Propel 1.5.0 is live. After six months of hard work, three times as much unit tests than the previous version, and a lot of help from beta testers, we are very proud to release the first stable release of Propel 1.5.<p /> The Propel blog has already mentioned <a href="http://propel.posterous.com/propels-criteria-gets-smarter" target="_blank">most</a> <a href="http://propel.posterous.com/propel-gets-collections" target="_blank">of</a> <a href="http://propel.posterous.com/the-ultimate-orm-query-api-only-with-propel" target="_blank">the</a> <a href="http://propel.posterous.com/new-in-propel-15-concrete-table-inheritance-a" target="_blank">highlights</a> of the new Propel version, and the <a href="http://www.propelorm.org/wiki/Documentation/1.5/WhatsNew" target="_blank">WHAT'S NEW page</a> of the 1.5 release describes all the new features extensively, so we won't list them once more in this post. If you have never heard of Propel 1.5, you just need to know that it brings at least two killer features to Propel (the <a href="http://propel.posterous.com/propel-query-by-example" target="_blank">New Query API</a>, and the <a href="http://www.propelorm.org/wiki/Documentation/1.5/WhatsNew#ConcreteTableInheritanceBehavior" target="_blank">Concrete Table Inheritance With Data Replication</a>), that it improves the runtime performance, and that it is backward compatible with Propel 1.4 and 1.3. Of course, all the bugfixes from the 1.4 branch are already in this version, and it's fully documented. In short, there is no reason not to upgrade, plus it offers a whole new world of possibilities.<p /> But rather than sell the new version ourselves, we'd like to let early adopters tell you how they feel about Propel 1.5. We hope their enthusiasm will be able to convince you:<p />"It is phenomenal" <i>Steve J., CEO</i><p /> "It allowed my last project to reach a whole new dimension" <i>James C., Director</i><p />"This thing is worth Millions" <i>Mike A., Editor</i><p />"You can continue to write crappy code if you want. For my part, I made the switch." <i>David HH., Programmer</i><p /> "I need more" <i>Iggy P., Singer</i> <p />"I don't like it. You should always put your business logic into stored procedures" <i>Larry E., CEO</i><p />Don't wait more, and upgrade to Propel 1.5 using your favorite method:<p /> <span style="font-family: courier new,monospace;"> # Subversion</span><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> > svn co </span><a href="http://svn.propelorm.org/tags/1.5.0" target="_blank" style="font-family: courier new,monospace;">http://svn.propelorm.org/tags/1.5.0</a><span style="font-family: courier new,monospace;"> propel<p /> </span><span style="font-family: courier new,monospace;"> # PEAR</span><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> > sudo pear uninstall phpdb/propel_generator</span><br style="font-family: courier new,monospace;" /> <span style="font-family: courier new,monospace;"> > sudo pear uninstall phpdb/propel_runtime</span><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> > sudo pear channel-discover </span><a href="http://pear.propelorm.org" target="_blank" style="font-family: courier new,monospace;">pear.propelorm.org</a><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> > sudo pear install propel/propel_generator</span><br style="font-family: courier new,monospace;" /> <span style="font-family: courier new,monospace;"> > sudo pear install propel/propel_runtime</span><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> </span><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> # Download archive</span><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> > wget </span><a href="http://files.propelorm.org/propel-1.5.0.tar.gz" target="_blank" style="font-family: courier new,monospace;">http://files.propelorm.org/propel-1.5.0.tar.gz</a><br style="font-family: courier new,monospace;" /> <span style="font-family: courier new,monospace;"> > wget </span><a href="http://files.propelorm.org/propel-1.5.0.zip" target="_blank" style="font-family: courier new,monospace;">http://files.propelorm.org/propel-1.5.0.zip</a><p /> And if you're satisfied with it, don't forget to leave us a comment like the early adopters did, to help convince the newcomers.</p>
Propel 1.5.0RC1 Is Released, Propel 1.5 stable is near2010-04-20T00:00:00+00:00http://www.propelorm.org/blog/2010/04/20/propel-1-5-0rc1-is-released-propel-1-5-stable-is-near<p>The stable release of Propel 1.5, the next major release of the Propel ORM, is just around the corner. Today, we've just released the first Release Candidate. Unless we discover a major bug or regression in this release, it will become the stable release early next week.<p /> You can checkout Propel 1.5.0RC1 from the Propel subversion repository:<p />> svn checkout <a href="http://svn.propelorm.org/tags/1.5.0RC1">http://svn.propelorm.org/tags/1.5.0RC1</a><p />Propel 1.5.0 RC1 is also available in two PEAR packages, hosted by the brand new PEAR channel. You need to uninstall Propel prior to installing this beta if you want to use PEAR:<p /> > sudo pear uninstall phpdb/propel_generator<br />> sudo pear uninstall phpdb/propel_runtime<br />> sudo pear channel-discover <a href="http://pear.propelorm.org">pear.propelorm.org</a><br />> sudo pear install propel/propel_generator<br /> > sudo pear install propel/propel_runtime<p /> If you use PEAR, please test this new release and the new PEAR channel, as it will become the default channel for all 1.5.x releases.</p>
Propel 1.4.2 Is Out2010-04-19T00:00:00+00:00http://www.propelorm.org/blog/2010/04/19/propel-1-4-2-is-out<p>The Propel 1.4 branch has a new minor release.<p /><strong>Update</strong></p>
<ul>
<li>Subversion tag</li>
</ul>
<p><span style="font-family: courier new,monospace;"> > svn checkout <a href="http://svn.phpdb.org/propel/tags/1.4.2">http://svn.propelorm.org/tags/1.4.2</a><br /> </span></p>
<ul>
<li>PEAR package </li>
</ul>
<p><span style="font-family: courier new,monospace;"> > sudo pear upgrade phpdb/propel-generator</span><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> > sudo pear upgrade phpdb/propel-runtime<br /></span><span style="font-family: courier new,monospace;"><br /></span><strong>Changelog</strong><p />This is a bugfix release, backwards compatible with the 1.4 branch. It corrects a few bugs found in the past three months. The changelog is available at <a href="http://www.propelorm.org/wiki/Documentation/1.4/CHANGELOG">http://www.propelorm.org/wiki/Documentation/1.4/CHANGELOG</a>.</p>
propel.phpdb.org has shut down, long live propelorm.org!2010-04-15T00:00:00+00:00http://www.propelorm.org/blog/2010/04/15/propel-phpdb-org-has-shut-down-long-live-propelorm-org-<div>The long awaited server switch finally took place today. We changed the Propel server, domain name, and website design, mostly to solve the speed and stability problems of the previous host. The new Propel website is <a href="http://www.propelorm.org" target="_blank">http://www.propelorm.org</a>, and you're welcome to visit it anytime. It still has few rough edges, but with your feedback and a few days of polish, it will be almost perfect!</div>
<p />
<div>The new website emphasizes the installation and documentation sections. It's still based on Trac, but we managed to make Trac a little more discreet in the documentation pages. Of course, all the Trac history, tickets and milestones from the <a href="http://phpdb.org" target="_blank">phpdb.org</a> server were imported, and you will find the ticketing, source and timeline under the '<a href="http://www.propelorm.org/timeline" target="_blank">Dev</a>' section. We couldn't import the old user accounts from the previous site, but the good news is that it is now easy to register directly in the 'Dev' section. If you had a user account, open a new account at <a href="http://propelorm.org" target="_blank">propelorm.org</a> with the same username, and you should receive alerts on the tickets you've opened. If you had commit access, you'll have to ask me again.</div>
<p />
<div>The subversion server has also moved. The old server, available at <a href="http://svn.phpdb.org/propel" target="_blank">http://svn.phpdb.org/propel</a>, is still accessible, but it's now read only, and the next changes won't appear there. The new Propel SVN server address is <a href="http://svn.propelorm.org" target="_blank">http://svn.propelorm.org</a>. It has been synchronized with the previous server. You'll need to change your svn externals, or relocate your existing checkouts:</div>
<p />
<div> > svn switch --relocate <a href="http://svn.phpdb.org/propel" target="_blank">http://svn.phpdb.org/propel</a> <a href="http://svn.propelorm.orm" target="_blank">http://svn.propelorm.org</a>
</div>
<p />
<div>The PEAR server will remain at the previous address for a short period of time. We'll soon offer a pear channel at <a href="http://pear.propelorm.org" target="_blank">pear.propelorm.org</a>.</div>
<p />
<div>The blog URL is unchanged - <a href="http://propel.posterous.com" target="_blank">http://propel.posterous.com</a>.</div>
<p />
<div>The old server should redirect most of the documentation and ticket pages to the new server, page to page. However, search engines may take time to update their index, so mentioning the new Propel website url in your own sites may speed up re-crawl.</div>
<p />
<div>I'd like to thank Veikko Mäkinen, for giving the domain name and designing the new website, and Benjamin Boerngen-Schmidt, for providing the server, adapting the design to Trac, transferring all the data from the old server, and administering the new server.</div>
<p />
<div>Of course, your feedback is welcome!</div>
Propel 1.5 Beta 2 Released2010-04-01T00:00:00+00:00http://www.propelorm.org/blog/2010/04/01/propel-1-5-beta-2-released<p>It took a little more than a month to fix the 25 bugs found in the first 1.5 beta, and today we are pleased to release the second beta version of Propel 1.5. We would like to thank all the developers who downloaded and tested the first beta. Their feedback allowed the discovery (and the fix) of a few bugs appearing in some special use cases.<p /><div>To be noted, in this new beta, the Propel license was changed from LGPL3 to MIT. You can read more about this decision in <a href="http://propel.posterous.com/propel-changes-license-from-lgpl3-to-mit">a previous blog post</a>. Also, the generated query classes now have a clean <code class="language-plaintext highlighter-rouge">create()</code> factory, and smart <code class="language-plaintext highlighter-rouge">joinXXX()</code> methods guessing the join type based on the foreign key properties. You can see the whole changelog in the <a href="http://propel.phpdb.org/trac/query?status=closed&group=resolution&milestone=1.5+Beta+2">Propel Trac</a>. Lastly, the <a href="http://propel.phpdb.org/trac/query?status=closed&group=resolution&milestone=1.4.2">12 bug fixes from branch 1.4</a> were also backported to this release.</div> <p /><div><div>This second 1.5 beta has been released in all your favorite formats:</div><p /><div>Subversion tag</div><div> > svn checkout <a href="http://svn.phpdb.org/propel/tags/1.5.0BETA2">http://svn.phpdb.org/propel/tags/1.5.0BETA2</a> </div> <p /><div>Tarball</div><div> <a href="http://propel.phpdb.org/propel-1.5.0BETA2.tar.gz">http://propel.phpdb.org/propel-1.5.0BETA2.tar.gz</a></div><p /><div>PEAR Package</div><div> > sudo pear config-set preferred_state beta</div> <div> > sudo pear upgrade phpdb/propel-generator</div><div> > sudo pear upgrade phpdb/propel-runti</div><p /><div>Please test it now if you have the opportunity to do so, to avoid discovering possible issues after the stable release. If everything goes smoothly, the stable version should follow within two weeks.</div> </div></p>
Propel 1.5 reaches 3000 tests2010-03-25T00:00:00+00:00http://www.propelorm.org/blog/2010/03/25/propel-1-5-reaches-3000-tests<p>With <a href="http://propel.phpdb.org/trac/changeset/1639">revision 1639</a>, Propel 1.5 has reached the 3000th unit test in the test suite. That's more than three times the number of unit tests in Propel 1.4 (928), and six times the number of unit tests in Propel 1.3.<p /><div>Propel 1.5 is going to be the most robust Propel release ever. Even though we added a lot of features, we're very confident that Propel will run smoothly in your applications, thanks to the quality insurance provided by unit tests and test-driven development.</div></p>
Propel Changes License From LGPL3 to MIT2010-03-17T00:00:00+00:00http://www.propelorm.org/blog/2010/03/17/propel-changes-license-from-lgpl3-to-mit<p>Propel 1.5, the next version to be released later this month, will no longer be published under a LGPL3 License, but under the more liberal <a href="http://propel.phpdb.org/trac/browser/branches/1.5/LICENSE">MIT license</a>. This change removes a usage restriction enforced by the LGPL3: you no longer need to release any modifications to the core Propel source code under a LGPL compatible license. Propel users still have the right to use, copy, modify, merge, publish, distribute, sublicense, and/or sell Propel. In other terms, you can do whatever you want with the Propel code, without worrying about the license, as long as you leave the LICENSE file within.<p /><div>This change encourages the reuse of Propel in third-party libraries, whatever their license. Furthermore, there is virtually no limit to bundling Propel in any commercial or open-source software. So don't hesitate, and try out Propel 1.5 right now: fast, effective, and discreet, it will fit perfectly in your application or framework.</div></p>
How Fast Is Propel 1.5?2010-03-15T00:00:00+00:00http://www.propelorm.org/blog/2010/03/15/how-fast-is-propel-1-5-<div>With the addition of very powerful features like formatters, collections and the new query object, you may expect the next version of Propel to be slower than its predecessor. Let’s benchmark the two to see the actual differences.</div>
<p />
<h3>Benchmark Scenarios</h3>
<p />
<div>To check the speed difference, I set up a few test scenarios emphasizing various parts of the Propel runtime code:</div>
<div>
<ul>
<li> Scenario 1: Create a new Model object, set its columns, and save it. Tests Model object speed, and INSERT SQL generation.</li>
<li> Scenario 2: Lookup a record by its primary key. Tests basic query and hydration.</li>
<li> Scenario 3: Lookup a record using a complex query. Tests object query speed.</li>
<li> Scenario 4: Lookup 5 records on a simple criterion. Tests hydration speed.</li>
<li> Scenario 5: Lookup a record and hydrate it together with its related record in another table. Tests join hydration speed.</li>
</ul>
</div>
<div>The code for the implementation of these scenarios in Propel 1.4 and Propel 1.5 can be found at <a href="http://code.google.com/p/php-orm-benchmark/">http://code.google.com/p/php-orm-benchmark/</a>. Each test is run several times to average the execution time. The result is the number of milliseconds necessary for each test, so the lower it is, the faster the scenario executes.</div>
<p />
<div>I ran the benchmarks using PHP 5.3 and SQLite in memory as a backend on a MacBookPro. I’m aware that it is not a "real" production server, and that its I/O system is probably slower than that of a true web server. However, the results are so distinct that I doubt that an EC2 instance would show a different winner.</div>
<p />
<h3>Propel 1.4 vs Propel 1.5</h3>
<p />
<div>Without further ado, let’s jump to the results:</div>
<div> <!--more--></div>
<div class="CodeRay">
<div class="code"><pre>| Insert | findPk | complex| hydrate| with |
|--------|--------|--------|--------|--------|
Propel14TestSuite | 986 | 416 | 123 | 280 | 286 |
Propel15aLa14TestSuite | 966 | 407 | 128 | 277 | 282 |</pre></div>
</div>
<p />
<div>Both test suites use the Criteria and Peer syntax; the first uses Propel 1.4 as a backend, while the second uses Propel 1.5. So with the exact same code, Propel 1.5 is slightly faster than Propel 1.4. This is caused by a few optimizations added to Propel 1.5 in the SQL code generation process. In production, the speed difference is barely noticeable, except when saving a large number of objects. Nonetheless, it's reassuring to know that upgrading an exisiting application to Propel 1.5 will not degrade performance.</div>
<p />
<div>But what's more interesting is how the Propel 1.5 new Query API compares with Criteria and Peer methods. This is what the following chart compares:</div>
<p />
<div class="CodeRay">
<div class="code"><pre>| Insert | findPk | complex| hydrate| with |
|--------|--------|--------|--------|--------|
Propel14TestSuite | 986 | 416 | 123 | 280 | 286 |
Propel15TestSuite | 966 | 567 | 164 | 376 | 398 |</pre></div>
</div>
<p />
<div>Except for insertions, Propel 1.5 with the Query API is slower than Propel 1.4 by 30% to 50%. This sounds quite normal, considering the added intelligence in the query object, and the new intermediates in the hydration process (formatter, collection). If you want to take advantage of the powerful new features, you have to accept a certain decrease in performance, probably compensated by the added flexibility.</div>
<p />
<div>Also, the main bottleneck in most web applications is the number of queries executed in order to display a single page. Propel 1.5 provides new tools to reduce the query count, including joined hydration of one-to-many relationships, additional column hydration, and collection relation population (see <a href="http://propel.posterous.com/reduce-your-query-count-with-propel-15">http://propel.posterous.com/reduce-your-query-count-with-propel-15</a> for details). So when used wisely, the new Query API in Propel 1.5 will allow your code to run faster than with Propel 1.4.</div>
<p />
<div>Tip: PHP 5.3 and Moore's law make web servers much faster today than two years ago. On up-to-date hardware, Propel 1.5 feels just like Propel 1.3 used to feel when it was released.</div>
<p />
<h3>Propel 1.5 vs. Doctrine 1.2</h3>
<p />
<div>Since the new Propel 1.5 query syntax makes it slower than Propel 1.4, is it still worth using Propel rather than Doctrine? Let’s write a Doctrine 1.2 implementation of the benchmark scenarios and compare the results with Propel. </div>
<p />
<div>The Doctrine code, available at <a href="http://code.google.com/p/php-orm-benchmark/">http://code.google.com/p/php-orm-benchmark/</a>, was reviewed by Roman Borschel, who is one of the Doctrine core developers. I also asked him to provide an implementation in Doctrine 2 (which is not yet in Alpha), and this should come shortly.</div>
<p />
<div>Here are the benchmark results:</div>
<p />
<div class="CodeRay">
<div class="code"><pre>| Insert | findPk | complex| hydrate| with |
|--------|--------|--------|--------|--------|
Propel14TestSuite | 986 | 416 | 123 | 280 | 286 |
Propel15TestSuite | 966 | 567 | 164 | 376 | 398 |
Doctrine12TestSuite | 1779 | 2738 | 467 | 1628 | 1914 |</pre></div>
</div>
<p />
<div>Propel 1.5, like Propel 1.4, is still much faster at runtime than Doctrine 1.2 – between 2x and 5x. Doctrine uses runtime introspection while Propel relies on code generation, so that explains the difference. Doctrine also provides an extensive query language called DQL that requires parsing, and that has a cost. The generated `filterByXXX()` methods in Propel Query classes execute much faster, because they don’t require any introspection.</div>
<p />
<div>Note that I’m aware that the two ORMs don't provide the same exact features. But both are first class ORMs, and provide powerful tools to reduce the query count, which is the main performance bottleneck in Model code.</div>
<p />
<h3>Enter The Query Cache</h3>
<p />
<div>When I asked him to review the Doctrine code, Roman Borschel suggested that I enable the Query Cache in the Doctrine benchmark. This cache removes much of the performance hog in the query parsing, so that runtime code just deals with hydration.</div>
<p />
<div>Query cache is a smart technique that can speed up Propel as well. In fact, Propel 1.5 provides the same feature, as a behavior. So I ran the benchmarks once more, this time comparing Propel 1.5 with query cache, and Doctrine 1.2 with query cache:</div>
<p />
<div class="CodeRay">
<div class="code"><pre>| Insert | findPk | complex| hydrate| with |
|--------|--------|--------|--------|--------|
Propel14TestSuite | 986 | 416 | 123 | 280 | 286 |
Propel15TestSuite | 966 | 567 | 164 | 376 | 398 |
Propel15WithCacheTestSuite | 965 | 459 | 182 | 372 | 337 |
Doctrine12TestSuite | 1779 | 2738 | 467 | 1628 | 1914 |
Doctrine12WithCacheTestSuite | 2059 | 1205 | 553 | 984 | 763 |</pre></div>
</div>
<p />
<div>The query cache is very effective in improving Doctrine’s runtime performance – at least in these tests. The setup used for the tests uses a static array as the backend for the query cache. In a real application, this cache would use an APC or Memcache backend, much slower than a PHP array.</div>
<p />
<div>The query cache also provides a noticeable boost to Propel 1.5. It’s not enough to put it on par with Propel 1.4, though.</div>
<p />
<div>And most important: the query cache sometimes degrades performance. In the “complex” scenario, both Propel and Doctrine perform worse using a query cache than without. Also, the query cache makes insertions even slower with Doctrine, while it was already a weak point.</div>
<p />
<h3>Propel vs. PDO</h3>
<p />
<div>If losing performance is a major drawback for you, perhaps you should bypass the ORM entirely. Not for all queries, of course, but for the really critical ones. ORMs are notably slow, because they add object-oriented code on top of database operations. PHP is fast for string operations, but it's not the fastest language for objects.</div>
<p />
<div>So if you have to execute a lot of database queries in a small amount of time, don't blame Propel 1.5, but rather use PDO directly. Propel makes it easy to execute a raw SQL query using the database connections defined in your settings. Check the following benchmark comparing ORMs to raw PDO:</div>
<p />
<div class="CodeRay">
<div class="code"><pre>| Insert | findPk | complex| hydrate| with |
|--------|--------|--------|--------|--------|
PDOTestSuite | 102 | 106 | 103 | 106 | 100 |
Propel14TestSuite | 986 | 416 | 123 | 280 | 286 |
Propel15TestSuite | 966 | 567 | 164 | 376 | 398 |
Propel15WithCacheTestSuite | 965 | 459 | 182 | 372 | 337 |
Doctrine12TestSuite | 1779 | 2738 | 467 | 1628 | 1914 |
Doctrine12WithCacheTestSuite | 2059 | 1205 | 553 | 984 | 763 |</pre></div>
</div>
<p />
<div>As you can see, the number of iterations of each scenario was adjusted so that PDO scores about 100 each time. This new benchmark demonstrates that, for very fast SQL queries like insertion or search by PK, the cost of an ORM is very important (up to 27x slower than raw PDO for Doctrine). On the contrary, for slow and complex SQL queries, the speed drop caused by an ORM isn’t that big, because the bottleneck is then the SQL code, not the ORM.</div>
<p />
<h3>Conclusion</h3>
<p />
<div>Propel 1.5 is faster than Propel 1.4 if you use the exact same code. It’s a little slower if you use the new features, like collections, generated query classes, and formatters. It's more efficient if you use the new tools to reduce your query count. And it’s still much faster than Doctrine 1.2.</div>
<p />
<div>There is room for improvement – the Propel development team spends a considerable amount of time profiling the Propel code and improving it. There are also techniques to make your Model code perform faster ; you can use query cache, for instance.</div>
<p />
<div>But the bottomline is that an ORM provides coding tools that will speed up your development and help you write more efficient queries in no time. If you really need brute speed, you should bypass the ORM completely and use PDO in selected queries. So the performance of an ORM should not be your main element of choice.</div>
<p />
<div>The code used for this benchmark is available at <a href="http://code.google.com/p/php-orm-benchmark/">http://code.google.com/p/php-orm-benchmark/</a>. </div>
Should Developers Know Design Patterns?2010-03-02T00:00:00+00:00http://www.propelorm.org/blog/2010/03/02/should-developers-know-design-patterns-<div><strong><span style="font-weight: normal;">Developers of Open Source libraries often advocate the use of Design Patterns to market their product. But, if some may consider this as a sign of quality, other developers are afraid of Design Patterns simply because the term is intimidating. Should Propel assume that its users know that they manipulate Active Record objects, or let them do so blindly? </span></strong></div>
<p />
<div><strong>Architects Must Know Architecture</strong></div>
<p />
<div>Propel, like other ORMs, implements a lot of common Design Patterns. Active Record, Unit Of Work, Identity Map, Lazy Load, Foreign Key Mapping, Concrete Table Inheritance, Query Object, to name a few, all appear in Propel. The very idea of an Object-Relational Mapping is indeed a Design Pattern itself. </div>
<p />
<div>Implementing a library like Propel implies that the library developers should be aware of existing solutions to the problems they are solving. They should also be aware of the common patterns in these solutions. Nobody wants to reinvent the wheel, or to spend time implementing a feature, just to have some random guy tell you one day: "Oh, but you've just reinvented ___". That's why the Propel developers spend quite some time reading CS books, watching the development of other ORMs, in PHP and in other languages, to learn from the efforts of others.</div>
<p />
<div><strong>How About You?</strong></div>
<p />
<div>But the point of this post is to ask whether the developers of web applications <em>using</em> Propel should know about these design patterns, too. In other terms, should the Propel documentation assume that these concepts are well known, or explain the use of Propel without spending time explaining the reasons of a particular implementation?</div>
<div> <!--more--></div>
<div>On the one side, the manager inside me may think that a developer who is not aware of Design Patterns, and tries to solve every problem by himself, is not a good developer. Such a developer should not be considered for the projects I'm dealing with - it's too risky. I may end up with a feature developed in a week, while an applied Design Pattern would have given a better solution in only two days. Also, the feature developed using a naive implementation would probably be harder to maintain and to explain to another developer, while a solution following a Design Pattern would benefit from the general knowledge of this pattern.</div>
<p />
<div>On the other side, a library that guides developers into a particular type of coding might be enough to keep a good quality level. A Domain Model built with Propel implicitly contains all these common solutions to well-known problems. Even if the developer is not aware of Design Patterns, she applies them anyway. I have seen many inexperienced developers write code with Propel, and produce implementations of rather good quality. I remember asking one of them why he used Nested Sets rather than Materialized Path for a particular feature. He answered that he just needed a working tree implementation, and that it was apparently the best one provided by the library. He didn't know anything about the Nested Sets algorithmic. He had never heard of the Gang of Four or Martin Fowler or Eric Evans, but his lack of knowledge was compensated by a careful observance of the usage documentation.</div>
<p />
<div><strong>Design Patterns For Dummies</strong></div>
<p />
<div>Take this question to another domain: Do you have to know the theory of the reciprocating engine to drive a car? Do you need to know Chemistry to cook noodle soup? Should you learn Mathematical Finance prior to investing in the stock exchange? It's always better if you do, but you don't actually have to. And advocating that the theoretical knowledge is a requirement to a good usage of a library is just a way to drive potential users away from the library. </div>
<p />
<div>Even better: by applying design patterns <span style="font-size: 10.0pt; font-family: Arial; color: black;">unconsciously</span>, a developer actually learns them. If a library can allow the manipulation of such patterns even to non-expert developers, then the library will probably improve the developers' development skills.</div>
<p />
<div><strong>A Library For Developers, Not Against Them</strong></div>
<p />
<div>So targeting people with no prior knowledge of Design Patterns opens a library to a broader audience, and educates this audience. This implies working more on the documentation to make it more accessible, but this effort is necessary anyway. The documentation should be more directive on the usage description, and less verbose on the implementation choice. Design Pattern names should probably be banned from the user documentation, to make it less intimidating. </div>
<p />
<div>Somehow, this idea draws the limit between a library and a framework. You have to know the <em>concepts </em>to use a library; you just need to understand the <em>usage</em> to develop with a framework.</div>
<p />
<div>Propel bets that you don't need to know the theory behind the ORM to be comfortable with it. Propel provides improved productivity and better code quality to all developers, including inexperienced ones. Don't be afraid by the powerful engine; the driver's license is easy to get.</div>
<p> </p>
Propel 1.5.0 Beta 1 Is Out2010-02-21T00:00:00+00:00http://www.propelorm.org/blog/2010/02/21/propel-1-5-0-beta-1-is-out<p>After three and a half months of hard work, Propel 1.5 is ready for the beta flag. That means that the API should not change from now on, and that the 1.5 branch is feature freeze.<p /><div><b>What's in Propel 1.5</b></div> <p /><div>Propel 1.5 is a backwards-compatible evolution of Propel 1.4. It brings significant improvements to the usability of the ORM. Among the new features: </div><div><div><ul><li>New runtime Query API, replacing Criteria+Peer methods with generated query classes having runtime introspection abilities</li> <li>Support for collections</li><li>New behaviors (nested sets, sluggable, concrete table inheritance, sortable)</li><li>Many-to-many relationships</li><li>Improved Oracle support</li></ul></div><div>You will find more details in the "<a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/WhatsNew">What's New in 1.5?</a>" page in the Propel wiki. The documentation is already up to date, so don't hesitate to <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5">dive in</a>.</div> <p /><div>Of course, Propel still focuses on the best runtime performance and IDE friendliness. Propel 1.5 is no exception on these two matters - it should even be better than 1.4.</div><p /><div><b>Installing / Upgrading</b></div> <p /><div>This first 1.5 beta has been released in all your favorite formats:</div><div><div><ul><li>Subversion tag</li></ul></div><div> > svn checkout <a href="http://svn.phpdb.org/propel/tags/1.5.0BETA1">http://svn.phpdb.org/propel/tags/1.5.0BETA1</a> </div> <div><ul><li>Tarball</li></ul></div><div> <a href="http://propel.phpdb.org/propel-1.5.0BETA1.tar.gz">http://propel.phpdb.org/propel-1.5.0BETA1.tar.gz</a></div> <div><ul><li>PEAR Package</li></ul></div><div> > sudo pear config-set preferred_state beta</div><div> > sudo pear upgrade phpdb/propel-generator</div> <div> > sudo pear upgrade phpdb/propel-runti</div></div></div><p /><div><b>Feedback Is Welcome</b></div><p /><div>It is the perfect moment to test Propel 1.5 and check the compatibility of your existing applications built with Propel 1.3 and 1.4. Please open a Trac ticket for any problem you may encounter while upgrading. You can also find assistance in the <a href="http://groups.google.fr/group/propel-development">propel developers mailing-list</a>.</div> <p /><div>We plan on releasing 1.5.0 stable within three to four weeks if everything goes smoothly. Please test it now if you have the opportunity to do so, to avoid discovering possible issues after the stable release.</div></p>
Propel Query by Example2010-02-16T00:00:00+00:00http://www.propelorm.org/blog/2010/02/16/propel-query-by-example<p>If you’re used to Criteria and Peer methods, you may find the new query API introduced in Propel 1.5 intimidating. There is nothing to be afraid of: you will find this new API more intuitive and faster to read, write and test, whether you use a text editor or an IDE.<p /> To convince you, there is nothing better than a side-by-side comparison of the same query written with the old and the new API. Without further introduction, let’s dive in:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
* Retrieving an article by its primary key
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">retrieveByPk</span><span class="p">(</span><span class="mi">123</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span><span class="o">-></span><span class="nf">findPk</span><span class="p">(</span><span class="mi">123</span><span class="p">);</span>
<span class="cm">/*
* Retrieving the comments related to an article
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$comments</span> <span class="o">=</span> <span class="nv">$article</span><span class="o">-></span><span class="nf">getComments</span><span class="p">();</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$comments</span> <span class="o">=</span> <span class="nv">$article</span><span class="o">-></span><span class="nf">getComments</span><span class="p">();</span> <span class="c1">// no change</span>
<span class="cm">/*
* Retrieving an article from its title
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">TITLE</span><span class="p">,</span> <span class="s1">'FooBar'</span><span class="p">);</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelectOne</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span><span class="o">-></span><span class="nf">findOneByTitle</span><span class="p">(</span><span class="s1">'FooBar'</span><span class="p">);</span>
<span class="cm">/*
* Retrieving articles based on a word appearing in the title
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">TITLE</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LIKE</span><span class="p">);</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelect</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByTitle</span><span class="p">(</span><span class="s1">'%FooBar%'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving articles where the publication date is between last week and today
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">PUBLISHED_AT</span><span class="p">,</span> <span class="nb">time</span><span class="p">()</span> <span class="o">-</span> <span class="p">(</span><span class="mi">7</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span><span class="p">),</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">GREATER_THAN</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addAnd</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">PUBLISHED_AT</span><span class="p">,</span> <span class="nb">time</span><span class="p">(),</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LESS_THAN</span><span class="p">);</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelect</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByPublishedAt</span><span class="p">(</span><span class="k">array</span><span class="p">(</span>
<span class="s1">'min'</span> <span class="o">=></span> <span class="nb">time</span><span class="p">()</span> <span class="o">-</span> <span class="p">(</span><span class="mi">7</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span><span class="p">),</span>
<span class="s1">'max'</span> <span class="o">=></span> <span class="nb">time</span><span class="p">(),</span>
<span class="p">))</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving articles based on a custom condition
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">TITLE</span><span class="p">,</span> <span class="s1">'UPPER(article.TITLE) LIKE '</span> <span class="mf">.</span> <span class="nv">$pattern</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">CUSTOM</span><span class="p">);</span> <span class="c1">// risk of SQL injection!!</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelect</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'UPPER(Article.Title) like ?'</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">)</span> <span class="c1">// binding made by PDO, no injection risk</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving articles based on a word appearing in the title or the summary
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$cton1</span> <span class="o">=</span> <span class="nv">$c</span><span class="o">-></span><span class="nf">getNewCriterion</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">TITLE</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LIKE</span><span class="p">);</span>
<span class="nv">$cton2</span> <span class="o">=</span> <span class="nv">$c</span><span class="o">-></span><span class="nf">getNewCriterion</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">SUMMARY</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LIKE</span><span class="p">);</span>
<span class="nv">$cton1</span><span class="o">-></span><span class="nf">addOr</span><span class="p">(</span><span class="nv">$cton2</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nv">$cton1</span><span class="p">);</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelect</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Article.Title like ?'</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">orWhere</span><span class="p">(</span><span class="s1">'Article.Summary like ?'</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving articles based on a complex AND/OR clause
* Articles having name or summary like %FooBar% and published between $begin and $end
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$cton1</span> <span class="o">=</span> <span class="nv">$c</span><span class="o">-></span><span class="nf">getNewCriterion</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">TITLE</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LIKE</span><span class="p">);</span>
<span class="nv">$cton2</span> <span class="o">=</span> <span class="nv">$c</span><span class="o">-></span><span class="nf">getNewCriterion</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">SUMMARY</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LIKE</span><span class="p">);</span>
<span class="nv">$cton1</span><span class="o">-></span><span class="nf">addOr</span><span class="p">(</span><span class="nv">$cton2</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nv">$cton1</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">PUBLISHED_AT</span><span class="p">,</span> <span class="nv">$begin</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">GREATER_THAN</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addAnd</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">PUBLISHED_AT</span><span class="p">,</span> <span class="nv">$end</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LESS_THAN</span><span class="p">);</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelect</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5 "Reverse Polish Notation" style</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">condition</span><span class="p">(</span><span class="s1">'cond1'</span><span class="p">,</span> <span class="s1">'Title like ?'</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">condition</span><span class="p">(</span><span class="s1">'cond2'</span><span class="p">,</span> <span class="s1">'Summary like ?'</span><span class="p">,</span> <span class="s1">'%FooBar%'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">combine</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'cond1'</span><span class="p">,</span> <span class="s1">'cond2'</span><span class="p">),</span> <span class="s1">'or'</span><span class="p">,</span> <span class="s1">'cond3'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">condition</span><span class="p">(</span><span class="s1">'cond4'</span><span class="p">,</span> <span class="s1">'PublishedAt > ?'</span><span class="p">,</span> <span class="nv">$begin</span><span class="p">)</span>
<span class="o">-></span><span class="nf">condition</span><span class="p">(</span><span class="s1">'cond5'</span><span class="p">,</span> <span class="s1">'PublishedAt &lt; ?'</span><span class="p">,</span> <span class="nv">$end</span><span class="p">)</span>
<span class="o">-></span><span class="nf">combine</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'cond4'</span><span class="p">,</span> <span class="s1">'cond5'</span><span class="p">),</span> <span class="s1">'and'</span><span class="p">,</span> <span class="s1">'cond6'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">combine</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'cond3'</span><span class="p">,</span> <span class="s1">'cond6'</span><span class="p">),</span> <span class="s1">'and'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving the latest 5 articles
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addDescendingOrderByColumn</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">PUBLISHED_AT</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">setLimit</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelect</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">orderByPublishedAt</span><span class="p">(</span><span class="s1">'desc'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">limit</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving the last comment related to an article
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addDescendingOrderByColumn</span><span class="p">(</span><span class="nc">CommentPeer</span><span class="o">::</span><span class="no">PUBLISHED_AT</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">CommentPeer</span><span class="o">::</span><span class="no">ARTICLE_ID</span><span class="p">,</span> <span class="nv">$article</span><span class="o">-></span><span class="nf">getId</span><span class="p">());</span>
<span class="nv">$comment</span> <span class="o">=</span> <span class="nc">CommentPeer</span><span class="o">::</span><span class="nf">doSelectOne</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$comment</span> <span class="o">=</span> <span class="nc">CommentQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByArticle</span><span class="p">(</span><span class="nv">$article</span><span class="p">)</span>
<span class="o">-></span><span class="nf">orderByPublishedAt</span><span class="p">(</span><span class="s1">'desc'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
<span class="cm">/*
* Retrieving articles authored by someone
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addJoin</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">AUTHOR_ID</span><span class="p">,</span> <span class="nc">AuthorPeer</span><span class="o">::</span><span class="no">ID</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">AuthorPeer</span><span class="o">::</span><span class="no">NAME</span><span class="p">,</span> <span class="s1">'John Doe'</span><span class="p">);</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelect</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">useAuthorQuery</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByName</span><span class="p">(</span><span class="s1">'John Doe'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">endUse</span><span class="p">()</span>
<span class="o">-></span><span class="nf">find</span><span class="p">()</span>
<span class="cm">/*
* Retrieving articles authored by people of a certain group
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addJoin</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">AUTHOR_ID</span><span class="p">,</span> <span class="nc">AuthorPeer</span><span class="o">::</span><span class="no">ID</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addJoin</span><span class="p">(</span><span class="nc">AuthorPeer</span><span class="o">::</span><span class="no">GROUP_ID</span><span class="p">,</span> <span class="nc">GroupPeer</span><span class="o">::</span><span class="no">ID</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">GroupPeer</span><span class="o">::</span><span class="no">NAME</span><span class="p">,</span> <span class="s1">'The Foos'</span><span class="p">);</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelect</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">useAuthorQuery</span><span class="p">()</span>
<span class="o">-></span><span class="nf">useGroupQuery</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByName</span><span class="p">(</span><span class="s1">'The Foos'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">endUse</span><span class="p">()</span>
<span class="o">-></span><span class="nf">endUse</span><span class="p">()</span>
<span class="o">-></span><span class="nf">find</span><span class="p">()</span>
<span class="cm">/*
* Retrieving all articles and hydrating their category object in the same query
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelectJoinCategory</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">joinWith</span><span class="p">(</span><span class="s1">'Category'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving an article and its category by the article primary key
*/</span>
<span class="c1">// Propel 1.4</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">ArticlePeer</span><span class="o">::</span><span class="no">ID</span><span class="p">,</span> <span class="mi">123</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">setLimit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticlePeer</span><span class="o">::</span><span class="nf">doSelectJoinCategory</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="nv">$article</span> <span class="o">=</span> <span class="k">isset</span><span class="p">(</span><span class="nv">$articles</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="o">?</span> <span class="nv">$articles</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">:</span> <span class="kc">null</span><span class="p">;</span>
<span class="c1">// Propel 1.5</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">joinWith</span><span class="p">(</span><span class="s1">'Category'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findPk</span><span class="p">(</span><span class="mi">123</span><span class="p">);</span>
</code></pre></div></div>
<p>In addition, the new Propel Query API allows for things that were simply not possible with the Criteria and Peer API:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
* Retrieving articles and hydrating their author object and the author group
*/</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">joinWith</span><span class="p">(</span><span class="s1">'Article.Author'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">joinWith</span><span class="p">(</span><span class="s1">'Author.Group'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving articles based on a list of conditions
*/</span>
<span class="nv">$conds</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
<span class="s1">'Title'</span> <span class="o">=></span> <span class="s1">'Foo'</span><span class="p">,</span>
<span class="s1">'PublishedAt'</span> <span class="o">=></span> <span class="k">array</span><span class="p">(</span><span class="s1">'min'</span> <span class="o">=></span> <span class="nb">time</span><span class="p">()</span> <span class="o">-</span> <span class="p">(</span><span class="mi">7</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span><span class="p">))</span>
<span class="p">);</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByArray</span><span class="p">(</span><span class="nv">$conds</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="cm">/*
* Retrieving a paginated list of the latest articles
* Get the second page, 10 articles per page
*/</span>
<span class="nv">$articlePager</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">orderByPublishedAt</span><span class="p">(</span><span class="s1">'desc'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">paginate</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$articlePager</span> <span class="k">as</span> <span class="nv">$article</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// do stuff with an $article object</span>
<span class="p">}</span>
<span class="cm">/*
* Iterating over a very large list of results without running out of memory
*/</span>
<span class="nv">$articles</span> <span class="o">=</span> <span class="nc">ArticleQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">limit</span><span class="p">(</span><span class="mi">50000</span><span class="p">)</span>
<span class="o">-></span><span class="nf">useFormatter</span><span class="p">(</span><span class="nc">ModelCriteria</span><span class="o">::</span><span class="no">FORMAT_ON_DEMAND</span><span class="p">)</span>
<span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$articles</span> <span class="k">as</span> <span class="nv">$article</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// do stuff 50,000 times</span>
<span class="p">}</span>
</code></pre></div></div>
<p>There is more for you to discover, and fortunately, the <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/ModelCriteria">Propel Query documentation</a> is already up-to-date in the 1.5 branch.</p></p>
Meet Me At The Symfony Parade2010-02-15T00:00:00+00:00http://www.propelorm.org/blog/2010/02/15/meet-me-at-the-symfony-parade<p>The <a href="http://www.symfony-live.com/">Symfony Live 2010 Conference</a> starts tomorrow in Paris, France. I'll be attending the conference on Wednesday the 17th, so if you'd like to talk about Propel or any related subject, don't hesitate to meet me there.</p>
Reduce Your Query Count With Propel 1.52010-02-05T00:00:00+00:00http://www.propelorm.org/blog/2010/02/05/reduce-your-query-count-with-propel-1-5<p>Propel 1.5 offers a lot of new tools to help you reduce your query count. If you thought that the generated `doSelectJoin()` methods of Propel 1.2 were a great help, you're going to love Propel 1.5.<p /><strong>Instance Pool</strong><p /> But first, let's remind those of you who are not familiar with ORM terminology of one important notion: ''hydration''. Hydration is the process of populating a PHP object from a row in a database query result. In Propel, every generated model object offers a `hydrate()` method just for that purpose. <p /> This hydration process can take some time, so Propel stores all the hydrated objects in an internal registry, called the ''instance pool''. If you try to hydrate the same row twice in a script, then Propel returns the object hydrated the first time, that it has kept in the instance pool. <p /> Even better, if you need to retrieve a row using its primary key, and if an object was already hydrated from that row, Propel won't even execute the database query, but instead it will return the requested object from the instance pool. This is a precious time saver for queries like the following:<p /> [code]
$books = BookQuery::create()->find(); // one database query
$authors = AuthorQuery::create()->find(); // one database query
foreach ($books as $book) {
echo $book->getAuthor()->getName(); // no query, since all the author rows are already hydrated
}
[/code]<p />Object Instance Pool has been in Propel since version 1.3. Chances are that you alredy benefit from it.<p /><strong>Collection Relation Population</strong><p />But the previous example does not correspond to any real use case. In practice, you usually retrieve a subset of all the Authors and Books, and you can never be sure that the instance pool will contain the related object you need according to the query you make.<p /> Fortunately, Propel offers a quick way to hydrate the objects related to a list of objects. This feature uses the fact that Propel queries return a `PropelCollection` object and not an array. That means that you can call methods on the results of a query. Starting with Propel 1.5, the `PropelObjectCollection` class offers a `populateRelation()` method that will change your life:<br /> <!--more--><br />[code]
$books = BookQuery::create()->find(); // one database query
$books->populateRelation('Author'); // one database query
foreach ($books as $book) {
echo $book->getAuthor()->getName(); // no query, since the necessary author rows are already hydrated
}
[/code]<p />The difference from the previous example is that `populateRelation()` retrieves and hydrates only the `Author` objects related to the `Book` objects present in the `$books` collection. So `populateRelation()` needs a single effective query to reduce the query count.<p /> And the greatest thing about `populateRelation()` is that it also works the other way around, that means for one-to-many relationships:<p />[code]
$authors = AuthorQuery::create()->find(); // one database query
$authors->populateRelation('Book'); // one database query
foreach ($authors as $author) {
foreach ($author->getBooks() as $book) { // no query, since the necessary book rows are already hydrated
echo $book->getTitle(), $author->getName(); // no query either
}
}
[/code]<p />No more scripts with a query count proportional to the number of results! `PropelObjectCollection::populateRelation()` will keep the query count reasonnable in all situations, in all database models.<p /> <strong>Query With Class</strong><p />There is one limitation, though. `populateRelation()` only allows to hydrate relations of the main object. You can't hydrate, for instance, relations of a relation. For that purpose, you will need to add one line in your Query, and it's called `joinWith()`.<p /> `joinWith()` expects a composed relation name ('Start.End') and can be called several times in a query. It tells the query to also hydrate the related objects on a many-to-one relationship. That makes the following query possible:<p /> [code]
$books = BookQuery::create()
->joinWith('Book.Author')
->joinWith('Book.Publisher')
->joinWith('Publisher.Group')
->find(); // one database query
foreach ($books as $book) {
echo $book->getAuthor()->getName(); // no query
echo $book->getPublisher()->getName(); // no query
echo $book->getPublisher()->getGroup()->getName(); // no query
}
[/code]<p />`joinWith()` doesn't work with one-to-many relationships, because it would require some very heavy code to handle the LIMIT clause in a query. And since `populateRelation()` deals with most of the use cases, Propel keeps its codebase lightweight and fast by not implementing `joinWith()` on one-to-many relationships.<p /> <strong>Query With Column</strong><p />Sometimes you don't need to hydrate a full object in addition to the main object. If you only need one additional column, the `withColumn()` method is a good alternative to `joinWith()`:<p /> [code]
$book = PropelQuery::from('Book')
->join('Book.Author')
->withColumn('Author.Name', 'AuthorName')
->findOne();
$authorName = $book->getAuthorName();
[/code]<p />Propel adds the 'with' column to the SELECT clause of the query, and uses the second argument of the `withColumn()` call as a column alias. This additional column is later available as a 'virtual' column, i.e. using a getter that does not correspond to a real column. You don't actually need to write the `getAuthorName()` method ; Propel uses the magic `__call()` method of the generated `Book` class to catch the call to a virtual column.<p /> `withColumn()` is also of great use to add calculated columns:<p />[code]
$authors = PropelQuery::from('Author')
->leftJoin('Author.Book')
->withColumn('COUNT(Book.Id)', 'NbBooks')
->groupBy('Author.Id')
->find();
foreach ($authors as $author) {
echo $author->getName() . ': ' . $author->getNbBooks() . " books\n";
}
[/code]<p />With a single SQL query, you can have both a list of objects and an additional column for each object. This makes `withColumn()` a great query saver.<p /> Of course, you can call `withColumn()` multiple times to add more than one virtual column to the resulting objects.<p /><strong>Conclusion</strong><p />Instance pooling, collection relation population, `joinWith()`, and `withColumn()` are the new weapons that Propel 1.5 gives you to fight for your application performance. You will find them very easy to use, and you will soon wonder how you could program without them in the past!</p>
Many-to-many Relationships: Check!2010-02-04T00:00:00+00:00http://www.propelorm.org/blog/2010/02/04/many-to-many-relationships-check-<p>Propel users used to complain about the lack of support for many-to-many relationships in the generated model classes. Writing a custom getter by hand for this kind of relationship wasn't so hard, but since people kept doing so repeatedly, it became clear that Propel had to support it. After all, the purpose of an ORM is to automate repetitive tasks and to make the life of a developer easier.</p>
<p>Starting with Propel 1.5, many-to-many relationships are first class citizens in Propel. In order to declare them, you must set the `isCrossRef` attribute to `true` in the `<table>` element of the cross-reference table (or "junction" table). For instance, if the `user` and `group` tables are related by a many-to-many relationship, this happens through the rows of a `user_group` table:</p>
<div class="CodeRay">
<div class="code"><pre>
<table name="user">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
<column name="name" type="VARCHAR" size="32"/>
</table>
<table name="group">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
<column name="name" type="VARCHAR" size="32"/>
</table>
<table name="user_group" isCrossRef="true">
<column name="user_id" type="INTEGER" primaryKey="true"/>
<column name="group_id" type="INTEGER" primaryKey="true"/>
<foreign-key foreignTable="user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<foreign-key foreignTable="group">
<reference local="group_id" foreign="id"/>
</foreign-key>
</table>
</pre></div></div>
<p>From both sides of the relation, a many-to-many relationship is seen as a one-to-many relationship ; besides, Propel takes care of creating and retrieving instances of the middle class, so you never actually need to deal with them. That means that manipulating many-to-many relationships is nothing new if you already deal with one-to-many relationships:<!--more--></p>
<div class="CodeRay">
<div class="code"><pre>
// create and relate objects as if they shared a one-to-many relationship
$user = new User();
$user->setName('John Doe');
$group = new Group();
$group->setName('Anonymous');
// relate $user and $group
$user->addGroup($group);
// save the $user object, the $group object, and a new instance of the UserGroup class
$user->save();
// retrieve objects as if they shared a one-to-many relationship
$groups = $user->getGroups();
// the model query also features a smart filter method for the relation
$groups = GroupQuery::create()
->filterByUser($user)
->find();
</pre></div></div>
<p>So besides the `isCrossRef` attribute, there is nothing to learn - Propel avoids introducing new conventions when existing ones fit a new use case.</p>
New In Propel 1.5: Concrete Table Inheritance And A New Behavior. Wait, That's The Same Thing.2010-01-26T00:00:00+00:00http://www.propelorm.org/blog/2010/01/26/new-in-propel-1-5-concrete-table-inheritance-and-a-new-behavior-wait-that-s-the-same-thing-<p>Inheritance is very common in the Object-Oriented world, much less in the database world. Yet, being able to extend a model allows for clean code organization and more complex logic. Some RDBMS, like PostgreSQL, offer <a href="http://www.postgresql.org/docs/8.1/static/ddl-inherit.html">native inheritance</a>. Starting with Propel 1.5, Propel gives the same ability to every compatible storage (including MySQL, Oracle, SQLite, and MSSQL) through the new <span style="font-family: courier new,monospace;">concrete_inheritance</span> behavior.</p>
<h2 id="understanding-inheritance-design-patterns">Understanding Inheritance Design Patterns</h2>
<p>Propel has offered <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/Inheritance#SingleTableInheritance">Single Table Inheritance</a> for a long time. This feature allows several model classes to extend one another, but all the records are persisted in a single table. For complex inheritance patterns, this leads to tables with a lot of columns - and a lot of NULL values in the records.</p>
<p>Another common strategy for implementing inheritance in an ORM is called <a href="http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html">Concrete Table Inheritance</a>. In this case, each child class has its own table for storage, and the inheritance adds the columns of the parent class to each of the child classes. It’s this implementation that Propel 1.5 introduces - with a twist.</p>
<p><strong>Tip</strong>: If you want to know more about table inheritance and other design patterns, I highly recommend the reading of “<a href="http://martinfowler.com/books.html#eaa">Patterns Of Enterprise Application Architecture</a>”, by Martin Fowler.</p>
<h2 id="the-concrete-table-inheritance-behavior">The Concrete Table Inheritance Behavior</h2>
<p>In the following example, the <code class="language-plaintext highlighter-rouge">article</code> and <code class="language-plaintext highlighter-rouge">video</code> tables use this behavior to inherit the columns and foreign keys of their parent table, <code class="language-plaintext highlighter-rouge">content</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><table</span> <span class="na">name=</span><span class="s">"content"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">type=</span><span class="s">"INTEGER"</span> <span class="na">primaryKey=</span><span class="s">"true"</span> <span class="na">autoIncrement=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"title"</span> <span class="na">type=</span><span class="s">"VARCHAR"</span> <span class="na">size=</span><span class="s">"100"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"category_id"</span> <span class="na">required=</span><span class="s">"false"</span> <span class="na">type=</span><span class="s">"INTEGER"</span><span class="nt">></span>
<span class="nt"><foreign-key</span> <span class="na">foreignTable=</span><span class="s">"category"</span> <span class="na">onDelete=</span><span class="s">"cascade"</span><span class="nt">></span>
<span class="nt"><reference</span> <span class="na">local=</span><span class="s">"category_id"</span> <span class="na">foreign=</span><span class="s">"id"</span> <span class="nt">/></span>
<span class="nt"></foreign-key></span>
<span class="nt"></table></span>
<span class="nt"><table</span> <span class="na">name=</span><span class="s">"category"</span><span class="nt">></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">required=</span><span class="s">"true"</span> <span class="na">primaryKey=</span><span class="s">"true"</span> <span class="na">autoIncrement=</span><span class="s">"true"</span> <span class="na">type=</span><span class="s">"INTEGER"</span> <span class="nt">/></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"name"</span> <span class="na">type=</span><span class="s">"VARCHAR"</span> <span class="na">size=</span><span class="s">"100"</span> <span class="na">primaryString=</span><span class="s">"true"</span> <span class="nt">/></span>
<span class="nt"></table></span>
<span class="nt"><table</span> <span class="na">name=</span><span class="s">"article"</span><span class="nt">></span>
<span class="nt"><behavior</span> <span class="na">name=</span><span class="s">"concrete_inheritance"</span><span class="nt">></span>
<span class="nt"><parameter</span> <span class="na">name=</span><span class="s">"extends"</span> <span class="na">value=</span><span class="s">"content"</span> <span class="nt">/></span>
<span class="nt"></behavior></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"body"</span> <span class="na">type=</span><span class="s">"VARCHAR"</span> <span class="na">size=</span><span class="s">"100"</span><span class="nt">/></span>
<span class="nt"></table></span>
<span class="nt"><table</span> <span class="na">name=</span><span class="s">"video"</span><span class="nt">></span>
<span class="nt"><behavior</span> <span class="na">name=</span><span class="s">"concrete_inheritance"</span><span class="nt">></span>
<span class="nt"><parameter</span> <span class="na">name=</span><span class="s">"extends"</span> <span class="na">value=</span><span class="s">"content"</span> <span class="nt">/></span>
<span class="nt"></behavior></span>
<span class="nt"><column</span> <span class="na">name=</span><span class="s">"resource_link"</span> <span class="na">type=</span><span class="s">"VARCHAR"</span> <span class="na">size=</span><span class="s">"100"</span><span class="nt">/></span>
<span class="nt"></table></span>
</code></pre></div></div>
<p>The behavior copies the columns of the parent table to the child tables. That means that the generated <code class="language-plaintext highlighter-rouge">Article</code> and <code class="language-plaintext highlighter-rouge">Video</code> models have a <code class="language-plaintext highlighter-rouge">Title</code> property and a <code class="language-plaintext highlighter-rouge">Category</code> relationship:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="c1">// create a new Category</span>
<span class="nv">$cat</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Category</span><span class="p">();</span>
<span class="nv">$cat</span><span class="o">-></span><span class="nf">setName</span><span class="p">(</span><span class="s1">'Movie'</span><span class="p">);</span>
<span class="nv">$cat</span><span class="o">-></span><span class="nf">save</span><span class="p">();</span>
<span class="c1">// create a new Article</span>
<span class="nv">$art</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Article</span><span class="p">();</span>
<span class="nv">$art</span><span class="o">-></span><span class="nf">setTitle</span><span class="p">(</span><span class="s1">'Avatar Makes Best Opening Weekend in the History'</span><span class="p">);</span>
<span class="nv">$art</span><span class="o">-></span><span class="nf">setCategory</span><span class="p">(</span><span class="nv">$cat</span><span class="p">);</span>
<span class="nv">$art</span><span class="o">-></span><span class="nf">setContent</span><span class="p">(</span><span class="s1">'With $232.2 million worldwide total, Avatar had one of the best-opening weekends in the history of cinema.'</span><span class="p">);</span>
<span class="nv">$art</span><span class="o">-></span><span class="nf">save</span><span class="p">();</span>
<span class="c1">// create a new Video</span>
<span class="nv">$vid</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Video</span><span class="p">();</span>
<span class="nv">$vid</span><span class="o">-></span><span class="nf">setTitle</span><span class="p">(</span><span class="s1">'Avatar Trailer'</span><span class="p">);</span>
<span class="nv">$vid</span><span class="o">-></span><span class="nf">setCategory</span><span class="p">(</span><span class="nv">$cat</span><span class="p">);</span>
<span class="nv">$vid</span><span class="o">-></span><span class="nf">setResourceLink</span><span class="p">(</span><span class="s1">'http://www.avatarmovie.com/index.html'</span><span class="p">)</span>
<span class="nv">$vid</span><span class="o">-></span><span class="nf">save</span><span class="p">();</span>
</code></pre></div></div>
<h2 id="model-inheritance">Model Inheritance</h2>
<p>If Propel stopped there, the <code class="language-plaintext highlighter-rouge">concrete_inheritance</code> behavior would only provide a shorcut to avoid repeating tags in the schema. But the behaviors uses PHP’s inheritance system to make the models of the child tables extends the model of the parent table: the <code class="language-plaintext highlighter-rouge">Article</code> and <code class="language-plaintext highlighter-rouge">Video</code> classes actually extend the <code class="language-plaintext highlighter-rouge">Content</code> class:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="kd">class</span> <span class="nc">Content</span> <span class="kd">extends</span> <span class="nc">BaseContent</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">getCategoryName</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-&</span><span class="n">gt</span><span class="p">;</span><span class="nf">getCategory</span><span class="p">()</span><span class="o">-&</span><span class="n">gt</span><span class="p">;</span><span class="nf">getName</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">echo</span> <span class="nv">$art</span><span class="o">-></span><span class="nf">getCategoryName</span><span class="p">();</span> <span class="c1">// 'Movie'</span>
<span class="k">echo</span> <span class="nv">$vid</span><span class="o">-></span><span class="nf">getCategoryName</span><span class="p">();</span> <span class="c1">// 'Movie'</span>
</code></pre></div></div>
<p>Imagine how convenient this class extension is to avoid repeating code across similar classes. And contrary to the Single Table Inheritance pattern, the Concrete Table Inheritance scales without problems to dozens of tables with very different columns.</p>
<p>The fact that the behavior system, introduced in Propel 1.4, provides the best implementation for the Concrete Table Inheritance, shows how powerful behaviors can be. Propel keeps getting new features without adding too much complexity to the core.</p>
<h2 id="one-more-thing">One More Thing</h2>
<p>Usually, ORMs stop the Concrete Table Inheritance implementation there. But not Propel. The <code class="language-plaintext highlighter-rouge">concrete_inheritance</code> behavior does not only copy the <em>table structure</em>, it also copies <em>data</em>.<p /> Every time you save an <code class="language-plaintext highlighter-rouge">Article</code> or a <code class="language-plaintext highlighter-rouge">Video</code> object, Propel saves a copy of the <code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">category_id</code> columns in a <code class="language-plaintext highlighter-rouge">Content</code> object. Consequently, retrieving objects regardless of their child type becomes very easy:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="nv">$conts</span> <span class="o">=</span> <span class="nc">ContentQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span><span class="o">-></span><span class="nf">find</span><span class="p">();</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$conts</span> <span class="k">as</span> <span class="nv">$content</span><span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="nv">$content</span><span class="o">-></span><span class="nf">getTitle</span><span class="p">()</span> <span class="mf">.</span> <span class="s2">"("</span><span class="mf">.</span> <span class="nv">$content</span><span class="o">-></span><span class="nf">getCategoryName</span><span class="p">()</span> <span class="s2">")/n"</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Avatar Makes Best Opening Weekend in the History (Movie)</span>
<span class="c1">// Avatar Trailer (Movie)</span>
</code></pre></div></div>
<p>The resulting relational model is denormalized - in other terms, data is copied across tables - but the behavior takes care of everything for you. That allows for very effective read queries on complex inheritance structures.</p>
<p>Check out the brand new <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/Inheritance#ConcreteTableInheritance">Inheritance Documentation</a> for more details on using and customizing this behavior.</p>
Yet Another Propel Behavior: Sluggable2010-01-19T00:00:00+00:00http://www.propelorm.org/blog/2010/01/19/yet-another-propel-behavior-sluggable<p>The development on the Propel 1.5 branch keeps up at a good pace. Today, a new addition was made to the list of the available Propel behaviors: <strong>sluggable</strong>. </p>
<div>It does exactly what you would expect: automatically compose a unique slug for every object that you save. The slug can be used to provide friendly URLs:</div>
<div><span style="font-family: Verdana, Arial, Bitstream Vera Sans, Helvetica, sans-serif; font-size: 13px;">
</span><div class="CodeRay">
<div class="code"><pre>$post1 = new Post();
$post1->setTitle('How Is Life On Earth?');
$post1->setContent('Lorem Ipsum...');
$post1->save();
echo $post1->getSlug(); // '/posts/how-is-life-on-earth' </pre></div>
</div>
<div class="CodeRay">
<div class="code"><pre>Once your objects have slugs, it is very easy to find the object matching a given slug - for instance, from an URL:</pre></div>
</div>
<div class="CodeRay">
<div class="code"><pre>$post = PostQuery::create()->findOneBySlug('/posts/how-is-life-on-earth');</pre></div>
</div>
<div class="CodeRay">
<div class="code"><pre>As for other behaviors, it it dead simple to initialize. Just add the sluggable behavior tag in your schema, rebuild your model, and you're ready to go:</pre></div>
</div>
<div class="CodeRay">
<div class="code"><pre><table name="post">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="title" type="VARCHAR" required="true" primaryString="true" /
<column name="content" type="LONGVARCHAR"/>
<behavior name="sluggable">
<parameter name="slug_pattern" value="/posts/{Title}" />
</behavior>
</table></pre></div>
</div>
<div class="CodeRay">
<div class="code"><pre>Make sure you read the sluggable documentation to see all the available settings to customize this brand new behavior.</pre></div>
</div>
<p />
</div>
Propel 1.4.1 Is Out2010-01-13T00:00:00+00:00http://www.propelorm.org/blog/2010/01/13/propel-1-4-1-is-out<p>The first maintenance release for the 1.4 branch has just been published, in all your favorite formats:<br /><ul><li>Subversion tag</li></ul><span style="font-family: courier new,monospace;"> > svn checkout <a href="http://svn.phpdb.org/propel/tags/1.4.1">http://svn.phpdb.org/propel/tags/1.4.1</a> </span><br /> <ul><li>Tarball</li></ul><span style="font-family: courier new,monospace;"> <a href="http://propel.phpdb.org/propel-1.4.1.tar.gz">http://propel.phpdb.org/propel-1.4.1.tar.gz</a></span><br /><ul><li>PEAR Package</li></ul> <span style="font-family: courier new,monospace;"> > sudo pear upgrade phpdb/propel-generator</span><br style="font-family: courier new,monospace;" /><span style="font-family: courier new,monospace;"> > sudo pear upgrade phpdb/propel-runtime</span><p /> This release contains only bugfixes - no new feature - and is backwards compatible with 1.4.0. The changelog is available at <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.4/CHANGELOG">http://propel.phpdb.org/trac/wiki/Users/Documentation/1.4/CHANGELOG</a>. The online API documentation has been updated to reflect the latest changes.<p /> The next maintenance release is scheduled for mid-April.</p>
The Ultimate ORM Query API. Only With Propel.2010-01-06T00:00:00+00:00http://www.propelorm.org/blog/2010/01/06/the-ultimate-orm-query-api-only-with-propel-<p>This post is pretty long, but it explains a new shift in the Propel Query API, and shows how this new syntax will be the killer feature of Propel 1.5. You’ll soon learn more about it, but first, let’s see is how the idea came.</p>
<h2 id="better-is-not-best">Better Is Not Best</h2>
<p>The latest additions in the Propel Query API intended to make it easier to read and write queries. For one part, an important factor of readability is the amount of code necessary to write a query. From this point of view, the recent ModelCriteria syntax, introduced in the 1.5 branch, appears as an important improvement:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Propel 1.4 way</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">BookPeer</span><span class="o">::</span><span class="no">PRICE</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LESS_THAN</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addAscendingOrderByColumn</span><span class="p">(</span><span class="nc">BookPeer</span><span class="o">::</span><span class="no">TITLE</span><span class="p">);</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookPeer</span><span class="o">::</span><span class="nf">doSelectOne</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">PropelQuery</span><span class="o">::</span><span class="nf">from</span><span class="p">(</span><span class="s1">'Book'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Book.Price < ?'</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span>
<span class="o">-></span><span class="nf">orderBy</span><span class="p">(</span><span class="s1">'Book.Title'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<p>But after introducing this new syntax in a <a href="http://propel.posterous.com/propels-criteria-gets-smarter">recent blog post</a>, I received negative reactions from various Propel users who complained about the lost ability to use autocompletion in an IDE.</p>
<p>At first, I saw these complaints as reluctance to change. I thought that people would get used to the new syntax pretty quickly. They would also see how the added benefits of <code class="language-plaintext highlighter-rouge">ModelCriteria</code> compensate the lost IDE autocompletion.</p>
<h2 id="ide-autocompletion-is-a-key-feature">IDE Autocompletion Is A Key Feature</h2>
<p>And then I tried it myself, with an IDE. I have to admit that autocompletion helps a lot. In fact, using NetBeans, the Propel 1.5 syntax requires a little longer to write than the Propel 1.4 syntax. Besides, the IDE provides the comfort and insurance that the code was correct before running it, while any typo in the 1.5 code can only appear at runtime.</p>
<p>That didn’t decide me to use an IDE for day-to-day coding - I’m satisfied with the reactivity of Textmate and I don’t like the blinking interfaces of IDEs. But that opened my eyes on a key feature that Propel has, and that no other ORM offers. The ability to use autocompletion is one big advantage of Propel that can’t be left aside, or else Propel will lose part of its IDE-users community.</p>
<p>That didn’t decide me to drop the Propel 1.5 syntax either, because after years of using Propel, I can’t read a Criteria query without whispering, and I can’t write one without complaining. The fast syntax offered by other ORMs makes Propel look like C. The Propel 1.4 syntax is still counterintuitive, and the fact that it relies on class constants makes a lot of thinks impossible.</p>
<p>That means that the ModelCriteria syntax is just a step in the path of The Definitive Query Syntax. But then how to improve it?</p>
<h2 id="code-generation-to-the-rescue">Code Generation To The Rescue</h2>
<p>ModelCriteria uses runtime introspection to offer very powerful features. And it also uses code generation to support query behaviors. In fact, any call to <code class="language-plaintext highlighter-rouge">PropelQuery::from('Book')</code> returns an instance of <code class="language-plaintext highlighter-rouge">BookQuery</code>, which is a class generated by Propel alongside <code class="language-plaintext highlighter-rouge">Book</code> and <code class="language-plaintext highlighter-rouge">BookPeer</code>.</p>
<p>But the <code class="language-plaintext highlighter-rouge">PropelQuery::from()</code> factory has no way to tell an IDE which class it is going to return. The phpDoc for this method stipulates that the return value is an instance of <code class="language-plaintext highlighter-rouge">ModelCriteria</code>, but in reality it’s a subclass of it.</p>
<p>So the first step is to improve the generated <code class="language-plaintext highlighter-rouge">BookQuery</code> to give it the charge of the factory. By generating a <code class="language-plaintext highlighter-rouge">BookQuery::create()</code> method, returning a new instance of <code class="language-plaintext highlighter-rouge">BookQuery</code>, it becomes possible to tell the IDE which class is actually in use:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Old Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">PropelQuery</span><span class="o">::</span><span class="nf">from</span><span class="p">(</span><span class="s1">'Book'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Book.Price < ?'</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span>
<span class="o">-></span><span class="nf">orderBy</span><span class="p">(</span><span class="s1">'Book.Title'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
<span class="c1">// New Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Book.Price < ?'</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span>
<span class="o">-></span><span class="nf">orderBy</span><span class="p">(</span><span class="s1">'Book.Title'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<h2 id="generated-filter-methods">Generated Filter Methods</h2>
<p>But the generated query objects, like <code class="language-plaintext highlighter-rouge">BookQuery</code>, have very few methods. And there is no way an IDE will help the developer to write the content of a string, as in the <code class="language-plaintext highlighter-rouge">where()</code> argument. So it’s necessary to modify the generator of the custom Query objects again, in order to add one <code class="language-plaintext highlighter-rouge">where()</code> method for every column of the table. And since the operator cannot be part of the method name, it has to move as second argument of the call. The example query then becomes:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">BookQuery</span> <span class="kd">extends</span> <span class="nc">BaseBookQuery</span>
<span class="p">{</span>
<span class="c1">// this method is generated</span>
<span class="cd">/**
* Filter the query on the price column
*
* @param mixed $price The value to use as filter.
* @param string $operator Operator to use for the comparison
*
* @return BookQuery The current query, for fluid interface
*/</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">wherePrice</span><span class="p">(</span><span class="nv">$price</span><span class="p">,</span> <span class="nv">$operator</span> <span class="o">=</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">EQUAL</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">whereColumn</span><span class="p">(</span><span class="s1">'Price'</span><span class="p">,</span> <span class="nv">$price</span><span class="p">,</span> <span class="nv">$operator</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// New Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">wherePrice</span><span class="p">(</span><span class="mi">40</span><span class="p">,</span> <span class="s1">'<='</span><span class="p">)</span>
<span class="o">-></span><span class="nf">orderBy</span><span class="p">(</span><span class="s1">'Book.Price'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<p>Notice how the phpDoc of the generated <code class="language-plaintext highlighter-rouge">wherePrice()</code> method stipulates that the method returns a <code class="language-plaintext highlighter-rouge">BookQuery</code> and not only a <code class="language-plaintext highlighter-rouge">ModelCriteria</code>. This is important to allow IDEs to keep on suggesting the correct methods when the fluid interface is used.</p>
<p>After seing the <code class="language-plaintext highlighter-rouge">wherePrice()</code> method call, you probably agree that it is not very good looking ; the operator without the column name is a call for a return to the Criteria constants. Maybe there is an alternative solution. Since the <code class="language-plaintext highlighter-rouge">wherePrice()</code> method is generated, the generator can use the fact that the <code class="language-plaintext highlighter-rouge">price</code> column is numeric to offer additional abilities. After further thinking, each column type should offer special comparison types : a string column should support wildcards, a boolean column should support string booleans (like ‘no’ or ‘false’), etc.</p>
<p>So after a new addition to the query generator, here is how the query looks:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">BookQuery</span> <span class="kd">extends</span> <span class="nc">BaseBookQuery</span>
<span class="p">{</span>
<span class="c1">// this method is generated</span>
<span class="cd">/**
* Filter the query on the price column
*
* @param double|array $price The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
*
* @return BookQuery The current query, for fluid interface
*/</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">wherePrice</span><span class="p">(</span><span class="nv">$price</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$price</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">isset</span><span class="p">(</span><span class="nv">$price</span><span class="p">[</span><span class="s1">'min'</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="nf">whereColumn</span><span class="p">(</span><span class="s1">'Price'</span><span class="p">,</span> <span class="nv">$price</span><span class="p">[</span><span class="s1">'min'</span><span class="p">],</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">GREATER_EQUAL</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="k">isset</span><span class="p">(</span><span class="nv">$price</span><span class="p">[</span><span class="s1">'max'</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="nf">whereColumn</span><span class="p">(</span><span class="s1">'Price'</span><span class="p">,</span> <span class="nv">$price</span><span class="p">[</span><span class="s1">'max'</span><span class="p">],</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LESS_EQUAL</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">whereColumn</span><span class="p">(</span><span class="s1">'Price'</span><span class="p">,</span> <span class="nv">$price</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">EQUAL</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// New Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">wherePrice</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'max'</span> <span class="o">=></span> <span class="mi">40</span><span class="p">))</span>
<span class="o">-></span><span class="nf">orderBy</span><span class="p">(</span><span class="s1">'Book.Title'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<p>The final touch is to rename these <code class="language-plaintext highlighter-rouge">whereXXX()</code> methods into <code class="language-plaintext highlighter-rouge">filterByXXX()</code> (and to rename <code class="language-plaintext highlighter-rouge">whereColumn()</code> to <code class="language-plaintext highlighter-rouge">filterBy()</code>). It’s not just a matter of taste, it’s a similarity with <code class="language-plaintext highlighter-rouge">orderBy()</code> and <code class="language-plaintext highlighter-rouge">groupBy()</code>, and a deliberate step away from relational SQL. More on that matter later, but in the meantime, here is the query again:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">BookQuery</span> <span class="kd">extends</span> <span class="nc">BaseBookQuery</span>
<span class="p">{</span>
<span class="c1">// this method is generated</span>
<span class="cd">/**
* Filter the query on the price column
*
* @param double|array $price The value to use as filter.
* Accepts an associative array('min' => $minValue, 'max' => $maxValue)
*
* @return BookQuery The current query, for fluid interface
*/</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">filterByPrice</span><span class="p">(</span><span class="nv">$price</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$price</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">isset</span><span class="p">(</span><span class="nv">$price</span><span class="p">[</span><span class="s1">'min'</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="nf">filterBy</span><span class="p">(</span><span class="s1">'Price'</span><span class="p">,</span> <span class="nv">$price</span><span class="p">[</span><span class="s1">'min'</span><span class="p">],</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">GREATER_EQUAL</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="k">isset</span><span class="p">(</span><span class="nv">$price</span><span class="p">[</span><span class="s1">'max'</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="nf">filterBy</span><span class="p">(</span><span class="s1">'Price'</span><span class="p">,</span> <span class="nv">$price</span><span class="p">[</span><span class="s1">'max'</span><span class="p">],</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LESS_EQUAL</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">filterBy</span><span class="p">(</span><span class="s1">'Price'</span><span class="p">,</span> <span class="nv">$price</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">EQUAL</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// New Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByPrice</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'max'</span> <span class="o">=></span> <span class="mi">40</span><span class="p">))</span>
<span class="o">-></span><span class="nf">orderBy</span><span class="p">(</span><span class="s1">'Book.Price'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<h2 id="phpdoc-for-magic-methods">phpDoc For Magic Methods</h2>
<p>As for allowing the <code class="language-plaintext highlighter-rouge">orderBy()</code> method to use IDE completion, using generated methods is out of the question. It is acceptable to add filter methods that react differently according to the column type, it is not acceptable to add one-line proxy methods for each column just to support an <code class="language-plaintext highlighter-rouge">orderByTitle()</code> syntax.<p /> The ModelCriteria already supports a call to <code class="language-plaintext highlighter-rouge">orderByTitle()</code>, through the magic <code class="language-plaintext highlighter-rouge">__call()</code> method. But magic and IDE completion don’t get along well with each other. Or do they?</p>
<p>The phpDocumentor grammar allows one to document magic methods by way of <a href="http://manual.phpdoc.org/HTMLSmartyConverter/PHP/phpDocumentor/tutorial_tags.method.pkg.html"><code class="language-plaintext highlighter-rouge">@method</code> comments</a> in the class description, that the IDEs recognize. So the alternative to generating one method for every column is to generate one comment line for every column. That way the IDE sees the <code class="language-plaintext highlighter-rouge">orderByTitle()</code> method at development time, but this method doesn’t bloat the <code class="language-plaintext highlighter-rouge">BookQuery</code> code since <code class="language-plaintext highlighter-rouge">__call()</code> already handles it pretty well.</p>
<p>So the query can be even more ‘IDEified’ as follows:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/**
* Base class that represents a query for the 'book' table.
*
* Book Table
*
* @method BookQuery orderById($order = Criteria::ASC) Order by the id column
* @method BookQuery orderByTitle($order = Criteria::ASC) Order by the title column
* @method BookQuery orderByISBN($order = Criteria::ASC) Order by the isbn column
* @method BookQuery orderByPrice($order = Criteria::ASC) Order by the price column
* @method BookQuery orderByPublisherId($order = Criteria::ASC) Order by the publisher_id column
* @method BookQuery orderByAuthorId($order = Criteria::ASC) Order by the author_id column
*/</span>
<span class="kd">class</span> <span class="nc">BookQuery</span> <span class="kd">extends</span> <span class="nc">BaseBookQuery</span>
<span class="p">{</span>
<span class="mf">...</span>
<span class="p">}</span>
<span class="c1">// New Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByPrice</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'min'</span> <span class="o">=></span> <span class="mi">40</span><span class="p">))</span>
<span class="o">-></span><span class="nf">orderByTitle</span><span class="p">()</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<p>The same addition is required for <code class="language-plaintext highlighter-rouge">groupByXXX()</code> methods.</p>
<p>And while adding smart phpDoc comments to the query class description, why not override the phpDoc for <code class="language-plaintext highlighter-rouge">findOne()</code>? That way an IDE knows that the returned <code class="language-plaintext highlighter-rouge">$book</code> is not just a Propel <code class="language-plaintext highlighter-rouge">BaseObject</code>, it’s a <code class="language-plaintext highlighter-rouge">Book</code>.</p>
<h2 id="how-about-relationships">How About Relationships?</h2>
<p>So far, so good, but how about the handling of conditions on related tables? For instance, how to translate this Propel 1.4 query into the new syntax?</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Find first book published at 'Penguin' editions</span>
<span class="c1">// Propel 1.4 way</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addJoin</span><span class="p">(</span><span class="nc">BookPeer</span><span class="o">::</span><span class="no">PUBLISHER_ID</span><span class="p">,</span> <span class="nc">PublisherPeer</span><span class="o">::</span><span class="no">ID</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">PublisherPeer</span><span class="o">::</span><span class="no">NAME</span><span class="p">,</span> <span class="s1">'Penguin'</span><span class="p">);</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookPeer</span><span class="o">::</span><span class="nf">doSelectOne</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
</code></pre></div></div>
<p>This is in fact quite easy, since a recent addition to the <code class="language-plaintext highlighter-rouge">ModelCriteria</code> class allows it to use a secondary query object instead of applying conditions on a related column. So without any change in the Query generator, this can be written as follows:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// New Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nb">join</span><span class="p">(</span><span class="s1">'Book.Publisher'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">useQuery</span><span class="p">(</span><span class="s1">'Publisher'</span><span class="p">)</span> <span class="c1">// returns a new PublisherQuery instance</span>
<span class="o">-></span><span class="nf">filterByName</span><span class="p">(</span><span class="s1">'Penguin'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">endUse</span><span class="p">()</span> <span class="c1">// returns the original BookQuery instance, merged with the PublisherQuery</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<p>But the two calls to <code class="language-plaintext highlighter-rouge">join()</code> and <code class="language-plaintext highlighter-rouge">useQuery()</code> are not very IDE friendly. No problem, let’s modify the query class generator again, so as to add support for a faster <code class="language-plaintext highlighter-rouge">use()</code> syntax:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">BookQuery</span> <span class="kd">extends</span> <span class="nc">BaseBookQuery</span>
<span class="p">{</span>
<span class="c1">// this method is generated</span>
<span class="cd">/**
* Use the Publisher relation Publisher object
*
* @return PublisherQuery A secondary query class using the current class as primary query
*/</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">usePublisherQuery</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span>
<span class="o">-></span><span class="nb">join</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="nf">getModelAliasOrName</span><span class="p">()</span> <span class="mf">.</span> <span class="s1">'.Publisher'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">useQuery</span><span class="p">(</span><span class="s1">'Publisher'</span><span class="p">,</span> <span class="s1">'PublisherQuery'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// New Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">usePublisherQuery</span><span class="p">()</span> <span class="c1">// returns a new PublisherQuery instance and makes the join</span>
<span class="o">-></span><span class="nf">filterByName</span><span class="p">(</span><span class="s1">'Penguin'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">endUse</span><span class="p">()</span> <span class="c1">// returns the original BookQuery instance, merged with the PublisherQuery</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<p>This is getting very interesting. Since this method is created at generation time, it’s possible to create <code class="language-plaintext highlighter-rouge">useXXXQuery()</code> for every foreign key, but also for the foreign keys of other tables pointing to <code class="language-plaintext highlighter-rouge">Book</code>.</p>
<h2 id="syntactic-sugar">Syntactic Sugar</h2>
<p>And the subject of conditions on related tables is not closed. Propel still requires that you know foreign key columns to apply a condition on a related object. For instance:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Propel 1.4 way</span>
<span class="c1">// $author is an Author object</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">BookPeer</span><span class="o">::</span><span class="no">AUTHOR_ID</span><span class="p">,</span> <span class="nv">$author</span><span class="o">-></span><span class="nf">getId</span><span class="p">());</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookPeer</span><span class="o">::</span><span class="nf">doSelectOne</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// new Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByAuthorId</span><span class="p">(</span><span class="nv">$author</span><span class="o">-></span><span class="nf">getId</span><span class="p">())</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<p>An ORM is all about thinking “objects” rather than “relational”, and yet this kind of query forces the developer to remember the relation between the columns of two tables. So in the process of adding generated methods to the Query classes, let’s make a tiny but very useful addition:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">BookQuery</span> <span class="kd">extends</span> <span class="nc">BaseBookQuery</span>
<span class="p">{</span>
<span class="c1">// this method is generated</span>
<span class="cd">/**
* Filter the query by a related Author object
*
* @param Author $author the related object to use as filter
* @return BookQuery The current query, for fluid interface
*/</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">filterByAuthor</span><span class="p">(</span><span class="kt">Author</span> <span class="nv">$author</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">filterBy</span><span class="p">(</span><span class="s1">'AuthorId'</span><span class="p">,</span> <span class="nv">$author</span><span class="o">-></span><span class="nf">getId</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByAuthor</span><span class="p">(</span><span class="nv">$author</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<h2 id="wrapping-it-all-together">Wrapping It All Together</h2>
<p>The generated query classes now have much more methods and phpDocumentation, and in fact everything is ready to provide a full IDE completion support. Let’s check on a final example:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// $author is an Author object</span>
<span class="c1">// Find all books by $author, sold for less than 40$, ordered by title, and published at 'Penguin' editions</span>
<span class="c1">// Version A: Propel 1.4 way</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Criteria</span><span class="p">();</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">BookPeer</span><span class="o">::</span><span class="no">AUTHOR_ID</span><span class="p">,</span> <span class="nv">$author</span><span class="o">-></span><span class="nf">getId</span><span class="p">());</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">BookPeer</span><span class="o">::</span><span class="no">PRICE</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="nc">Criteria</span><span class="o">::</span><span class="no">LESS_THAN</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addAscendingOrderByColumn</span><span class="p">(</span><span class="nc">BookPeer</span><span class="o">::</span><span class="no">TITLE</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">addJoin</span><span class="p">(</span><span class="nc">BookPeer</span><span class="o">::</span><span class="no">PUBLISHER_ID</span><span class="p">,</span> <span class="nc">PublisherPeer</span><span class="o">::</span><span class="no">ID</span><span class="p">);</span>
<span class="nv">$c</span><span class="o">-></span><span class="nf">add</span><span class="p">(</span><span class="nc">PublisherPeer</span><span class="o">::</span><span class="no">NAME</span><span class="p">,</span> <span class="s1">'Penguin'</span><span class="p">);</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookPeer</span><span class="o">::</span><span class="nf">doSelectOne</span><span class="p">(</span><span class="nv">$c</span><span class="p">);</span>
<span class="c1">// Version B: Old Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">PropelQuery</span><span class="o">::</span><span class="nf">from</span><span class="p">(</span><span class="s1">'Book'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Book.AuthorId = ?'</span><span class="p">,</span> <span class="nv">$author</span><span class="o">-></span><span class="nf">getId</span><span class="p">())</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Book.Price < ?'</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span>
<span class="o">-></span><span class="nf">orderBy</span><span class="p">(</span><span class="s1">'Book.Title'</span><span class="p">)</span>
<span class="o">-></span><span class="nb">join</span><span class="p">(</span><span class="s1">'Book.Publisher'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'Publisher.Name = ?'</span><span class="p">,</span> <span class="s1">'Penguin'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
<span class="c1">// Version C: New Propel 1.5 way</span>
<span class="nv">$book</span> <span class="o">=</span> <span class="nc">BookQuery</span><span class="o">::</span><span class="nf">create</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByAuthor</span><span class="p">(</span><span class="nv">$author</span><span class="p">)</span>
<span class="o">-></span><span class="nf">filterByPrice</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'min'</span> <span class="o">=></span> <span class="mi">40</span><span class="p">))</span>
<span class="o">-></span><span class="nf">orderByTitle</span><span class="p">()</span>
<span class="o">-></span><span class="nf">usePublisherQuery</span><span class="p">()</span>
<span class="o">-></span><span class="nf">filterByName</span><span class="p">(</span><span class="s1">'Penguin'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">endUse</span><span class="p">()</span>
<span class="o">-></span><span class="nf">findOne</span><span class="p">();</span>
</code></pre></div></div>
<p>Version C is both more concise than versions A and B, and totally compatible with IDE completion. It is still very readable, less error prone than version B, and extremely easy to extend.</p>
<h3 id="the-meaning-of-everything">The Meaning Of Everything</h3>
<p>Incidentally, the new syntax cuts the link with the SQL query. The syntax doesn’t show any <code class="language-plaintext highlighter-rouge">where()</code> or <code class="language-plaintext highlighter-rouge">join()</code> calls. This is because the Propel Query API is now truly in the object oriented world.</p>
<p>Also, this new Propel Query syntax uses code generation the same way the Propel Models do. Code generation provides intuitive and IDE friendly methods. And these generated methods are very fast, because they don’t rely on string parsing or model introspection. Version C is faster to execute than version B.</p>
<p>Speed and code completion are the marks of Propel. No other ORM offer such good development tools that are also very performant at runtime. That’s the sign that this new syntax is the ultimate query syntax for Propel.</p>
<p>One last thing: if version C will be the default syntax for queries in Propel 1.5, versions A and B will also be supported. That means no backwards compatibility problem, and no switching problems for Doctrine users who might want to try the mighty Propel Query API.</p>
Propel Gets Collections2010-01-04T00:00:00+00:00http://www.propelorm.org/blog/2010/01/04/propel-gets-collections<p>Propel 1.5 keeps bringing new features for a better developer experience and improved performance. Today, let's see the latest addition in the Propel runtime package: the PropelCollection objects.<br /><strong><br />From Arrays to Collections</strong><p /> In Propel 1.4, the results of a `doSelect()` call used to be an array of model objects. That made iteration on the results of a query very straighforward:<br /> <br />[code]
<?php
// doSelect() returns an array
$books = BookPeer::doSelect(new Criteria()); // $books is an array of Book objects
?>
There are <?php echo count($books) ?> books:
<ul>
<?php foreach ($books as $book): ?>
<li>
<?php echo $book->getTitle() ?>
</li>
<?php endforeach; ?>
</ul>
[/code]<p />Propel 1.5 introduces a <a href="http://propel.posterous.com/propels-criteria-gets-smarter">new way to make queries on your model object</a>. It's the occasion to improve the way Propel returns the results of a query. So starting with Propel 1.5, model queries return a <strong>collection object</strong> instead of an array. <p /> First, let's see what doesn't change. You can iterate over a collection object just like you do with an array:<p />[code]
<?php
// find() returns a PropelCollection, which you can use just like an array
$books = PropelQuery::from('Book')->find(); // $books is a PropelObjectCollection of Book objects
?>
There are <?php echo count($books) ?> books:
<ul>
<?php foreach ($books as $book): ?>
<li>
<?php echo $book->getTitle() ?>
</li>
<?php endforeach; ?>
</ul>
[/code]<p />As you can see, no modification to the template code was required. `foreach()`, `count()`, `append()`, and even `unset()` can be executed on a PropelCollection object as you usually do on an array. This is because the `PropelCollection` class extends `<a href="http://www.php.net/manual/en/class.arrayobject.php">ArrayObject</a>`, one of the new <a href="http://www.php.net/manual/en/book.spl.php">SPL classes</a> introduced by PHP 5. <p /> <strong>Tip</strong>: The generated `doSelect()` methods in your Peer classes keep on returning arrays in Propel 1.5. It's only if you use the new Query API that you get Collections in return. That makes this new feature completely backwards compatible with existing Propel code.<p /> <strong>PropelCollection Abilities</strong><p />A PropelCollection is more than just an array. First of all, you can call some special methods on it. Check the following example:<p />[code]
<?php if($books->isEmpty()): ?>
There are no books.
<?php else: ?>
There are <?php echo $books->count() ?> books:
<ul>
<?php foreach ($books as $book): ?>
<li class="<?php echo $books->isOdd() ? 'odd' : 'even' ?>">
<?php echo $book->getTitle() ?>
</li>
<?php if($books->isLast()): ?>
<li>Do you want more books?</li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
<?php endif; ?>
[/code]<p />In this example, `isEmpty()`, `count()`, `isOdd()`, and `isLast()` are all methods of the `PropelObjectCollection` instance returned by `find()`. But there is more. The collection object offers methods allowing to alter the objects it contains:<p /> [code]
<?php
foreach ($books as $book) {
$book->setIsPublished(true);
}
$books->save();
?>
[/code]<p />Notice how the `save()` method is not called on each object, but on the collection object. This groups all the `UPDATE` queries into a single database transaction, which is faster than individual saves. A PropelCollection also allows you to delete all the objects in the collection in a single call with `delete()`, or to retrieve the primary keys with `getPrimaryKeys()`.<p /> Lastly, a `PropelCollection` can be exported to an array of arrays, so that you can easily inspect the results of a query. It is as simple as calling `toArray()` on a collection object:<p />[code]
<?php
$books = PropelQuery::from('Book')
->with('Book.Author')
->with('Book.Publisher')
->find();
print_r($books->toArray());
/* => array(
array(
'Id' => 123,
'Title' => 'War And Peace',
'ISBN' => '3245234535',
'AuthorId' => 456,
'PublisherId' => 567
'Author' => array(
'Id' => 456,
'FirstName' => 'Leo',
'LastName' => 'Tolstoi'
),
'Publisher' => array(
'Id' => 567,
'Name' => 'Penguin'
)
),
array(
'Id' => 535,
'Title' => 'Pride And Prejudice',
'ISBN' => '5665764586',
'AuthorId' => 853,
'PublisherId' => 567
'Author' => array(
'Id' => 853,
'FirstName' => 'Jane',
'LastName' => 'Austen'
),
'Publisher' => array(
'Id' => 567,
'Name' => 'Penguin'
)
),
) */
[/code]<p /><strong>Using An Alternative Collection</strong><p />If what you need is actually an array of arrays, you'd better skip the collection of objects completely, and use a colleciton of arrays instead. This is easily done by specifying an alternative formatter when building the query, as follows:<p /> [code]
<?php
$books = PropelQuery::from('Book')
->with('Book.Author')
->with('Book.Publisher')
->setFormatter(ModelCriteria::FORMAT_ARRAY)
->find();
[/code]<p />Now, the result of the query is not a `PropelObjectCollection` anymore, but a `PropelArrayCollection`. The elements in the collection are associative arrays, where the keys are the column names:<p /> [code]
<?php
foreach ($books as $book) {
echo $book['Title'];
}
[/code]<p />And if you think that using a Collection object rather than a simple array is a bad idea regarding performance and memory consumption, try the new `PropelOnDemandCollection`. It behaves just like the `PropelObjectCollection`, except that the model objects are hydrated row-by-row and then cleaned up so that the query uses the same memory for 5 results as for 50,000:<p /> [code]
<?php
$books = PropelQuery::from('Book')
->setFormatter(ModelCriteria::FORMAT_ON_DEMAND)
->limit(50000)
->find();
// You won't get a Fatal error for not enough memory with the following code
foreach($books as $book) {
echo $book->getTitle();
}
[/code]<p />For those who want to deal with `PDOStatement` instances themselves, the `ModelCriteria::FORMAT_STATEMENT` formatter is at your disposal.<p /> <strong>Going Further</strong><p />The Formatter/Collection system in the new Propel Query architecture is very extensible, so it's very easy to write a new formatter and collection objects to package your own custom hydration logic.<p /> Of course, as usual with Propel 1.5, this feature is fully unit tested and <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/ModelCriteria#PropelCollectionMethods">already documented</a>. So you can start using it right now in the 1.5 branch.</p>
Would You Like To Play With Propel 1.5?2009-12-17T00:00:00+00:00http://www.propelorm.org/blog/2009/12/17/would-you-like-to-play-with-propel-1-5-<p>The release date of Propel 1.5 is <a href="http://propel.phpdb.org/trac/milestone/1.5">a couple months ahead</a>, but since this new version offers only backwards compatible enhancements, you can play with it in your existing applications, or start developing new applications with it, right now. <p /> The 1.5 branch is very stable, and passes all the 928 unit tests of Propel 1.4 - plus 891 new tests added just for the 1.5 additional features. That's right, Propel 1.5 has nearly doubled the total number of unit tests from Propel 1.4 ; and if you remember correctly, Propel 1.4 already had twice as many unit tests as Propel 1.3.<p /> There are three killer features in Propel 1.5 that you may want to use right away:<p /> - <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/ModelCriteria">New Query API</a><br /> - <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/Behaviors/nested_set">Nested set behavior</a><br /> - <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/Behaviors/sortable">Sortable behavior</a><p />Propel 1.5 doesn't offer a PEAR package for now, but a Subversion checkout will do the trick:<p /> > svn checkout <a href="http://svn.phpdb.org/propel/branches/1.5">http://svn.phpdb.org/propel/branches/1.5</a> <p />Alternatively, symfony users can benefit from the brand new <a href="http://www.symfony-project.org/plugins/sfPropel15Plugin">sfPropel15Plugin</a>, which is a drop-in replacement for the core sfPropelPlugin in applications using symfony 1.3 or 1.4.<p /> Feedback is welcome on the stability and usability of the new features. As always, the <a href="http://propel.phpdb.org/trac/">Propel Trac ticketing</a> is your best friend to mention bugs or request features.</p>
Save The Open-Source, Save The World2009-12-13T00:00:00+00:00http://www.propelorm.org/blog/2009/12/13/save-the-open-source-save-the-world<p>MySQL is in danger. It's Monthy, the very creator of MySQL, and currently lead developer of MariaDB, who says it:<p /><a href="http://monty-says.blogspot.com/2009/12/help-saving-mysql.html">http://monty-says.blogspot.com/2009/12/help-saving-mysql.html</a><p /> Propel wouldn't be what it is today without MySQL. The same goes for a lot of open-source projects out there. With MySQL in jeopardy, the whole web development activity could suffer serious damage.<p />If MySQL and the idea of Open-Source matter to you, I suggest you that relay this information in your blog / twitter / company. You an also write to the EC commission, as I did myself.</p>
Propel's Criteria Gets Smarter2009-12-10T00:00:00+00:00http://www.propelorm.org/blog/2009/12/10/propel-s-criteria-gets-smarter<p>I've always been frustrated with Propel's Criteria. Despite its powerful Object-Oriented API for querying a database, I find it quite dumb and very hard to learn. It used to be one of Propel's main drawbacks. But starting with Propel 1.5, you can count on the Criteria to be much smarter.<p /> <strong>Criteria's Shortcomings<br /></strong> <br />Let me be more specific. The Criteria object doesn't know about your model schema, and forces you to repeat both the members of a relation each time you write a join:<p />[code]
$c = new Criteria();
$c->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID);
[/code]<p />With a Criteria, you can't write queries that make two joins on the same table. You can't easily write a custom SQL clause that will use proper binding.<br /> <!--more--><br />Criteria also follows a syntax that you cannot guess, so you need the Criteria API documentation open at all times for months until you can figure out that kind of snippet:<p />[code]
$c = new Criteria();
$c1 = $c->getNewCriterion(BookPeer::TITLE, '%Leo%', Criteria::LIKE);
$c2 = $c->getNewCriterion(BookPeer::ISBN, '1234', Criteria::EQUAL);
$c1->addOr($c2);
$c->add($c1);
$c->addAscendingOrderByColumn(BookPeer::CREATED_AT);
[/code]<p />After giving several trainings for Propel, I keep on seeing developers struggle to write a simple query. Propel's Criteria is definitely one of its less intuitive features, and it's not the most powerful query API in the world either.<p /> <strong>Criteria's Power</strong><p />I already tried to address these shortcomings a year and a half ago, through a plugin for the Symfony framework called <a href="http://www.symfony-project.org/plugins/DbFinderPlugin">DbFinder</a>. With DbFinder, it became a pleasure to develop model code based on Propel, and to teach how to use it.<p /> DbFinder relied heavily on Criteria, but added a lot of custom code. Some of this code was required to do runtime introspection of the model struture. Propel 1.4 recently introduced the RelationMap classes, and the ability for a model to know all its relations at runtime. This was the first brick for a native implementation of DbFinder in Propel.<p /> Building DbFinder abilities right into Propel Criteria was the next step, but the Propel implementation had to differ from DbFinder's one. There is one thing that I have understood in the past months, and it's the biggest strength of Propel: the fact that Propel can generate objects at buildtime is what makes it faster than all its competitors. So I used Propel's generator in conjunction with DbFinder's power to create the ModelCriteria class and the PropelQuery factory.<p /> By initializing a Criteria object with a Model name, it is now possible to write queries in the following way:<p />[code]
$books = PropelQuery::from('Book b')
->join('b.Author a')
->where('a.FirstName = ?', 'Leo')
->orderBy('b.Title')
->limit(10)
->find();
[/code]<p />Behind this `PropelQuery`, it's the same old Criteria at work. That means that this new syntax is entirely backwards compatible. You can see it as a usability layer on top of Criteria, that doesn't break any existing application. The new syntax is already committed to the Propel 1.5 branch, fully unit tested, and documented in a <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.5/ModelCriteria">brand new documentation chapter</a>.<p /> <strong>There Is One Way To Do It<br /></strong><br />You probably understood that this new syntax can completely replace the use of Peer classes in Propel. But for the time being, that means that there are two ways to write and execute a query with Propel. The old way, using Criteria and Peer classes, and the new way, using PropelQuery. <p /> And it's a good usability guideline not to leave too much choice to avoid that users get lost.<p />The old way has to be there for backwards compatibility. It will remain the in the Propel core for as long as the Propel 1.x branch lives.<p /> The new way will become the default way some time in the future - maybe in time for the 1.5 release. That will mean a large rewrite of the current documentation and behaviors, but it's not such a big task. More specifically, the main documentation will omit the Criteria and Peer way. But rest assured, the cookbook will still provide a chapter to learn how to run them.<p /> <strong>Propel Is Not Becoming Doctrine</strong><p />I know what most Propel old timers will think of this addition: this is Propel becoming Doctrine and giving away its soul to the new ORM golden boy. Well, in a way, I agree with that. This new feature takes some of the recipes that proved successful in Doctrine, and that Doctrine took from other projects beforehand. This is called Open-Source cross-pollinisation. Reusing the best ideas found elsewhere is not losing one's soul. What makes Propel's soul is its ability to query a database through an object-oriented API. This is still there, and better than ever.<p /> Now the new Propel Query API has some significant differences with Doctrine's Query API. First of all, it is faster. Propel still relies on generated code to execute queries in roughly half the time Doctrine needs. Propel's queries don't need a Parser/Lexer implementation, because they only accept simple clauses, described in a standard way. By combining these simple clauses, you can write very complex queries. Also, you can extend Propel Query objects to write Named Queries, which are objects embedding a query for later reuse. This will be the true revolution in your development habits. Your model code will be more readable, more maintainable, and more robust than ever.<p /> <strong>Propel's Future<br /></strong><br />One last word: This new feature is not particularily aimed at current Propel users. You guys already master the Criteria, you have Criteria all over your application code, and you will be able to use its seamlessly until the 1.x branch dies. If you want to ignore the new Propel Query features, then go on as before and don't worry about it - it won't interfere with your code.<p /> But newcomers who have to choose an ORM nowadays have basically two choices, and I don't want them to ignore Propel for the wrong reasons. The poor usability of Propel's Criteria used to be a reason for not choosing Propel. It should no longer be the case.<p /> So as of now, Propel is a very usable ORM. Make sure you spread the word.</p>
Symfony 1.3 and 1.4 released with Propel support2009-12-02T00:00:00+00:00http://www.propelorm.org/blog/2009/12/02/symfony-1-3-and-1-4-released-with-propel-support<p>The symfony framework just announced a major step in its history: the <a href="http://www.symfony-project.org/blog/2009/12/01/symfony-1-3-and-1-4-stable-released">dual release of the 1.3 and 1.4 versions</a>. Both these versions embed Propel 1.4 so you can start building web applications faster with your favorite ORM.<p /> Symfony is, simply said, the best PHP framework out there. And I don't write that because I'm a former member of the symfony team - the competitors can't compare in terms of robustness, design, features, coherence, ease of use, and integration into a professional workflow of web development. The work on the 1.3 and 1.4 versions of the framework is astounding, making symfony better in every possible way. Congratulations to the symfony core team and to all the contributors of the framework for this great job. It's a great day for symfony.<p /> It's also a great day for Propel, for the developers and the users community. Propel will get more exposure thanks to symfony, and the new users and volunteers will keep on enhancing it more and more every day.</p>
Propel 1.4 will have minor bugfix releases2009-11-30T00:00:00+00:00http://www.propelorm.org/blog/2009/11/30/propel-1-4-will-have-minor-bugfix-releases<p>The Propel 1.3 branch never had minor releases with only bugfixes - mostly due to the tight schedule required for a 1.4 release. But that should not be the case of the 1.4 branch. Even if the development of the next major release (the 1.5) has already started, users of Propel 1.4 should not have to wait for the 1.5 release to benefit from bug fixes, nor should they have to upgrade to this new release, which adds a lot of enhancements.<p /> That's why Propel 1.4 will have minor bugfix releases every two month. The 1.4.1 milestone is already scheduled for mid-January, and includes the first bugfixes committed to the 1.5 branch. <p />From now on, every bugfix will be committed to both the 1.4 and 1.5 branch, and enhancements will be committed to the 1.5 branch only. Of course, the changes in the Propel 1.x branch will always be backwards compatible, so an application built with Propel 1.3 or 1.4 will work perfectly with 1.5 and above.<p /> That also means that the initial release of the 1.5 milestone will be postponed a little. It doesn't make sense to offer major releases and minor releases every two or three months, because that would mean committing bugfixes to up to four branches in the future. So Propel 1.5 is now scheduled for late February, and that should give the core team plenty of time to pack up a large number of very interesting features.<p /> If you want to have a view at the Propel roadmap at all times, head to the <a href="http://propel.phpdb.org/trac/roadmap">Roadmap page</a> on the Propel Trac. This page shows the progression of every milestone, together with release date and statistics about the tickets included in a milsestone.</p>
Propel 1.4 Already Integrated Into Symfony2009-11-16T00:00:00+00:00http://www.propelorm.org/blog/2009/11/16/propel-1-4-already-integrated-into-symfony<p>The latest release of Propel is <a href="http://propel.posterous.com/propel-140-stable-is-there">only a few days old</a>, but the symfony framework <a href="http://www.symfony-project.org/blog/2009/11/08/using-propel-1-4-detailed-logging">has already integrated it</a>. Symfony 1.3, which is currently in beta, features a tight integration of the new Propel 1.4 features (including buildtime behaviors, full query logging, and better runtime introspection) with the core of the framework.<p /> The sfPropelPlugin, which provides ORM functionality to the symfony framework, has been partly rewritten to use the new buildtime behaviors instead of custom builders. If you want to write behaviors for Propel 1.4 yourself, the sfPropelPlugin code is probably a good example of how you can work it out. It makes the features added by symfony to the ORM more robust and evolutive.<p /> The symfony roadmap <a href="http://www.symfony-project.org/blog/2009/11/10/symfony-1-3-beta-2">schedules the stable release of symfony 1.3</a> for the end of November. Whether you start a new project or upgrade an existing application using symfony, Propel 1.4 can already be your ORM of choice.<p /> Thanks to Kris Wallsmith and Fabian Lange, of the symfony core team, for their fast and effective work on integrating Propel with symfony!</p>
Propel 1.4.0 Stable Is There2009-11-08T00:00:00+00:00http://www.propelorm.org/blog/2009/11/08/propel-1-4-0-stable-is-there<p>Propel 1.4.0 was just released. It's gorgeous, as fast as the previous release (some say faster), and it's full of new features, completely backwards-compatible with Propel 1.3. If you use Propel 1.3 on your projects, you don't have any reason not to upgrade - you would just miss so much!</p>
<p>For details about the Propel 1.4 enhancements, check the <a href="http://propel.phpdb.org/trac/wiki/Users/Documentation/1.4/WhatsNew">What's new in Propel 1.4 page</a>. From behaviors to Joins with multiple conditions, and including a shiny runtime introspection engine, you wouldn't imagine how much we packed in this release in just two months. That's right, Propel 1.4 was started in early September 2009, and it's already out as a stable release. You will notice that the documentation was also reworked and partly rewritten, making it even easier to use Propel every day.</p>
<p>To install this release, you can checkout or update your externals to the 1.4.0 tag in the Subversion repository:</p>
<div class="CodeRay">
<div class="code"><pre>> svn checkout http://svn.phpdb.org/propel/tags/1.4.0 propel_1.4</pre></div>
</div>
<p>Alternatively, you can install the generator and runtime as PEAR packages:</p>
<div class="CodeRay">
<div class="code"><pre>> pear install phpdb/propel_generator-1.4.0 </pre></div>
</div>
<div class="CodeRay">
<div class="code"><pre>> pear install phpdb/propel_runtime-1.4.0</pre></div>
</div>
<p>Then rebuild your model, and that's it - no upgrade of the existing code is necessary.</p>
<p>Now that Propel 1.4.0 is out, we need you to make some noise about it. If you use Propel, tell everybody how fast it is and how active the development is. And if you like the new features, please use them in your tutorials and open-source projects to showcase the power of the Propel ORM.</p>
<p>The next backwards-compatible release, labelled 1.5, is planned for early 2010 and already features a new behavior. Keep an eye open on this blog for upcoming news about the 1.5 enhancements. </p>
<p> </p>
Welcome to the Propel Blog2009-11-07T00:00:00+00:00http://www.propelorm.org/blog/2009/11/07/welcome-to-the-propel-blog<p>Propel has a blog. At last, some may say. </p>
<p />
<div>You'll find here posts related to <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">ORMs</a> in general, and to <a href="http://propel.phpdb.org/trac/">Propel</a> in particular. Don't hesitate to subscribe to the blog <a href="http://propel.posterous.com/rss.xml">RSS feed</a>, we'll not spam you. And don't hesitate to comment, we'll listen to you.</div>
<p />
<div>Note that Propel used to have some sort of blog in the past - only it was in the <a href="http://propel.phpdb.org/trac/wiki/Users/News">Propel Trac</a>. You will still find there all the old posts related to Propel 1.3 and previous versions.</div>
<p />
<div>See you soon on the Propel Blog!</div>