What's new in Propel 1.5?
First and foremost, don't be frightened by the long list of new features that follows. Propel 1.5 is completely backwards compatible with Propel 1.4 and 1.3, so there is no hidden cost to benefit from these features. If you didn't do it already, upgrade the propel libraries, rebuild your model, and you're done - your application can now use the Propel 1.5 features.
New Query API
This is the killer feature of Propel 1.5. It will transform the painful task of writing Criteria queries into a fun moment.
Model Queries
Along Model and Peer classes, Propel 1.5 now generates one Query class for each table. These Query classes inherit from Criteria, but have additional abilities since the Propel generator has a deep knowledge of your schema. That means that Propel 1.5 advises that you use ModelQueries instead of raw Criteria.
Model queries have smart filter methods for each column, and termination methods on their own. That means that instead of writing:
<?php $c = new Criteria(); $c->add(BookPeer::TITLE, 'War And Peace'); $book = BookPeer::doSelectOne($c);
You can write:
<?php $q = new BookQuery(); $q->filterByTitle('War And Peace'); $book = $q->findOne();
In addition, each Model Query class benefits from a factory method called create(), which returns a new instance of the query class. And the filter methods return the current query object. So it's even easier to write the previous query as follows:
<?php $book = BookQuery::create() ->filterByTitle('War And Peace'); ->findOne();
The termination methods are find(), findOne(), count(), paginate(), update(), and delete(). They all accept a connection object as last parameter.
Remember that a Model Query IS a Criteria. So your Propel 1.4 code snippets still work:
<?php $book = BookQuery::create() ->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID); ->add(AuthorPeer::LAST_NAME, 'Tolstoi') ->addAscendingOrderByColumn(BookPeer::TITLE) ->findOne();
But you will soon see that it's faster to use the generated methods of the Model Query classes:
<?php $book = BookQuery::create() ->useAuthorQuery(); ->filterByLastName('Tolstoi') ->endUse() ->orderByTitle() ->findOne();
That's right, you can embed a query into another; Propel guesses the join to apply from the foreign key declared in your schema.
That makes it very easy to package your own custom model logic into reusable query methods. After a while, your code can easily look like the following:
<?php $books = BookQuery::create() ->filterByPublisher($publisher) ->cheap() ->recent() ->useAuthorQuery(); ->stillAlive() ->famous() ->endUse() ->orderByTitle() ->find();
The Model Queries can understand findByXXX() method calls, where 'XXX' is the phpName of a column of the model. That answers one of the most common customization need:
<?php $book = BookQuery::create()->findOneByTitle('War And Peace');
Eventually, these Query classes will replace the Peer classes; you should place all the code necessary to request or alter Model object in these classes. The Criteria/Peer way of doing queries still work exactly the same as in previous Propel versions, so your existing applications won't suffer from this update.
Tip: Incidentally, if you use an IDE with code completion, you will see that writing a query has never been so easy.
Collections And On-Demand Hydration
The find() method of generated Model Query objects returns a PropelCollection object. You can use this object just like an array of model objects, iterate over it using foreach, access the objects by key, etc.
<?php $books = BookQuery::create() ->limit(5) ->find(); // $books is a PropelCollection object foreach ($books as $book) { echo $book->getTitle(); }
Propel also returns a PropelCollection object instead of an array when you use a getter for a one-to-many relationship:
<?php $books = $author->getBooks(); // $books is a PropelCollection object
If your code relies on list of objects being arrays, you will need to update it a little. The PropelCollection object provides a method for most common array operations:
Array | Collection object ------------------------ | ----------------------------------------- foreach($books as $book) | foreach($books as $book) count($books) | count($books) or $books->count() $books[]= $book | $books[]= $book or $books->append($book) $books[0] | $books[0] or $books->getFirst() $books[123] | $books[123] or $books->offsetGet(123) unset($books[1]) | unset($books[1]) or $books->remove(1) empty($books) | $books->isEmpty() in_array($book, $books) | $books->contains($book) array_pop($books) | $books->pop() etc.
Warning: empty($books) always returns false when using a collection, even on a non-empty one. This is a PHP limitation. Prefer $books->isEmpty(), or count($books)>0.
Tip: If you can't afford updating your code to support collections instead of arrays, you can still ask Propel to generate 1.4-compatible model objects by overriding the propel.builder.object.class setting in your build.properties, as follows:
propel.builder.object.class = builder.om.PHP5ObjectNoCollectionBuilder
The PropelCollection class offers even more methods that you will soon use a lot:
<?php $books->getArrayCopy() // get the array inside the collection $books->toArray() // turn all objects to associative arrays $books->getPrimaryKeys() // get an array of the primary keys of all the objects in the collection $books->getModel() // return the model of the collection, e.g. 'Book'
Another advantage of using a collection instead of an array is that Propel can hydrate model objects on demand. Using this feature, you'll never fall short of memory again. Available through the setFormatter() method of Model Queries, on-demand hydration is very easy to trigger:
<?php $books = BookQuery::create() ->limit(50000) ->setFormatter(ModelCriteria::FORMAT_ON_DEMAND) // just add this line ->find(); foreach ($books as $book) { echo $book->getTitle(); }
In this example, Propel will hydrate the Book objects row by row, after the foreach call, and reuse the memory between each iteration. The consequence is that the above code won't use more memory when the query returns 50,000 results than when it returns 5.
ModelCriteria::FORMAT_ON_DEMAND is one of the many formatters provided by the new Query objects. You can also get a collection of associative arrays instead of objects, if you don't need any of the logic stored in your model object, by using ModelCriteria::FORMAT_ARRAY.
The documentation? describes each formatter, and how to use it.
Model Criteria
Generated Model Queries inherit from ModelCriteria, which extends your good old Criteria, and adds a few useful features. Basically, a ModelCriteria is a Criteria linked to a Model; by using the information stored in the generated TableMaps at runtime, ModelCriteria offers powerful methods to simplify the process of writing a query.
For instance, ModelCriteria::where() provides similar functionality to Criteria::add(), except that its PDO-like syntax removes the burden of Criteria constants for comparators.
<?php $book = BookQuery::create() ->where('Book.Title LIKE ?', 'War And P%') ->findOne();
Propel analyzes the clause passed as first argument of where() to determine which escaping to use for the value passed as second argument. In the above example, the Book::TITLE column is declared as VARCHAR in the schema, so Propel will bind the title as a string.
The where() method can also accept more complex clauses. You just need to explicit every column name as 'ModelClassName.ColumnPhpName', as follows:
<?php $book = BookQuery::create() ->where('UPPER(Book.Title) LIKE ?', 'WAR AND P%') ->where('(Book.Price * 100) <= ?', 1500) ->findOne();
Another great addition of ModelCriteria is the join() method, which just needs the name of a related model to build a JOIN clause:
<?php $books = BookQuery::create() ->join('Book.Author') ->where('CONCAT(Author.FirstName, " ", Author.LastName) = ?', 'Leo Tolstoi') ->find();
ModelCriteria has a built-in support for table aliases, which allows to setup a query using two joins on the same table, which was not possible with the Criteria object:
<?php $books = BookQuery::create('b') // use 'b' as an alias for 'Book' in the query ->join('b.Author a') // use 'a' as an alias for 'Author' in the query ->where('CONCAT(a.FirstName, " ", a.LastName) = ?', 'Leo Tolstoi') ->find();
This syntax probably looks familiar, because it is very close to SQL. So you probably won't need long to figure out how to write a complex query with it. The documentation offers an entire chapter? dedicated to the new ModelCriteria class. Make sure you read it to see the power of this new query API.
Criteria Enhancements
Generated queries and ModelQueries are not the only ones to have received a lot of attention in Propel 1.5. The Criteria object itself sees a few improvements, that will ease the writing of queries with complex logic.
Criteria::addOr() operates the way you always expected it to. For instance, in Propel 1.4, addOr() resulted in a SQL AND if called on a column with no other condition:
<?php // addOr() used to work on a column with an existing condition $c = new Criteria(); $c->add(BookPeer::TITLE, '%Leo%', Criteria::LIKE); $c->addOr(BookPeer::TITLE, '%Tolstoi%', Criteria::LIKE); // translates in SQL as // WHERE (book.TITLE LIKE '%Leo%' OR book.TITLE LIKE '%Tolstoi%') // addOr() used to fail on a column with no existing condition $c = new Criteria(); $c->add(BookPeer::TITLE, '%Leo%', Criteria::LIKE); $c->addOr(BookPeer::ISBN, '1234', Criteria::EQUAL); // translates in SQL as // WHERE book.TITLE LIKE '%Leo%' AND book.ISBN = '1234'
This is fixed in Propel 1.5. This means that you don't need to call upon the Criterion object for a simple OR clause:
<?php // addOr() now works on a column with no existing condition $c = new Criteria(); $c->add(BookPeer::TITLE, '%Leo%', Criteria::LIKE); $c->addOr(BookPeer::ISBN, '1234', Criteria::EQUAL); // translates in SQL as // WHERE (book.TITLE LIKE '%Leo%' OR book.ISBN = '1234') // and it's much faster to write than $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);
add() and addOr() only allow simple logical operations on a single condition. For more complex logic, Propel 1.4 forced you to use Criterions again. This is no longer the case in Propel 1.5, which provides a new Criteria::combine() method. It expects an array of named conditions to be combined, and an operator. Use Criteria::addCond() to create a condition, instead of the usual add():
<?php $c = new Criteria(); $c->addCond('cond1', BookPeer::TITLE, 'Foo', Criteria::EQUAL); // creates a condition named 'cond1' $c->addCond('cond2', BookPeer::TITLE, 'Bar', Criteria::EQUAL); // creates a condition named 'cond2' $c->combine(array('cond1', 'cond2'), Criteria::LOGICAL_OR); // combine 'cond1' and 'cond2' with a logical OR // translates in SQL as // WHERE (book.TITLE = 'Foo' OR book.TITLE = 'Bar');
combine() accepts more than two conditions at a time:
<?php $c = new Criteria(); $c->addCond('cond1', BookPeer::TITLE, 'Foo', Criteria::EQUAL); $c->addCond('cond2', BookPeer::TITLE, 'Bar', Criteria::EQUAL); $c->addCond('cond3', BookPeer::TITLE, 'FooBar', Criteria::EQUAL); $c->combine(array('cond1', 'cond2', 'cond3'), Criteria::LOGICAL_OR); // translates in SQL as // WHERE ((book.TITLE = 'Foo' OR book.TITLE = 'Bar') OR book.TITLE = 'FooBar');
combine() itself can return a named condition to be combined later. So it allows for any level of logical complexity:
<?php $c = new Criteria(); $c->addCond('cond1', BookPeer::TITLE, 'Foo', Criteria::EQUAL); $c->addCond('cond2', BookPeer::TITLE, 'Bar', Criteria::EQUAL); $c->combine(array('cond1', 'cond2'), Criteria::LOGICAL_OR, 'cond12'); $c->addCond('cond3', BookPeer::ISBN, '1234', Criteria::EQUAL); $c->addCond('cond4', BookPeer::ISBN, '4567', Criteria::EQUAL); $c->combine(array('cond3', 'cond4'), Criteria::LOGICAL_OR, 'cond34'); $c->combine(array('cond12', 'cond34'), Criteria::LOGICAL_AND); // WHERE (book.TITLE = 'Foo' OR book.TITLE = 'Bar') // AND (book.ISBN = '1234' OR book.ISBN = '4567');
The new combine() method makes it much easier to handle logically complex criterions. The good news is that if your application code already uses the old Criterion way, it will continue to work with Propel 1.5 as all these changes are backwards compatible.
Of course, since Model Queries extend Criteria, this new feature is available for all your queries, with a slightly different syntax, in order to support column phpNames:
<?php $books = Bookquery::create() ->condition('cond1', 'Book.Title = ?', 'Foo') ->condition('cond2', 'Book.Title = ?', 'Bar') ->combine(array('cond1', 'cond2'), Criteria::LOGICAL_OR, 'cond12') ->condition('cond3', 'Book.ISBN = ?', '1234') ->condition('cond4', 'Book.ISBN = ?', '4567') ->combine(array('cond3', 'cond4'), Criteria::LOGICAL_OR, 'cond34') ->combine(array('cond12', 'cond34'), Criteria::LOGICAL_AND) ->find(); // WHERE (book.TITLE = 'Foo' OR book.TITLE = 'Bar') // AND (book.ISBN = '1234' OR book.ISBN = '4567');
Many-to-Many Relationships
At last, Propel generates the necessary methods to retrieve related objects in a many-to-many relationship. Since this feature is often needed, many developers already wrote these methods themselves. To avoid method collision, the generation of many-to-many getters is therefore optional.
All you have to do is to add the isCrossRef attribute to the cross reference table, and rebuild your model. For instance, if a User has many Groups, and the Group has many Users, the many-to-many relationship is materialized by a user_group cross reference table:
<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>
Then, both end of the relationship see the other end through a one-to-many relationship. That means that you can deal with related objects just like you normally do, without ever creating instances of the cross reference object:
<?php // 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 = GroupPeer::create() ->filterByUser($user) ->find();
The syntax should be no surprise, since it's the same as the one for one-to-many relationships. Find more details about many-to-many relationships in the relationships documentation?.
New Behaviors
The new behavior system, introduced in Propel 1.4, starts to unleash its true power with this release. Three new behaviors implement the most common customizations of object models: nested_sets, sluggable, and sortable.
Nested Set Behavior
Using the treeMode attribute in a schema, you could turn a Propel model into a hierarchical data store starting with Propel 1.3. This method is now deprecated in favor of a new nested_set behavior, that does eactly the same thing, but in a more extensible and effective way.
The main difference between the two implementations is performance. On the first levels of a large tree, the Propel 1.3 implementation of Nested sets used to consume a very large amount of memory and CPU to retrieve the siblings or the children of a given node. This is no longer true with the new behavior.
This performance boost comes at a small price: you must add a new "level" column to your nested set models, and let the behavior update this column for the whole tree.
For instance, if you used nested sets to keep a list of categories, the schema used to look like:
<table name="category" treeMode="NestedSet"> <column name="id" primaryKey="true" autoIncrement="true" type="INTEGER"/> <column name="left" nestedSetLeftKey="true" type="INTEGER"/> <column name="right" nestedSetLeftKey="true" type="INTEGER"/> <column name="name" required="true" type="VARCHAR" size="10" /> </table>
The upgrade path is then pretty straightforward:
1 - Update the schema, by removing the treeMode and nestedSet attributes and adding the nested_set behavior and the tree_level column:
<table name="category"> <column name="id" primaryKey="true" autoIncrement="true" type="INTEGER"/> <column name="left" type="INTEGER"/> <column name="right" type="INTEGER"/> <column name="tree_level" type="INTEGER"/> <column name="name" required="true" type="VARCHAR" size="10" /> <behavior name="nested_set"> <parameter name="left_column" value="left" /> <parameter name="right_column" value="right" /> <parameter name="level_column" value="tree_level" /> </behavior> </table>
2 - Rebuild the model
3 - Change the parent class of your model classes (object and peer) that used the nested set treeMode:
<?php // use class Category extends BaseCategory // instead of class Category extends BaseCategoryNestedSet // use class CategoryPeer extends BaseCategoryPeer // instead of class CategoryPeer extends BaseCategoryNestedSetPeer
4 - Add the level column to the database. For instance, in MySQL:
ALTER TABLE `category` ADD COLUMN tree_level INTEGER;
5 - Update the level value in the existing nodes, using the fixLevels() Peer method
<?php // run it once CategoryPeer::fixLevels();
The nested set behavior implementation has a few added benefits:
- All the methods that execute several queries use transactions, so a database failure won't break a tree anymore
- The methods retrieveing a list of nodes accept a Criteria as first parameter, to filter the results
- New methods make it easier to work with trees: retrieveRoots(), isDescendantOf(), isAncestorOf(), getBranch(), addChild(), etc.
- There is no longer any introspection at runtime (for instance to check scope support), resulting in yet another boost in performance
- A few bugs were fixed (for instance in the use of delete())
- A node can be inserted to the tree after it is saved. This allows for better preparation of data before insertion in the tree
- The new implementation is much more robust, thanks to a full unit testing coverage. That's more than 350 unit tests to ensure that your trees won't ever break due to an incorrect piece of code in the nested sets.
- The API was rethought to make it more intuitive - but with method proxies to keep BC
As a consequence, the use of treeMode="NestedSet" in a schema is deprecated. Check the new nested_set behavior documentation? for more details.
Sluggable Behavior
This behavior answers a very common need: to give a model a unique string representation that can be used to make a user-friendly URL.
The classical example is that of a blog engine, where you need every record of a post table to have a unique URL. Simply enable the sluggable behavior in your schema and rebuild the model:
<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>
Now, every time you save a new Post object, Propel will compose its slug according to the pattern defined in the behavior parameter and save it in an additional slug column:
<?php $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'
Propel replaces every name enclosed between brackets in the slug pattern by the related column value. It also cleans up the string to make it URL-compatible, and ensures that it is unique.
If you use this slug in URLs, you will need to retrieve a Post object based on it. This is just a one-liner:
<?php $post = PostQuery::create()->findOneBySlug('/posts/how-is-life-on-earth');
There are many ways to customize the sluggable behavior to match the needs of your applications. Check the new sluggable behavior documentation? for more details.
Concrete Table Inheritance Behavior
Propel has offered Single Table Inheritance? for a long time. But for complex table inheritance needs, it is necessary to provide Concrete Table Inheritance. Starting with Propel 1.5, this inheritance implementation is supported through the new concrete_inheritance behavior.
In the following example, the article and video tables use this behavior to inherit the columns and foreign keys of their parent table, content:
<table name="content"> <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/> <column name="title" type="VARCHAR" size="100"/> <column name="category_id" required="false" type="INTEGER" /> <foreign-key foreignTable="category" onDelete="cascade"> <reference local="category_id" foreign="id" /> </foreign-key> </table> <table name="category"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" /> <column name="name" type="VARCHAR" size="100" primaryString="true" /> </table> <table name="article"> <behavior name="concrete_inheritance"> <parameter name="extends" value="content" /> </behavior> <column name="body" type="VARCHAR" size="100"/> </table> <table name="video"> <behavior name="concrete_inheritance"> <parameter name="extends" value="content" /> </behavior> <column name="resource_link" type="VARCHAR" size="100"/> </table>
The behavior copies the columns of the parent table to the child tables. That means that the generated Article and Video models have a Title property and a Category relationship:
<?php // create a new Category $cat = new Category(); $cat->setName('Movie'); $cat->save(); // create a new Article $art = new Article(); $art->setTitle('Avatar Makes Best Opening Weekend in the History'); $art->setCategory($cat); $art->setContent('With $232.2 million worldwide total, Avatar had one of the best-opening weekends in the history of cinema.'); $art->save(); // create a new Video $vid = new Video(); $vid->setTitle('Avatar Trailer'); $vid->setCategory($cat); $vid->setResourceLink('http://www.avatarmovie.com/index.html') $vid->save();
If Propel stopped there, the concrete_inheritance behavior would only provide a shorcut to avoid repeating tags in the schema. But wait, there is more: the Article and Video classes actually extend the Content class:
<?php class Content extends BaseContent { public function getCategoryName() { return $this->getCategory()->getName(); } } echo $art->getCategoryName(); // 'Movie' echo $vid->getCategoryName(); // 'Movie'
And the true power of Propel's Concrete Table Inheritance is that every time you save an Article or a Video object, Propel saves a copy of the title and category_id columns in a Content object. Consequently, retrieving objects regardless of their child type becomes very easy:
<?php $conts = ContentQuery::create()->find(); foreach ($conts as $content) { echo $content->getTitle() . "(". $content->getCategoryName() ")/n"; } // Avatar Makes Best Opening Weekend in the History (Movie) // Avatar Trailer (Movie)
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.
Check out the brand new Inheritance Documentation? for more details on using and customizing this behavior.
Sortable Behavior
Have you ever enhanced a Propel Model to give it the ability to move up or down in an ordered list? The sortable behavior, new in Propel 1.5, offers exactly that... and even more.
As usual for behaviors, activate sortable in your schema.yml:
<table name="task"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" /> <column name="title" type="VARCHAR" required="true" primaryString="true" /> <column name="user_id" required="true" type="INTEGER" /> <foreign-key foreignTable="user" onDelete="cascade"> <reference local="user_id" foreign="id" /> </foreign-key> <behavior name="sortable"> <parameter name="use_scope" value="true" /> <parameter name="scope_column" value="user_id" /> </behavior> </table>
Then rebuild your model, and you're done. You have just created an ordered task list for users:
<?php // test users $paul = new User(); $john = new User(); // create the tasks $t1 = new Task(); $t1->setTitle('Wash the dishes'); $t1->setUser($paul); $t1->save(); echo $t1->getRank(); // 1 $t2 = new Task(); $t2->setTitle('Do the laundry'); $t2->setUser($paul); $t2->save(); echo $t2->getRank(); // 2 $t3 = new Task(); $t3->setTitle('Rest a little'); $t3->setUser($john); $t3->save() echo $t3->getRank(); // 1, because John has his own task list // retrieve the tasks $allPaulsTasks = TaskPeer::retrieveList($scope = $paul->getId()); $allJohnsTasks = TaskPeer::retrieveList($scope = $john->getId()); $t1 = TaskPeer::retrieveByRank($rank = 1, $scope = $paul->getId()); $t2 = $t1->getNext(); $t2->moveUp(); echo $t2->getRank(); // 1 echo $t1->getRank(); // 2
This new behavior is fully unit tested and very customizable. Check out all you can do with sortable in the sortable behavior documentation?.
Timestampable Behavior
This behavior is not new, since it was introduced in Propel 1.4. However, with the introduction of model queries, it gains specific query methods that will ease your work when retrieving objects based on their update date:
<?php $books = BookQuery::create() ->recentlyUpdated() // adds a minimum value for the update date ->lastUpdatedFirst() // orders the results by descending update date ->find();
Better toArray()
When you call toArray() on a model object, you can now ask for the related objects:
<?php $bookArray = $book->toArray($keyType = BasePeer::TYPE_COLNAME, $includeLazyLoadColumns = true, $includeForeignObjects = true); print_r($bookArray); => 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' ) )
Only the related objects that were already hydrated appear in the result, so toArray() never issues additional queries. Together with the ability to return arrays instead of objects when using PropelQuery, this addition will help to debug and optimize model code.
Better Oracle Support
The Oracle adapter for the generator, the reverse engineering, and the runtime components have been greatly improved. This should provide an easier integration of Propel with an Oracle database.
Code Cleanup
Directory Structure Changes
The organization of the Propel runtime and generator code has been reworked, in order to make navigation across Propel classes easier for developers. End users should see no difference, apart if your build.properties references alternate builder classes in the Propel code. In that case, you will need to update your build.properties with the new paths. For instance, a reference to:
propel.builder.peer.class = propel.engine.builder.om.php5.PHP5PeerBuilder
Must be changed to:
propel.builder.peer.class = builder.om.PHP5PeerBuilder
Browse the Propel generator directory structure to find the classes you need.
DebugPDO Refactoring
To allow custom connection handlers, the debug code that was written in the DebugPDO class has been moved to PropelPDO. The change is completely backwards compatible, but makes it easier to connect to a database without using PDO.
During the change, the documentation about Propel logging and debugging features? was rewritten and should now be clearer.
propel-gen Script Modifications
The propel-gen script no longer requires a path to the project directory if you call it from a project directory. That means that calling propel-gen with a single argument defaults to expecting a task name:
> cd /path/to/my/project > propel-gen reverse
By default, the propel-gen command called without a task name defaults to the main task (and builds the model, the SQL, and the generation).
Note: The behavior of the propel-gen script when called with one parameter differs from what it used to be in Propel 1.4, where the script expected a path in every situation. So the following syntax won't work anymore:
> propel-gen /path/to/my/project
Instead, use either:
> cd /path/to/my/project > propel-gen
or:
> propel-gen /path/to/my/project main
License Change
Propel is more open-source than ever. To allow for an easier distribution, the open-source license of the Propel library changes from LGPL3 to MIT. This MIT License is also known as the X11 License.
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.
Of course, you 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.
Miscellaneous
- Generated model classes now offer a fromArray() and a toArray() method by default. This feature existed before, but was disabled by default in the build.properties. The addGenericAccessors and addGenericMutators settings are therefore enabled by default in Propel 1.5.
- You can now prefix all the table names of a database schema by setting the tablePrefix attribute of the <database> tag.
- The addIncludes build property introduced in Propel 1.4 is now set to false by default. That means that the runtime autoloading takes care of loading all classes at runtime, including generated Base classes.
- A bugfix in the name generator for related object getter in tables with two foreign keys related to the same table may have introduced problems in applications relying on old (wrong) names. Check your generated base model classes for the getXXXrelatedByYYY() and modify the application code relying on it if it exists. A good rule of thumb to avoid problems in such case is to name your relations by using the phpName and refPhpName attributes in the <foreign-key> element in the schema.
- XSL transformation of your schemas is no longer enabled by default. Turn the propel.schema.transform setting to true in your build.properties to enable it again. This change removes the requirement on the libxslt extention for Propel.
- ModelObject::addSelectColumns() now accepts an additional parameter to allow the use of table aliases
- Added ModelObject::clear() to reinitialize a model object
- Added ModelObject::isPrimaryKeyNull() method to check of an object was hydrated with no values (in case of a left join)
- Added Criteria::addSelectModifier($modifier) to add more than one select modifier (e.g. 'SQL_CALC_FOUND_ROWS', 'HIGH_PRIORITY', etc.)
- Added PeerClass::addGetPrimaryKeyFromRow() to retrieve the Primary key from a result row
- Added a new set of constants in the generated Peer class to list column names without the table name (this is BasePeer::TYPE_RAW_COLNAME)
- Removed references to Creole in the code (Propel uses PDO instead of Creole since version 1.3)