= What's new in Propel 1.4? =

Propel 1.4 is a backwards compatible evolution of Propel 1.3. It offers lots of bugfixes, some very interesting new features, speed enhancements, and a very simple upgrade path: rebuild your model after updating Propel, and your application works as always. Except it works better...

== Pre and Post Hooks For `save()` And `delete()` Methods == 

The `save()` and `delete()` methods of your generated objects are now easier to override. In fact, Propel looks for one of the following methods in your objects and executes them when needed:

{{{
#!php
<?php
preInsert()            // code executed before insertion of a new object
postInsert()           // code executed after insertion of a new object
preUpdate()            // code executed before update of an existing object
postUpdate()           // code executed after update of an existing object
preSave()              // code executed before saving an object (new or existing)
postSave()             // code executed after saving an object (new or existing)
preDelete()            // code executed before deleting an object
postDelete()           // code executed after deleting an object
}}}

So for instance, you can force the update of a `created_at` column in a `book` table before every insertion as follows:

{{{
#!php
<?php
class Book extends BaseBook
{
  public function preInsert(PropelPDO $con = null)
  {
    $this->setCreatedAt(time());
  }
}
}}}

Since this new feature adds a small overhead to write operations, you can deactivate it in your build properties by setting `propel.addHooks` to `false`.

{{{
#!ini
# -------------------
#  TEMPLATE VARIABLES
# -------------------
propel.addHooks = false
}}}

== Behaviors ==

You can now package and reuse behaviors across your models. For instance, keeping the date of creation of an object and the date of latest update is as simple as declaring the `timestampable` behavior in the table declaration:

{{{
#!xml
<table name="book">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
  <column name="title" type="VARCHAR" required="true" />
  <behavior name="timestampable" />
</table>
}}}

Rebuild your model, and you now have two new columns in the `book` table automatically set to the creation and update dates:

{{{
#!php
<?php
$b = new Book();
$b->setTitle('War And Peace');
$b->save();
echo $b->getCreatedAt(); // 2009-10-02 18:14:23
echo $b->getUpdatedAt(); // 2009-10-02 18:14:23
$b->setTitle('Anna Karenina');
$b->save();
echo $b->getCreatedAt(); // 2009-10-02 18:14:23
echo $b->getUpdatedAt(); // 2009-10-02 18:14:25
}}}

