Author: Leon.van.der.Ree
Date: 2010-03-23 13:00:24 +0100 (Tue, 23 Mar 2010)
New Revision: 28701
Added:
plugins/sfPropelObjectPathBehaviorPlugin/branches/
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/README
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/ObjectPathBehavior.php
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/ObjectPathCriteria.php
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/test/
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/test/ObjectPathCriteriaTest.php
plugins/sfPropelObjectPathBehaviorPlugin/tags/
plugins/sfPropelObjectPathBehaviorPlugin/trunk/
Log:
initial commit of sfPropelObjectPathBehaviorPlugin
Added: plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/README
===================================================================
--- plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/README
(rev 0)
+++ plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/README
2010-03-23 12:00:24 UTC (rev 28701)
@@ -0,0 +1,61 @@
+Introduction
+============
+
+This plugin adds ObjectPath support to Propel (1.5+).
+
+This syntax makes it very easy to do sorting and filtering on foreign fields.
+
+ObjectPaths are dot-seperated relation-names that relate from one object to
another and this behavior translates the objectPaths to table-aliasses.
+
+
+Example
+=======
+
+So a very simple ObjectPath from City to Country is simply "Country" and from
Country to City it would be "City":
+In other words the relation-name.
+
+However ObjectPaths support dots to 'jump' from object to object. So for
example from Review to Book to Author would be "Book.Author".
+
+In Php-code this shows up as:
+
+ [php]
+ $query = new ReviewQuery();
+ $reviews = $query->
+ joinByObjectPath('Book.Author')->
+ orderBy('Book.Author.FirstName', Criteria::ASC)->
+ find();
+
+to find all reviews, joined with their Books, joined with their Authors.
+
+TODO: explain all changes and the behavior in more detail
+
+Installation
+============
+
+To install this plugin you need to checkout this code in your symfony plugins
folder under `sfPropelObjectPathBehaviorPlugin`
+
+After the checkout, modify your `project/config/propel.ini`
+
+add
+
+ [ini]
+ propel.behavior.object_path.class =
plugins.sfPropelObjectPathBehaviorPlugin.lib.ObjectPathBehavior
+
+
+and add the default behavior, by modifying
+
+ [ini]
+ propel.behavior.default =
symfony,symfony_i18n,object_path
+
+
+
+now rebuild your model and you are good to go!
+
+ $ php symfony propel:build-model
+
+
+
+Note
+====
+
+The objectPathsBehavior is used in the sfDataSourcePlugin.
\ No newline at end of file
Added:
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/ObjectPathBehavior.php
===================================================================
---
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/ObjectPathBehavior.php
(rev 0)
+++
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/ObjectPathBehavior.php
2010-03-23 12:00:24 UTC (rev 28701)
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+class ObjectPathBehavior extends Behavior
+{
+ public function getQueryBuilderModifier()
+ {
+ if (is_null($this->queryBuilderModifier))
+ {
+ $this->queryBuilderModifier = new ObjectPathQueryBuilderModifier($this);
+ }
+ return $this->queryBuilderModifier;
+ }
+}
+
+
+class ObjectPathQueryBuilderModifier
+{
+ public function parentClass()
+ {
+ return 'ObjectPathCriteria';
+ }
+
+}
Added:
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/ObjectPathCriteria.php
===================================================================
---
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/ObjectPathCriteria.php
(rev 0)
+++
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/lib/ObjectPathCriteria.php
2010-03-23 12:00:24 UTC (rev 28701)
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * An ObjectPath is a chain of relationNames, seperated by a dot, resulting in
a joined object
+ *
+ * @author leonvanderree
+ *
+ */
+class ObjectPathCriteria extends ModelCriteria
+{
+ /**
+ * Recursively performs JoinWith on the provided ObjectPaths
+ *
+ * @param string $objectPath one or more strings with objectPaths
+ * @return string
+ */
+ public function joinByObjectPath($objectPath)
+ {
+ $objectPaths = func_get_args();
+
+ // process all objectPaths
+ foreach ($objectPaths as $objectPath)
+ {
+ // brake up the objectPath into an array of relationsNames
+ $relationsNames = explode('.', $objectPath, 2);
+
+ // resolve current object and the (first) relationName
+ if (count($relationsNames)<1)
+ {
+ throw new PropelException('no relation provided');
+ }
+ $relationName = $relationsNames[0];
+ $alias = $this->translateObjectPathToAlias($relationName);
+
+ // don't perform JoinWith, if already joined
+ // alternatively I could test if the join is not already in the
with-array
+ // (this is a little more computational intense, but will perform with
even though the join has already been performed)
+ if (!isset($this->joins[$alias]))
+ {
+ $this->joinWith($relationName.' '.$alias);
+ }
+
+ // if more relations are provided, continue (recursively) parsing the
objectPath
+ if (isset($relationsNames[1]))
+ {
+ $this->useQuery($alias)
+ ->joinByObjectPath($relationsNames[1])
+ ->endUse();
+ }
+ }
+
+ //fluid interface
+ return $this;
+ }
+
+ /**
+ * Resolves the database alias for an objectPath
+ *
+ * Basically this comes down to replacing all dots with an underscore
+ * and preceeding it with the queryModel-alias
+ *
+ * so $reviewQuery->translateObjectPathToAlias('Book.Author')
+ * becomes Review_Book_Author as the table-alias for Book-Author
+ *
+ * @param string $objectPath
+ * @return string the constructed alias
+ */
+ public function translateObjectPathToAlias($objectPath)
+ {
+ // brake up the objectPath into an array of relationsNames
+ $relationsNames = explode('.', $objectPath, 2);
+
+ // resolve current object and the (first) relationName
+ if (count($relationsNames)<1)
+ {
+ throw new PropelException('no relation provided');
+ }
+ $relationName = $relationsNames[0];
+ $alias = $this->getModelAliasOrName().'_'.$relationName;
+
+ // if more relations are provided, continue (recursively) parsing the
objectPath
+ if (isset($relationsNames[1]))
+ {
+ $alias = $this->useQuery($alias)
+ ->translateObjectPathToAlias($relationsNames[1]);
+ }
+
+ return $alias;
+ }
+
+ /**
+ * Finds a column and a SQL translation for a pseudo SQL column name
+ * Respects (and requires) table aliases previously registered in a
join() or addAlias()
+ * Examples:
+ * <code>
+ * $c->getColumnFromName('Book.Title');
+ * => array($bookTitleColumnMap, 'book.TITLE')
+ * $c->join('Book.Author a')
+ * ->getColumnFromName('a.FirstName');
+ * => array($authorFirstNameColumnMap, 'a.FIRST_NAME')
+ *
+ * // NEW: propertyPaths can be used after joining by ObjectPaths
+ * $reviewQuery->joinByObjectPath('Book.Author')
+ * ->getColumnFromName('Book.Author.FirstName');
+ * => array($authorFirstNameColumnMap,
'Review_Book_Author.FIRST_NAME')
+ * </code>
+ *
+ * @param string $propertyPath String representing the column name
in a pseudo SQL clause, e.g. 'Book.Title'
+ *
+ * @return array List($columnMap, $realColumnName)
+ */
+ public function getColumnFromName($propertyPath, $failSilently = true)
+ {
+ // if we have a propertyPath containing an objectPath (not only
a columnName)
+ // we recursively parse the objectPath first to get into the
joined queryModel
+ if (($lastDot = strrpos($propertyPath, '.')) !== false) {
+ $objectPath = substr($propertyPath, 0, $lastDot);
+ $columnName = substr($propertyPath, $lastDot + 1);
+
+ //recursively get alias-name
+ $alias = $this->translateObjectPathToAlias($objectPath);
+
+ if (isset($this->joins[$alias])) {
+ return
$this->useQuery($alias)->getColumnFromName($columnName, $failSilently);
+ }
+
+ }
+
+ // only columnName provided, or relation-name is unknown (which
might be a table alias)
+ // process normal column as before
+ return parent::getColumnFromName($propertyPath, $failSilently);
+ }
+
+}
\ No newline at end of file
Added:
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/test/ObjectPathCriteriaTest.php
===================================================================
---
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/test/ObjectPathCriteriaTest.php
(rev 0)
+++
plugins/sfPropelObjectPathBehaviorPlugin/branches/1.5/test/ObjectPathCriteriaTest.php
2010-03-23 12:00:24 UTC (rev 28701)
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ *
+ * TODO: I will translate these tests to lime to make it easier to test them
in symfony
+ *
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+require_once 'tools/helpers/bookstore/BookstoreTestBase.php';
+require_once 'tools/helpers/bookstore/BookstoreDataPopulator.php';
+
+/**
+ * Test class for ObjectPathCriteria.
+ *
+ * @author Leon van der Ree
+ * @version $Id: ObjectPathCriteriaTest.php $
+ * @package runtime.query
+ */
+class ObjectPathCriteriaTest extends BookstoreTestBase
+{
+
+ public function testJoinByObjectPath()
+ {
+ // test one objectPath
+ $c = new ObjectPathCriteria('bookstore', 'Review');
+ $c->joinByObjectPath('Book.Author');
+
+ $con = Propel::getConnection(BookPeer::DATABASE_NAME);
+ $c->find($con);
+ // TODO: probably don't test against sql, but against
ModelCriteria
+ $expectedSQL = "SELECT review.ID, review.REVIEWED_BY,
review.REVIEW_DATE, review.RECOMMENDED, review.STATUS, review.BOOK_ID,
Review_Book.ID, Review_Book.TITLE, Review_Book.ISBN, Review_Book.PRICE,
Review_Book.PUBLISHER_ID, Review_Book.AUTHOR_ID, Review_Book_Author.ID,
Review_Book_Author.FIRST_NAME, Review_Book_Author.LAST_NAME,
Review_Book_Author.EMAIL, Review_Book_Author.AGE FROM `review` INNER JOIN book
Review_Book ON (review.BOOK_ID=Review_Book.ID) INNER JOIN author
Review_Book_Author ON (Review_Book.AUTHOR_ID=Review_Book_Author.ID)";
+ $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(),
'joinByObjectPath recursively performs joinWith on all relations, and creates
correct aliasses');
+
+
+ // test multiple objectPaths
+ $c = new ObjectPathCriteria('bookstore', 'Review');
+ $c->joinByObjectPath('Book.Author', 'Book.Publisher');
+
+ $con = Propel::getConnection(BookPeer::DATABASE_NAME);
+ $c->find($con);
+ // TODO: probably don't test against sql, but against
ModelCriteria
+ $expectedSQL = "SELECT review.ID, review.REVIEWED_BY,
review.REVIEW_DATE, review.RECOMMENDED, review.STATUS, review.BOOK_ID,
Review_Book.ID, Review_Book.TITLE, Review_Book.ISBN, Review_Book.PRICE,
Review_Book.PUBLISHER_ID, Review_Book.AUTHOR_ID, Review_Book_Author.ID,
Review_Book_Author.FIRST_NAME, Review_Book_Author.LAST_NAME,
Review_Book_Author.EMAIL, Review_Book_Author.AGE, Review_Book_Publisher.ID,
Review_Book_Publisher.NAME FROM `review` INNER JOIN book Review_Book ON
(review.BOOK_ID=Review_Book.ID) INNER JOIN author Review_Book_Author ON
(Review_Book.AUTHOR_ID=Review_Book_Author.ID) INNER JOIN publisher
Review_Book_Publisher ON (Review_Book.PUBLISHER_ID=Review_Book_Publisher.ID)";
+ $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(),
'joinByObjectPath can hande multiple objectPaths');
+ }
+
+ public function testTranslateObjectPathToAlias()
+ {
+ // test objectPath translation
+ $c = new ObjectPathCriteria('bookstore', 'Review');
+ $c->joinByObjectPath('Book.Author');
+
+ $tableAlias = $c->translateObjectPathToAlias('Book.Author');
+ $this->assertEquals('Review_Book_Author', $tableAlias,
'translateObjectPathToAlias resolves table-alias correctly');
+
+ }
+
+ public function testGetColumnFromName()
+ {
+ $c = new ObjectPathCriteria('bookstore', 'Review');
+ $c->joinByObjectPath('Book.Author');
+
+ list($columnMap, $realColumnName) =
$c->getColumnFromName('Book.Title');
+ $this->assertEquals('Review_Book.TITLE', $realColumnName,
'getColumnFromName resolves realColumnName correctly');
+ $this->assertTrue($columnMap instanceof ColumnMap,
'getColumnFromName returns instane of ColumnMap');
+
+ list($columnMap, $realColumnName) =
$c->getColumnFromName('Book.Author.FirstName', false);
+ $this->assertEquals('Review_Book_Author.FIRST_NAME',
$realColumnName, 'getColumnFromName resolves deeper realColumnNames correctly');
+ $this->assertTrue($columnMap instanceof ColumnMap,
'getColumnFromName returns deeper instanes of ColumnMap');
+ }
+}
\ No newline at end of file
--
You received this message because you are subscribed to the Google Groups
"symfony SVN" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/symfony-svn?hl=en.