Propel behaviors occur at build time, so they have absolutely no overhead. They can modify the database schema, alter the methods generated by Propel, and add methods of their own in both the model and Peer classes. As a matter of fact, this new feature is so powerful that it requires a documentation on its own. Read it in the [wiki:Users/Documentation/1.4#HowTos HowTos] section.

Propel 1.4 already bundles a few behaviors, like `timestampable` and `soft_delete`. Check the documentation linked above for more details.

== Joins with multiple conditions ==

You can now create joins with any number of conditions and any comparator using the new `Criteria::addMultipleJoin()` method.

{{{
#!php
<?php
$c = new Criteria();
$c->addMultipleJoin(array(
    array(ReaderFavoritePeer::BOOK_ID, BookOpinionPeer::BOOK_ID),
    array(ReaderFavoritePeer::READER_ID, BookOpinionPeer::READER_ID))
  Criteria::INNER_JOIN);
}}}
{{{
#!sql
// SQL result
SELECT ...
FROM reader_favorite 
INNER JOIN book_opinion ON (reader_favorite.BOOK_ID = book_opinion.BOOK_ID 
                        AND reader_favorite.READER_ID = book_opinion.READER_ID)
}}}

You can add a third operator to each join condition to allow complex joins:

{{{
#!php
<?php
$c = new Criteria();
$c->addMultipleJoin(array(
    array(Book::USER_ID, UserPeer::ID))
    array(UserPeer::RANK, 12, Criteria::GREATER_THAN),
  Criteria::LEFT_JOIN);
}}}
{{{
#!sql
// SQL result
SELECT ...
FROM book
LEFT JOIN user ON (book.USER_ID = user.ID AND user.RANK > 12)
}}}

Note that the former way to define a composite join using arrays as arguments of `addJoin()` is deprecated.

== Full Query Logging ==

Propel can now log all the queries issued to the database in their exact syntax, including parameter binding. That means that you can easily copy a query from the Propel logs and execute it in your database to see the result immediately.

In addition, Propel can log the time and memory spent on every query, or only on slow query, depending on a treshold that you can customize.

Last but not least, you can check the number of executer queries and the SQL code of the latest executed query at runtime.

Propel 1.2 users may not find these features spectacular, but by switching from Creole to PDO, Propel lost its powerful logging abilities in version 1.3. Propel 1.4 logging is now on par with Propel 1.2 logging again.

All this is possible thanks to a new connection class, called `DebugPDO`. To enable it, just change the connection `<classname>` to 'DebugPDO' in the `runtime-conf.xml` file, as follows:

{{{
#!xml
<?xml version="1.0"?>
<config>
  <propel>
    <datasources default="bookstore">
      <datasource id="bookstore">
        <adapter>sqlite</adapter>
        <connection>
          <classname>DebugPDO</classname>
}}}

That's all. After this change, Propel logs all the queries executed at runtime in its default log file:

{{{
time:  0.002 sec | mem: 1.4 MB | INSERT INTO publisher (`ID`,`NAME`) VALUES (NULL,'William Morrow')
time:  0.000 sec | mem: 1.4 MB | INSERT INTO author (`ID`,`FIRST_NAME`,`LAST_NAME`) VALUES (NULL,'J.K.','Rowling')
time:  0.000 sec | mem: 1.6 MB | INSERT INTO book (`ID`,`TITLE`,`ISBN`,`PRICE`,`PUBLISHER_ID`,`AUTHOR_ID`) VALUES (NULL,'Harry Potter and the Order of the Phoenix','043935806X',10.99,53,58)
time:  0.000 sec | mem: 1.7 MB | INSERT INTO review (`ID`,`REVIEWED_BY`,`REVIEW_DATE`,`RECOMMENDED`,`BOOK_ID`) VALUES (NULL,'Washington Post','2009-10-04',1,52)
...
time:  0.001 sec | mem: 2.1 MB | SELECT bookstore_employee_account.EMPLOYEE_ID, bookstore_employee_account.LOGIN FROM `bookstore_employee_account` WHERE bookstore_employee_account.EMPLOYEE_ID=25
}}}

Check the [wiki:Users/Documentation/1.4/Full-Query-Logging Full Query Logging Documentation] for details on the `DebugPDO` documentation.

== Generated `__toString()` in Base Object ==

Propel can generate a `__toString()` method for your Model objects if you define a column as `primaryString` in the schema:

{{{
#!xml
<table name="book" phpName="Book">
  ..
  <column name="title" type="varchar" size="125" primaryString="true" />
  ..
</table>
}}}   

After a model build, the generated `BaseBook` class offers the following magic method:

{{{
#!php
<?php
public function __toString()
{
  return (string) $this->getTitle();
}
}}}

That means that the default string representation of `Book` objects is the value of the `title` column:

{{{
#!php
<?php
$book = new book();
$book->setTitle('War And Peace');
echo $book; // 'War And Peace'
}}}

== New Peer constant: OM_CLASS ==

Did you ever try to get the Propel Model class related to a Peer class? If you did, you probably noticed that the only available constant providing this information also contained package information:

{{{
#!php
<?php
// in BaseBookPeer.php
/** A class that can be returned by this peer. */
const CLASS_DEFAULT = 'bookstore.Book';
}}}

This can be useful if you don't use autoloading; but if you do, retrieving the actual Propel model class name involved some string operations to remove the package information. This is no more necessary in Propel 1.4, since the Peer classes offer a new constant:

{{{
#!php
<?php
// in BaseBookPeer.php
/** the related Propel class for this table */
const OM_CLASS = 'Book';
}}}

The generated Peer methods now use this constant and avoid string operations. Therefore, they should be slightly faster.

== PropelPager Implements Countable and Iterator Interfaces ==

Did you know Propel offered a pagination utility class? It just got better, by now supporting the `Countable` and `Iterator` interfaces. That means that you can manipulate a `PropelPager` object as if it was an array:

{{{
#!php
<?php
$c = new Criteria();
$c->add(BookPeer::AUTHOR, $authorId);
$pager = new PropelPager($c, 'BookPeer', 'doSelect', $page = 1, $rowsPerPage = 20);

if(count($pager)) // if the current page has results
{
  foreach($pager as $book) // get pager results and iterate on them
  {
    echo $book->getTitle();
  }
}
}}}

Note that the `PropelPager` utility has a brand new documentation in the [wiki:Users/Documentation/1.4#HowTos HowTos] section. 

== Better Introspection at Runtime ==

A few methods were added to the Map classes to ease runtime introspection, as well as a new `RelationMap` class to describe database relationships:
{{{
TableMap    DatabaseMap::getTableByPhpName($name) // TableMap object by object model name, e.g. 'Book'
ColumnMap   DatabaseMap::getColumn($name) // ColumnMap object by fully qualified name, e.g. book.AUTHOR_ID
Array       TableMap::getPrimaryKeys()    // List of the ColumnMap objects corresponding to the table primary keys
Array       TableMap::getForeignKeys()    // List of the ColumnMap objects corresponding to the table foreign keys
Array       TableMap::getRelations()      // List of the table relationships, as RelationMap objects
String      TableMap::getPackage()        // Package of the table
mixed       ColumnMap::getDefaultValue()  // Default value defined in the schema for this column
TableMap    ColumnMap::getRelatedTable()  // Related TableMap object by foreign key
ColumnMap   ColumnMap::getRelatedColumn() // Related ColumnMap object by foreign key
RelationMap ColumnMap::getRelation()      // Related RelationMap object   
integer     RelationMap::getType()        // RelationMap::ONE_TO_MANY, RelationMap::MANY_TO_ONE, or RelationMap::ONE_TO_ONE
string      RelationMap::getOnDelete()    // ON DELETE directive, e.g. 'SET NULL'
TableMap    RelationMap::getLocalTable()  // Local TableMap object (the one bearing the fkey)
TabkeMap    RelationMap::getForeignTable()   // Foreign TableMap object
Array       RelationMap::getColumnMappings() // List of local => foreign column for this relation, e.g array('book.PUBLISHER_ID' => 'publisher.ID')
Array       RelationMap::getLocalColumns()   // List of the ColumnMap objects on the local side of the relation
Array       RelationMap::getForeignColumns() // List of the ColumnMap objects on the foreign side of the relation
}}}

Check the new [wiki:Users/Documentation/1.4/Runtime-Introspection Runtime Introspection] documentation in the [wiki:Users/Documentation/1.4#HowTos HowTos] section for a complete description of the introspection API.

Also, it is now much easier to get the name of the Propel object class from the Peer class:
{{{
echo BookPeer::getOMClass()                    => 'bookstore.Book'
echo BookPeer::getOMClass($withPrefix = false) => 'Book'
}}}

== MapBuilders are gone ==

Runtime introspection used to rely on runtime builder classes for TableMap objects, called MapBuilders. These classes are not generated anymore by Propel. Instead, Propel generates TableMap classes that are easier to deal with. 

The standard way to get a TableMap through the Peer classes works just the same as before:

{{{
#!php
<?php
$bookTableMap = BookPeer::getTableMap();
}}}

But building TableMaps by hand is a lot easier:

{{{
#!php
<?php
// Propel 1.3 way to initialize a TableMap
$className = 'Book';
$mapBuilderClass = $className . 'MapBuilder';
$mapBuilder = new $mapBuilderClass();
if (!$mapBuilder->isBuilt())
{
  $mapBuilder->doBuild();
}
$bookTableMap = $databaseMap->getTable('book');

// Propel 1.4 way to initialize a TableMap
$className = 'Book';
$tableMapClass = $className . 'TableMap';
$bookTableMap = $databaseMap->addTableFromMapClass($tableMapClass);
}}}

Note that if you want to make sure that all the tables related to a given table have their TableMaps loaded, you just need to ask for the table relations:

{{{
#!php
<?php
// build all the TableMap objects of tables related to Book
$relations = $bookTableMap->getRelations();
}}}

== New Build Options ==

You now have more control over the generated classes, through three new settings in `build.properties`:

{{{
propel.addValidateMethod = {true}|false
}}}
Whether to add `validate()` method to your classes. Set to false if you don't use Propel validation. 

{{{
propel.addIncludes = {true}|false
}}}
Whether to add `require` statements on the generated stub classes. Set to false if you autoload every class at runtime.

{{{
propel.addHooks = {true}|false
}}}
Whether to support pre- and post- hooks on `save()` and `delete()` methods. Set to false if you never use these hooks for a small speed boost.

== Foreign Key Retrievers Now Use Instance Pool ==

When you retrieve an object related to the current model object by a foreign key, you usually use a method generated by Propel, as follows:

{{{
#!php
<?php
$author = $book->getAuthor(); // $author is an Author instance
}}}

Behind the curtain, this method uses the `author_id` property of the current book object, makes a query to the database for the related `author` record, and hydrates an `Author` object with the result. But what if you already have hydrated this author object earlier in the script? Thanks to instance pooling, Propel remembers the objects it has in memory, which can save a lot of database queries. For instance:

{{{
#!php
<?php
foreach($author->getBooks() as $book)
{
	echo $book->getTitle();
  echo $book->getAuthor()->getName();
}
}}}

In Propel 1.3, this code would issue n+1 queries to the database (n being the number of books written by $author). But with Propel 1.4, this code only executes one query. As the `getAuthor()` method is called, Propel knows that the author of each book is already in memory, and therefore skips the database query and the hydration process altogether. hte result is a slight speed boost, and a decrease in the number of queries executed per page.

== `BasePeer::populateStmtValues()` is now public ==

You can now use a SQL query of your own, or modify the one returned by `BasePeer::createSelectSql()`, and use the binding capabilities of Propel to populate a query with a set of values. Propel uses the PDO type of the column to do the binding, so this method may save you some hassle: 

{{{
#!php
<?php
$sql = BasePeer::createSelectSql($criteria, $params);
$sql = "INSERT INTO temp_table_name $sql";
$stmt = $con->prepare($sql);
BasePeer::populateStmtValues($stmt, $params, $dbMap, $db);
$stmt->execute();
}}}

== Documentation Reorganization ==

The Propel documentation now lies in the Subversion repository. It becomes easier to contribute and trace documentation changes, attach corrections to tickets, and bundle the Propel library as a self-containing package.

Of course, the documentation has been updated to reflect all the changes described above, and all the new features are fully documented.

== Twice As Many Unit Tests ==

The Propel 1.4 development process introduced Test-Driven Development. As a consequence, the number of unit tests in the Propel codebase has dramatically increased. In fact, it has more than doubled since Propel 1.3. Propel is still far from a large unit test code coverage, but the stability and robustness of the Propel library get better every day.
