adding zend project folders into old campcaster.
This commit is contained in:
parent
56abfaf28e
commit
7ef0c18b26
4045 changed files with 1054952 additions and 0 deletions
130
library/propel/docs/behavior/aggregate_column.txt
Normal file
130
library/propel/docs/behavior/aggregate_column.txt
Normal file
|
@ -0,0 +1,130 @@
|
|||
= Aggregate Column Behavior =
|
||||
|
||||
The `aggregate_column` behavior keeps a column updated using an aggregate function executed on a related table.
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `aggregate_column` behavior to a table. You must provide parameters for the aggregate column `name`, the foreign table name, and the aggegate `expression`. For instance, to add an aggregate column keeping the comment count in a `post` table:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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>
|
||||
<table name="comment">
|
||||
<column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" />
|
||||
<column name="post_id" type="INTEGER" />
|
||||
<foreign-key foreignTable="post" onDelete="cascade">
|
||||
<reference local="post_id" foreign="id" />
|
||||
</foreign-key>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Rebuild your model, and insert the table creation sql again. The model now has an additional `nb_comments` column, of type `integer` by default. And each time an record from the foreign table is added, modified, or removed, the aggregate column is updated:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$post = new Post();
|
||||
$post->setTitle('How Is Life On Earth?');
|
||||
$post->save();
|
||||
echo $post->getNbComments(); // 0
|
||||
$comment1 = new Comment();
|
||||
$comment1->setPost($post);
|
||||
$comment1->save();
|
||||
echo $post->getNbComments(); // 1
|
||||
$comment2 = new Comment();
|
||||
$comment2->setPost($post);
|
||||
$comment2->save();
|
||||
echo $post->getNbComments(); // 2
|
||||
$comment2->delete();
|
||||
echo $post->getNbComments(); // 1
|
||||
}}}
|
||||
|
||||
The aggregate column is also kept up to date when related records get modified through a Query object:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
CommentQuery::create()
|
||||
->filterByPost($post)
|
||||
->delete():
|
||||
echo $post->getNbComments(); // 0
|
||||
}}}
|
||||
|
||||
== Customizing The Aggregate Calculation ==
|
||||
|
||||
Any aggregate function can be used on any of the foreign columns. For instance, you can use the `aggregate_column` behavior to keep the latest update date of the related comments, or the total votes on the comments. You can even keep several aggregate columns in a single table:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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>
|
||||
<behavior name="aggregate_column">
|
||||
<parameter name="name" value="last_comment" />
|
||||
<parameter name="foreign_table" value="comment" />
|
||||
<parameter name="expression" value="MAX(created_at)" />
|
||||
</behavior>
|
||||
<behavior name="aggregate_column">
|
||||
<parameter name="name" value="total_votes" />
|
||||
<parameter name="foreign_table" value="comment" />
|
||||
<parameter name="expression" value="SUM(vote)" />
|
||||
</behavior>
|
||||
</table>
|
||||
<table name="comment">
|
||||
<column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" />
|
||||
<column name="post_id" type="INTEGER" />
|
||||
<foreign-key foreignTable="post" onDelete="cascade">
|
||||
<reference local="post_id" foreign="id" />
|
||||
</foreign-key>
|
||||
<column name="created_at" type="TIMESTAMP" />
|
||||
<column name="vote" type="INTEGER" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
The behavior adds a `computeXXX()` method to the `Post` class to compute the value of the aggregate function. This method, called each time records are modified in the related `comment` table, is the translation of the behavior settings into a SQL query:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// in om/BasePost.php
|
||||
public function computeNbComments(PropelPDO $con)
|
||||
{
|
||||
$stmt = $con->prepare('SELECT COUNT(id) FROM `comment` WHERE comment.POST_ID = :p1');
|
||||
$stmt->bindValue(':p1', $this->getId());
|
||||
$stmt->execute();
|
||||
return $stmt->fetchColumn();
|
||||
}
|
||||
}}}
|
||||
|
||||
You can override this method in the model class to customize the aggregate column calculation.
|
||||
|
||||
== Customizing The Aggregate Column ==
|
||||
|
||||
By default, the behavior adds one columns to the model. If this column is already described in the schema, the behavior detects it and doesn't add it a second time. This can be useful if you need to use a custom `type` or `phpName` for the aggregate column:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="post">
|
||||
<column name="id" type="INTEGER" required="true" primaryKey="true" autoIncrement="true" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<column name="nb_comments" phpName="CommentCount" type="INTEGER" />
|
||||
<behavior name="aggregate_column">
|
||||
<parameter name="name" value="nb_comments" />
|
||||
<parameter name="foreign_table" value="comment" />
|
||||
<parameter name="expression" value="COUNT(id)" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
|
@ -0,0 +1,89 @@
|
|||
= Alternative Coding Standards Behavior =
|
||||
|
||||
The `alternative_coding_standards` behavior changes the coding standards of the model classes generated by Propel to match your own coding style.
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `alternative_coding_standards` behavior to a table:
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="alternative_coding_standards" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Rebuild your model, and you're ready to go. The code of the model classes now uses an alternative set of coding standards:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// in om/BaseBook.php
|
||||
/**
|
||||
* Set the value of [title] column.
|
||||
*
|
||||
* @param string $v new value
|
||||
* @return Table4 The current object (for fluent API support)
|
||||
*/
|
||||
public function setTitle($v)
|
||||
{
|
||||
if ($v !== null)
|
||||
{
|
||||
$v = (string) $v;
|
||||
}
|
||||
|
||||
if ($this->title !== $v)
|
||||
{
|
||||
$this->title = $v;
|
||||
$this->modifiedColumns[] = BookPeer::TITLE;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// instead of
|
||||
|
||||
/**
|
||||
* Set the value of [title] column.
|
||||
*
|
||||
* @param string $v new value
|
||||
* @return Table4 The current object (for fluent API support)
|
||||
*/
|
||||
public function setTitle($v)
|
||||
{
|
||||
if ($v !== null) {
|
||||
$v = (string) $v;
|
||||
}
|
||||
|
||||
if ($this->title !== $v) {
|
||||
$this->title = $v;
|
||||
$this->modifiedColumns[] = BookPeer::TITLE;
|
||||
}
|
||||
|
||||
return $this;
|
||||
} // setTitle()
|
||||
}}}
|
||||
|
||||
The behavior replaces tabulations by whitespace (2 spaces by default), places opening brackets on newlines, removes closing brackets comments, and can even strip every comments in the generated classes if you wish.
|
||||
|
||||
== Parameters ==
|
||||
|
||||
Each of the new coding style rules has corresponding parameter in the behavior description. Here is the default configuration:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="alternative_coding_standards">
|
||||
<parameter name="brackets_newline" value="true" />
|
||||
<parameter name="remove_closing_comments" value="true" />
|
||||
<parameter name="use_whitespace" value="true" />
|
||||
<parameter name="tab_size" value="2" />
|
||||
<parameter name="strip_comments" value="false" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
You can change these settings to better match your own coding styles.
|
73
library/propel/docs/behavior/auto_add_pk.txt
Normal file
73
library/propel/docs/behavior/auto_add_pk.txt
Normal file
|
@ -0,0 +1,73 @@
|
|||
= AutoAddPk Behavior =
|
||||
|
||||
The `auto_add_pk` behavior adds a primary key columns to the tables that don't have one. Using this behavior allows you to omit the declaration of primary keys in your tables.
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `auto_add_pk` behavior to a table:
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="auto_add_pk" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Rebuild your model, and insert the table creation sql. You will notice that the `book` table has two columns and not just one. The behavior added an `id` column, of type integer and autoincremented. This column can be used as any other column:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$b = new Book();
|
||||
$b->setTitle('War And Peace');
|
||||
$b->save();
|
||||
echo $b->getId(); // 1
|
||||
}}}
|
||||
|
||||
This behavior is more powerful if you add it to the database instead of a table. That way, it will alter all tables not defining a primary key column - and leave the others unchanged.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="bookstore" defaultIdMethod="native">
|
||||
<behavior name="auto_add_pk" />
|
||||
<table name="book">
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
You can even enable it for all your database by adding it to the default behaviors in your `build.properties` file:
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
propel.behavior.default = auto_add_pk
|
||||
}}}
|
||||
|
||||
== Parameters ==
|
||||
|
||||
By default, the behavior adds a column named `id` to the table if the table has no primary key. You can customize all the attributes of the added column by setting corresponding parameters in the behavior definition:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="bookstore" defaultIdMethod="native">
|
||||
<behavior name="auto_add_pk">
|
||||
<parameter name="name" value="identifier" />
|
||||
<parameter name="autoIncrement" value="false" />
|
||||
</behavior>
|
||||
<table name="book">
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
Once you regenerate your model, the column is now named differently:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$b = new Book();
|
||||
$b->setTitle('War And Peace');
|
||||
$b->setIdentifier(1);
|
||||
$b->save();
|
||||
echo $b->getIdentifier(); // 1
|
||||
}}}
|
370
library/propel/docs/behavior/nested_set.txt
Normal file
370
library/propel/docs/behavior/nested_set.txt
Normal file
|
@ -0,0 +1,370 @@
|
|||
= NestedSet Behavior =
|
||||
|
||||
The `nested_set` behavior allows a model to become a tree structure, and provides numerous methods to traverse the tree in an efficient way.
|
||||
|
||||
Many applications need to store hierarchical data in the model. For instance, a forum stores a tree of messages for each discussion. A CMS sees sections and subsections as a navigation tree. In a business organization chart, each person is a leaf of the organization tree. [http://en.wikipedia.org/wiki/Nested_set_model Nested sets] are the best way to store such hierachical data in a relational database and manipulate it. The name "nested sets" describes the algorithm used to store the position of a model in the tree ; it is also known as "modified preorder tree traversal".
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `nested_set` behavior to a table:
|
||||
{{{
|
||||
#!xml
|
||||
<table name="section">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="nested_set" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has the ability to be inserted into a tree structure, as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$s1 = new Section();
|
||||
$s1->setTitle('Home');
|
||||
$s1->makeRoot(); // make this node the root of the tree
|
||||
$s1->save();
|
||||
$s2 = new Section();
|
||||
$s2->setTitle('World');
|
||||
$s2->insertAsFirstChildOf($s1); // insert the node in the tree
|
||||
$s2->save();
|
||||
$s3 = new Section();
|
||||
$s3->setTitle('Europe');
|
||||
$s3->insertAsFirstChildOf($s2); // insert the node in the tree
|
||||
$s3->save()
|
||||
$s4 = new Section();
|
||||
$s4->setTitle('Business');
|
||||
$s4->insertAsNextSiblingOf($s2); // insert the node in the tree
|
||||
$s4->save();
|
||||
/* The sections are now stored in the database as a tree:
|
||||
$s1:Home
|
||||
| \
|
||||
$s2:World $s4:Business
|
||||
|
|
||||
$s3:Europe
|
||||
*/
|
||||
}}}
|
||||
|
||||
You can continue to insert new nodes as children or siblings of existing nodes, using any of the `insertAsFirstChildOf()`, `insertAsLastChildOf()`, `insertAsPrevSiblingOf()`, and `insertAsNextSiblingOf()` methods.
|
||||
|
||||
Once you have built a tree, you can traverse it using any of the numerous methods the `nested_set` behavior adds to the query and model objects. For instance:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$rootNode = SectionQuery::create()->findRoot(); // $s1
|
||||
$worldNode = $rootNode->getFirstChild(); // $s2
|
||||
$businessNode = $worldNode->getNextSibling(); // $s4
|
||||
$firstLevelSections = $rootNode->getChildren(); // array($s2, $s4)
|
||||
$allSections = $rootNode->getDescendants(); // array($s2, $s3, $s4)
|
||||
// you can also chain the methods
|
||||
$europeNode = $rootNode->getLastChild()->getPrevSibling()->getFirstChild(); // $s3
|
||||
$path = $europeNode->getAncestors(); // array($s1, $s2)
|
||||
}}}
|
||||
|
||||
The nodes returned by these methods are regular Propel model objects, with access to the properties and related models. The `nested_set` behavior also adds inspection methods to nodes:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
echo $s2->isRoot(); // false
|
||||
echo $s2->isLeaf(); // false
|
||||
echo $s2->getLevel(); // 1
|
||||
echo $s2->hasChildren(); // true
|
||||
echo $s2->countChildren(); // 1
|
||||
echo $s2->hasSiblings(); // true
|
||||
}}}
|
||||
|
||||
Each of the traversal and inspection methods result in a single database query, whatever the position of the node in the tree. This is because the information about the node position in the tree is stored in three columns of the model, named `tree_left`, `tree_left`, and `tree_level`. The value given to these columns is determined by the nested set algorithm, and it makes read queries much more effective than trees using a simple `parent_id` foreign key.
|
||||
|
||||
== Manipulating Nodes ==
|
||||
|
||||
You can move a node - and its subtree - across the tree using any of the `moveToFirstChildOf()`, `moveToLastChildOf()`, `moveToPrevSiblingOf()`, and `moveToLastSiblingOf()` methods. These operations are immediate and don't require that you save the model afterwards:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// move the entire "World" section under "Business"
|
||||
$s2->moveToFirstChildOf($s4);
|
||||
/* The tree is modified as follows:
|
||||
$s1:Home
|
||||
|
|
||||
$s4:Business
|
||||
|
|
||||
$s2:World
|
||||
|
|
||||
$s3:Europe
|
||||
*/
|
||||
// now move the "Europe" section directly under root, after "Business"
|
||||
$s2->moveToFirstChildOf($s4);
|
||||
/* The tree is modified as follows:
|
||||
$s1:Home
|
||||
| \
|
||||
$s4:Business $s3:Europe
|
||||
|
|
||||
$s2:World
|
||||
*/
|
||||
}}}
|
||||
|
||||
You can delete the descendants of a node using `deleteDescendants()`:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// move the entire "World" section under "Business"
|
||||
$s4->deleteDescendants($s4);
|
||||
/* The tree is modified as follows:
|
||||
$s1:Home
|
||||
| \
|
||||
$s4:Business $s3:Europe
|
||||
*/
|
||||
}}}
|
||||
|
||||
If you `delete()` a node, all its descendants are deleted in cascade. To avoid accidental deletion of an entire tree, calling `delete()` on a root node throws an exception. Use the `delete()` Query method instead to delete an entire tree.
|
||||
|
||||
== Filtering Results ==
|
||||
|
||||
The `nested_set` behavior adds numerous methods to the generated Query object. You can use these methods to build more complex queries. For instance, to get all the children of the root node ordered by title, build a Query as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$children = SectionQuery::create()
|
||||
->childrenOf($rootNode)
|
||||
->orderByTitle()
|
||||
->find();
|
||||
}}}
|
||||
|
||||
Alternatively, if you already have an existing query method, you can pass it to the model object's methods to filter the results:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$orderQuery = SectionQuery::create()->orderByTitle();
|
||||
$children = $rootNode->getChildren($orderQuery);
|
||||
}}}
|
||||
|
||||
== Multiple Trees ==
|
||||
|
||||
When you need to store several trees for a single model - for instance, several threads of posts in a forum - use a ''scope'' for each tree. This requires that you enable scope tree support in the behavior definition by setting the `use_scope` parameter to `true`:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="post">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="body" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="nested_set">
|
||||
<parameter name="use_scope" value="true" />
|
||||
<parameter name="scope_column" value="thread_id" />
|
||||
</behavior>
|
||||
<foreign-key foreignTable="thread" onDelete="cascade">
|
||||
<reference local="thread_id" foreign="id" />
|
||||
</foreign-key>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Now, after rebuilding your model, you can have as many trees as required:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$thread = ThreadQuery::create()->findPk(123);
|
||||
$firstPost = PostQuery::create()->findRoot($thread->getId()); // first message of the discussion
|
||||
$discussion = PostQuery::create()->findTree(thread->getId()); // all messages of the discussion
|
||||
PostQuery::create()->inTree($thread->getId())->delete(); // delete an entire discussion
|
||||
$firstPostOfEveryDiscussion = PostQuery::create()->findRoots();
|
||||
}}}
|
||||
|
||||
== Using a RecursiveIterator ==
|
||||
|
||||
An alternative way to browse a tree structure extensively is to use a [http://php.net/RecursiveIterator RecursiveIterator]. The `nested_set` behavior provides an easy way to retrieve such an iterator from a node, and to parse the entire branch in a single iteration.
|
||||
|
||||
For instance, to display an entire tree structure, you can use the following code:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$root = SectionQuery::create()->findRoot();
|
||||
foreach ($root->getIterator() as $node) {
|
||||
echo str_repeat(' ', $node->getLevel()) . $node->getTitle() . "\n";
|
||||
}
|
||||
}}}
|
||||
|
||||
The iterator parses the tree in a recursive way by retrieving the children of every node. This can be quite effective on very large trees, since the iterator hydrates only a few objects at a time.
|
||||
|
||||
Beware, though, that the iterator executes many queries to parse a tree. On smaller trees, prefer the `getBranch()` method to execute only one query, and hydrate all records at once:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$root = SectionQuery::create()->findRoot();
|
||||
foreach ($root->getBranch() as $node) {
|
||||
echo str_repeat(' ', $node->getLevel()) . $node->getTitle() . "\n";
|
||||
}
|
||||
}}}
|
||||
|
||||
== Parameters ==
|
||||
|
||||
By default, the behavior adds three columns to the model - four if you use the scope feature. You can use custom names for the nested sets columns. The following schema illustrates a complete customization of the behavior:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="post">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="lft" type="INTEGER" />
|
||||
<column name="rgt" type="INTEGER" />
|
||||
<column name="lvl" type="INTEGER" />
|
||||
<column name="thread_id" type="INTEGER" />
|
||||
<column name="body" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="nested_set">
|
||||
<parameter name="left_column" value="lft" />
|
||||
<parameter name="right_column" value="rgt" />
|
||||
<parameter name="level_column" value="lvl" />
|
||||
<parameter name="use_scope" value="true" />
|
||||
<parameter name="scope_column" value="thread_id" />
|
||||
</behavior>
|
||||
<foreign-key foreignTable="thread" onDelete="cascade">
|
||||
<reference local="thread_id" foreign="id" />
|
||||
</foreign-key>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Whatever name you give to your columns, the `nested_sets` behavior always adds the following proxy methods, which are mapped to the correct column:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$post->getLeftValue(); // returns $post->lft
|
||||
$post->setLeftValue($left);
|
||||
$post->getRightValue(); // returns $post->rgt
|
||||
$post->setRightValue($right);
|
||||
$post->getLevel(); // returns $post->lvl
|
||||
$post->setLevel($level);
|
||||
$post->getScopeValue(); // returns $post->thread_id
|
||||
$post->setScopeValue($scope);
|
||||
}}}
|
||||
|
||||
If your application used the old nested sets builder from Propel 1.4, you can enable the `method_proxies` parameter so that the behavior generates method proxies for the methods that used a different name (e.g. `createRoot()` for `makeRoot()`, `retrieveFirstChild()` for `getFirstChild()`, etc.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="section">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="nested_set">
|
||||
<parameter name="method_proxies" value="true" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
== Complete API ==
|
||||
|
||||
Here is a list of the methods added by the behavior to the model objects:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// storage columns accessors
|
||||
int getLeftValue()
|
||||
$node setLeftValue(int $left)
|
||||
int getRightValue()
|
||||
$node setRightValue(int $right)
|
||||
int getLevel()
|
||||
$node setLevel(int $level)
|
||||
// only for behavior with use_scope
|
||||
int getScopeValue()
|
||||
$node setScopeValue(int $scope)
|
||||
|
||||
// root maker (requires calling save() afterwards)
|
||||
$node makeRoot()
|
||||
|
||||
// inspection methods
|
||||
bool isInTree()
|
||||
bool isRoot()
|
||||
bool isLeaf()
|
||||
bool isDescendantOf()
|
||||
bool isAncestorOf()
|
||||
bool hasParent()
|
||||
bool hasPrevSibling()
|
||||
bool hasNextSibling()
|
||||
bool hasChildren()
|
||||
int countChildren()
|
||||
int countDescendants()
|
||||
|
||||
// tree traversal methods
|
||||
$node getParent()
|
||||
$node getPrevSibling()
|
||||
$node getNextSibling()
|
||||
array getChildren()
|
||||
$node getFirstChild()
|
||||
$node getLastChild()
|
||||
array getSiblings($includeCurrent = false, Criteria $c = null)
|
||||
array getDescendants(Criteria $c = null)
|
||||
array getBranch(Criteria $c = null)
|
||||
array getAncestors(Criteria $c = null)
|
||||
|
||||
// node insertion methods (require calling save() afterwards)
|
||||
$node addChild($node)
|
||||
$node insertAsFirstChildOf($node)
|
||||
$node insertAsLastChildOf($node)
|
||||
$node insertAsPrevSiblingOf($node)
|
||||
$node insertAsNextSiblingOf($node)
|
||||
|
||||
// node move methods (immediate, no need to save() afterwards)
|
||||
$node moveToFirstChildOf($node)
|
||||
$node moveToLastChildOf($node)
|
||||
$node moveToPrevSiblingOf($node)
|
||||
$node moveToNextSiblingOf($node)
|
||||
|
||||
// deletion methods
|
||||
$node deleteDescendants()
|
||||
|
||||
// only for behavior with method_proxies
|
||||
$node createRoot()
|
||||
$node retrieveParent()
|
||||
$node retrievePrevSibling()
|
||||
$node retrieveNextSibling()
|
||||
$node retrieveFirstChild()
|
||||
$node retrieveLastChild()
|
||||
array getPath()
|
||||
}}}
|
||||
|
||||
The behavior also adds some methods to the Query classes:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// tree filter methods
|
||||
query descendantsOf($node)
|
||||
query branchOf($node)
|
||||
query childrenOf($node)
|
||||
query siblingsOf($node)
|
||||
query ancestorsOf($node)
|
||||
query rootsOf($node)
|
||||
// only for behavior with use_scope
|
||||
query treeRoots()
|
||||
query inTree($scope = null)
|
||||
// order methods
|
||||
query orderByBranch($reverse = false)
|
||||
query orderByLevel($reverse = false)
|
||||
// termination methods
|
||||
$node findRoot($scope = null)
|
||||
coll findTree($scope = null)
|
||||
}}}
|
||||
|
||||
Lastly, the behavior adds a few methods to the Peer classes:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$node retrieveRoot($scope = null)
|
||||
array retrieveTree($scope = null)
|
||||
int deleteTree($scope = null)
|
||||
// only for behavior with use_scope
|
||||
array retrieveRoots(Criteria $c = null)
|
||||
}}}
|
||||
|
||||
== TODO ==
|
||||
|
||||
* InsertAsParentOf
|
103
library/propel/docs/behavior/query_cache.txt
Normal file
103
library/propel/docs/behavior/query_cache.txt
Normal file
|
@ -0,0 +1,103 @@
|
|||
= Query Cache Behavior =
|
||||
|
||||
The `query_cache` behavior gives a speed boost to Propel queries by caching the transformation of a PHP Query object into reusable SQL code.
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `query_cache` behavior to a table:
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="query_cache" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
After you rebuild your model, all the queries on this object can now be cached. To trigger the query cache on a particular query, just give it a query key using the `setQueryKey()` method. The key is a unique identifier that you can choose, later used for cache lookups:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$title = 'War And Peace';
|
||||
$books = BookQuery::create()
|
||||
->setQueryKey('search book by title')
|
||||
->filterByTitle($title)
|
||||
->findOne();
|
||||
}}}
|
||||
|
||||
The first time Propel executes the termination method, it computes the SQL translation of the Query object and stores it into a cache backend (APC by default). Next time you run the same query, it executes faster, even with different parameters:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$title = 'Anna Karenina';
|
||||
$books = BookQuery::create()
|
||||
->setQueryKey('search book by title')
|
||||
->filterByTitle($title)
|
||||
->findOne();
|
||||
}}}
|
||||
|
||||
'''Tip''': The more complex the query, the greater the boost you get from the query cache behavior.
|
||||
|
||||
== Parameters ==
|
||||
|
||||
You can change the cache backend and the cache lifetime (in seconds) by setting the `backend` and `lifetime` parameters:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="query_cache">
|
||||
<parameter name="backend" value="custom" />
|
||||
<parameter name="lifetime" value="600" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
To implement a custom cache backend, just override the generated `cacheContains()`, `cacheFetch()` and `cacheStore()` methods in the Query object. For instance, to implement query cache using Zend_Cache and memcached, try the following:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class BookQuery extends BaseBookQuery
|
||||
{
|
||||
public function cacheContains($key)
|
||||
{
|
||||
return $this->getCacheBackend()->test($key);
|
||||
}
|
||||
|
||||
public function cacheFetch($key)
|
||||
{
|
||||
return $this->getCacheBackend()->load($key);
|
||||
}
|
||||
|
||||
public function cacheStore($key, $value)
|
||||
{
|
||||
return $this->getCacheBackend()->save($key, $value);
|
||||
}
|
||||
|
||||
protected function getCacheBackend()
|
||||
{
|
||||
if (self::$cacheBackend === null) {
|
||||
$frontendOptions = array(
|
||||
'lifetime' => 7200,
|
||||
'automatic_serialization' => true
|
||||
);
|
||||
$backendOptions = array(
|
||||
'servers' => array(
|
||||
array(
|
||||
'host' => 'localhost',
|
||||
'port' => 11211,
|
||||
'persistent' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
self::$cacheBackend = Zend_Cache::factory('Core', 'Memcached', $frontendOptions, $backendOptions);
|
||||
}
|
||||
|
||||
return self::$cacheBackend;
|
||||
}
|
||||
}
|
||||
}}}
|
135
library/propel/docs/behavior/sluggable.txt
Executable file
135
library/propel/docs/behavior/sluggable.txt
Executable file
|
@ -0,0 +1,135 @@
|
|||
= Sluggable Behavior =
|
||||
|
||||
The `sluggable` behavior allows a model to offer a human readable identifier that can be used for search engine friendly URLs.
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `sluggable` behavior to a table:
|
||||
{{{
|
||||
#!xml
|
||||
<table name="post">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="sluggable" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has an additional getter for its slug, which is automatically set before the object is saved:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$p1 = new Post();
|
||||
$p1->setTitle('Hello, World!');
|
||||
$p1->save();
|
||||
echo $p1->getSlug(); // 'hello-world'
|
||||
}}}
|
||||
|
||||
By default, the behavior uses the string representation of the object to build the slug. In the example above, the `title` column is defined as `primaryString`, so the slug uses this column as a base string. The string is then cleaned up in order to allow it to appear in a URL. In the process, blanks and special characters are replaced by a dash, and the string is lowercased.
|
||||
|
||||
'''Tip''': The slug is unique by design. That means that if you create a new object and that the behavior calculates a slug that already exists, the string is modified to be unique:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$p2 = new Post();
|
||||
$p2->setTitle('Hello, World!');
|
||||
$p2->save();
|
||||
echo $p2->getSlug(); // 'hello-world-1'
|
||||
}}}
|
||||
|
||||
The generated model query offers a `findOneBySlug()` method to easily retrieve a model object based on its slug:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$p = PostQuery::create()->findOneBySlug('hello-world');
|
||||
}}}
|
||||
|
||||
== Parameters ==
|
||||
|
||||
By default, the behavior adds one columns to the model. If this column is already described in the schema, the behavior detects it and doesn't add it a second time. The behavior parameters allow you to use custom patterns for the slug composition. The following schema illustrates a complete customization of the behavior:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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="url" type="VARCHAR" size="100" />
|
||||
<behavior name="sluggable" />
|
||||
<parameter name="slug_column" value="url" />
|
||||
<parameter name="slug_pattern" value="/posts/{Title}" />
|
||||
<parameter name="replace_pattern" value="/[^\w\/]+/u" />
|
||||
<parameter name="replacement" value="-" />
|
||||
<parameter name="separator" value="/" />
|
||||
<parameter name="permanent" value="true" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Whatever `slug_column` name you choose, the `sluggable` behavior always adds the following proxy methods, which are mapped to the correct column:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$post->getSlug(); // returns $post->url
|
||||
$post->setSlug($slug); // $post->url = $slug
|
||||
}}}
|
||||
|
||||
The `slug_pattern` parameter is the rule used to build the raw slug based on the object properties. Any substring enclosed between brackets '{}' is turned into a getter, so the `Post` class generates slugs as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
protected function createRawSlug()
|
||||
{
|
||||
return '/posts/' . $this->getTitle();
|
||||
}
|
||||
}}}
|
||||
|
||||
Incidentally, that means that you can use names that don't match a real column phpName, as long as your model provides a getter for it.
|
||||
|
||||
The `replace_pattern` parameter is a regular expression that shows all the characters that will end up replaced by the `replacement` parameter. In the above example, special characters like '!' or ':' are replaced by '-', but not letters, digits, nor '/'.
|
||||
|
||||
The `separator` parameter is the character that separates the slug from the incremental index added in case of non-unicity. Set as '/', it makes `Post` objects sharing the same title have the following slugs:
|
||||
|
||||
{{{
|
||||
'posts/hello-world'
|
||||
'posts/hello-world/1'
|
||||
'posts/hello-world/2'
|
||||
...
|
||||
}}}
|
||||
|
||||
A `permanent` slug is not automatically updated when the fields that constitute it change. This is useful when the slug serves as a permalink, that should work even when the model object properties change. Note that you can still manually change the slug in a model using the `permanent` setting by calling `setSlug()`;
|
||||
|
||||
== Further Customization ==
|
||||
|
||||
The slug is generated by the object when it is saved, via the `createSlug()` method. This method does several operations on a simple string:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
protected function createSlug()
|
||||
{
|
||||
// create the slug based on the `slug_pattern` and the object properties
|
||||
$slug = $this->createRawSlug();
|
||||
// truncate the slug to accomodate the size of the slug column
|
||||
$slug = $this->limitSlugSize($slug);
|
||||
// add an incremental index to make sure the slug is unique
|
||||
$slug = $this->makeSlugUnique($slug);
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
protected function createRawSlug()
|
||||
{
|
||||
// here comes the string composition code, generated according to `slug_pattern`
|
||||
$slug = 'posts/' . $this->cleanupSlugPart($this->getTitle());
|
||||
// cleanupSlugPart() cleans up the slug part
|
||||
// based on the `replace_pattern` and `replacement` parameters
|
||||
|
||||
return $slug;
|
||||
}
|
||||
}}}
|
||||
|
||||
You can override any of these methods in your model class, in order to implement a custom slug logic.
|
116
library/propel/docs/behavior/soft_delete.txt
Normal file
116
library/propel/docs/behavior/soft_delete.txt
Normal file
|
@ -0,0 +1,116 @@
|
|||
= SoftDelete Behavior =
|
||||
|
||||
The `soft_delete` behavior overrides the deletion methods of a model object to make them 'hide' the deleted rows but keep them in the database. Deleted objects still don't show up on select queries, but they can be retrieved or undeleted when necessary.
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `soft_delete` behavior to a table:
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="soft_delete" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has one new column, `deleted_at`, that stores the deletion date. Select queries don't return the deleted objects:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$b = new Book();
|
||||
$b->setTitle('War And Peace');
|
||||
$b->save();
|
||||
$b->delete();
|
||||
echo $b->isDeleted(); // false
|
||||
echo $b->getDeletedAt(); // 2009-10-02 18:14:23
|
||||
$books = BookQuery::create()->find(); // empty collection
|
||||
}}}
|
||||
|
||||
Behind the curtain, the behavior adds a condition to every SELECT query to return only records where the `deleted_at` column is null. That's why the deleted objects don't appear anymore upon selection.
|
||||
|
||||
You can turn off the query alteration globally by calling the static method `disableSoftDelete()` on the related Query object:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
BookQuery::disableSoftDelete();
|
||||
$books = BookQuery::create()->find();
|
||||
$book = $books[0];
|
||||
echo $book->getTitle(); // 'War And Peace'
|
||||
}}}
|
||||
|
||||
Note that `find()` and other selection methods automatically re-enable the `soft_delete` filter. You can also enable it manually by calling the `enableSoftDelete()` method on Peer objects.
|
||||
|
||||
If you want to recover a deleted object, use the `unDelete()` method:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book->unDelete();
|
||||
$books = BookQuery::create()->find();
|
||||
$book = $books[0];
|
||||
echo $book->getTitle(); // 'War And Peace'
|
||||
}}}
|
||||
|
||||
If you want to force the real deletion of an object, call the `forceDelete()` method:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book->forceDelete();
|
||||
echo $book->isDeleted(); // true
|
||||
$books = BookQuery::create()->find(); // empty collection
|
||||
}}}
|
||||
|
||||
The query methods `delete()` and `deleteAll()` also perform a soft deletion, unless you disable the behavior on the peer class:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$b = new Book();
|
||||
$b->setTitle('War And Peace');
|
||||
$b->save();
|
||||
|
||||
BookQuery::create()->delete($b);
|
||||
$books = BookQuery::create()->find(); // empty collection
|
||||
// the rows look deleted, but they are still there
|
||||
BookQuery::disableSoftDelete();
|
||||
$books = BookQuery::create()->find();
|
||||
$book = $books[0];
|
||||
echo $book->getTitle(); // 'War And Peace'
|
||||
|
||||
// To perform a true deletion, disable the softDelete feature
|
||||
BookQuery::disableSoftDelete();
|
||||
BookQuery::create()->delete();
|
||||
// Alternatively, use forceDelete()
|
||||
BookQuery::create()->forceDelete();
|
||||
}}}
|
||||
|
||||
== Parameters ==
|
||||
|
||||
You can change the name of the column added by the behavior by setting the `deleted_column` parameter:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<column name="my_deletion_date" type="TIMESTAMP" />
|
||||
<behavior name="soft_delete">
|
||||
<parameter name="deleted_column" value="my_deletion_date" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$b = new Book();
|
||||
$b->setTitle('War And Peace');
|
||||
$b->save();
|
||||
$b->delete();
|
||||
echo $b->getMyDeletionDate(); // 2009-10-02 18:14:23
|
||||
$books = BookQuery::create()->find(); // empty collection
|
||||
}}}
|
285
library/propel/docs/behavior/sortable.txt
Normal file
285
library/propel/docs/behavior/sortable.txt
Normal file
|
@ -0,0 +1,285 @@
|
|||
= Sortable Behavior =
|
||||
|
||||
The `sortable` behavior allows a model to become an ordered list, and provides numerous methods to traverse this list in an efficient way.
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `sortable` behavior to a table:
|
||||
{{{
|
||||
#!xml
|
||||
<table name="task">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="sortable" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has the ability to be inserted into an ordered list, as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$t1 = new Task();
|
||||
$t1->setTitle('Wash the dishes');
|
||||
$t1->save();
|
||||
echo $t1->getRank(); // 1, the first rank to be given (not 0)
|
||||
$t2 = new Task();
|
||||
$t2->setTitle('Do the laundry');
|
||||
$t2->save();
|
||||
echo $t2->getRank(); // 2
|
||||
$t3 = new Task();
|
||||
$t3->setTitle('Rest a little');
|
||||
$t3->save()
|
||||
echo $t3->getRank(); // 3
|
||||
}}}
|
||||
|
||||
As long as you save new objects, Propel gives them the first available rank in the list.
|
||||
|
||||
Once you have built an ordered list, you can traverse it using any of the methods added by the `sortable` behavior. For instance:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$firstTask = TaskQuery::create()->findOneByRank(1); // $t1
|
||||
$secondTask = $firstTask->getNext(); // $t2
|
||||
$lastTask = $secondTask->getNext(); // $t3
|
||||
$secondTask = $lastTask->getPrevious(); // $t2
|
||||
|
||||
$allTasks = TaskQuery::create()->findList();
|
||||
// => collection($t1, $t2, $t3)
|
||||
$allTasksInReverseOrder = TaskQuery::create()->orderByRank('desc')->find();
|
||||
// => collection($t3, $t2, $t2)
|
||||
}}}
|
||||
|
||||
The results returned by these methods are regular Propel model objects, with access to the properties and related models. The `sortable` behavior also adds inspection methods to objects:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
echo $t2->isFirst(); // false
|
||||
echo $t2->isLast(); // false
|
||||
echo $t2->getRank(); // 2
|
||||
}}}
|
||||
|
||||
== Manipulating Objects In A List ==
|
||||
|
||||
You can move an object in the list using any of the `moveUp()`, `moveDown()`, `moveToTop()`, `moveToBottom()`, `moveToRank()`, and `swapWith()` methods. These operations are immediate and don't require that you save the model afterwards:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// The list is 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
|
||||
$t2->moveToTop();
|
||||
// The list is now 1 - Do the laundry, 2 - Wash the dishes, 3 - Rest a little
|
||||
$t2->moveToBottom();
|
||||
// The list is now 1 - Wash the dishes, 2 - Rest a little, 3 - Do the laundry
|
||||
$t2->moveUp();
|
||||
// The list is 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
|
||||
$t2->swapWith($t1);
|
||||
// The list is now 1 - Do the laundry, 2 - Wash the dishes, 3 - Rest a little
|
||||
$t2->moveToRank(3);
|
||||
// The list is now 1 - Wash the dishes, 2 - Rest a little, 3 - Do the laundry
|
||||
$t2->moveToRank(2);
|
||||
}}}
|
||||
|
||||
By default, new objects are added at the bottom of the list. But you can also insert them at a specific position, using any of the `insertAtTop(), `insertAtBottom()`, and `insertAtRank()` methods. Note that the `insertAtXXX` methods don't save the object:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// The list is 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
|
||||
$t4 = new Task();
|
||||
$t4->setTitle('Clean windows');
|
||||
$t4->insertAtRank(2);
|
||||
$t4->save();
|
||||
// The list is now 1 - Wash the dishes, 2 - Clean Windows, 3 - Do the laundry, 4 - Rest a little
|
||||
}}}
|
||||
|
||||
Whenever you `delete()` an object, the ranks are rearranged to fill the gap:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$t4->delete();
|
||||
// The list is now 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little
|
||||
}}}
|
||||
|
||||
'''Tip''': You can remove an object from the list without necessarily deleting it by calling `removeFromList()`. Don't forget to `save()` it afterwards so that the other objects in the lists are rearranged to fill the gap.
|
||||
|
||||
== Multiple Lists ==
|
||||
|
||||
When you need to store several lists for a single model - for instance, one task list for each user - use a ''scope'' for each list. This requires that you enable scope support in the behavior definition by setting the `use_scope` parameter to `true`:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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>
|
||||
}}}
|
||||
|
||||
Now, after rebuilding your model, you can have as many lists as required:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// test users
|
||||
$paul = new User();
|
||||
$john = new User();
|
||||
// now onto 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
|
||||
}}}
|
||||
|
||||
The generated methods now accept a `$scope` parameter to restrict the query to a given scope:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$firstPaulTask = TaskQuery::create()->findOneByRank($rank = 1, $scope = $paul->getId()); // $t1
|
||||
$lastPaulTask = $firstTask->getNext(); // $t2
|
||||
$firstJohnTask = TaskPeer::create()->findOneByRank($rank = 1, $scope = $john->getId()); // $t1
|
||||
}}}
|
||||
|
||||
Models using the sortable behavior with scope benefit from one additional Query methods named `inList()`:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$allPaulsTasks = TaskPeer::create()->inList($scope = $paul->getId())->find();
|
||||
}}}
|
||||
|
||||
== Parameters ==
|
||||
|
||||
By default, the behavior adds one columns to the model - two if you use the scope feature. If these columns are already described in the schema, the behavior detects it and doesn't add them a second time. The behavior parameters allow you to use custom names for the sortable columns. The following schema illustrates a complete customization of the behavior:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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="my_rank_column" required="true" type="INTEGER" />
|
||||
<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="rank_column" value="my_rank_column" />
|
||||
<parameter name="use_scope" value="true" />
|
||||
<parameter name="scope_column" value="user_id" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Whatever name you give to your columns, the `sortable` behavior always adds the following proxy methods, which are mapped to the correct column:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$task->getRank(); // returns $task->my_rank_column
|
||||
$task->setRank($rank);
|
||||
$task->getScopeValue(); // returns $task->user_id
|
||||
$task->setScopeValue($scope);
|
||||
}}}
|
||||
|
||||
The same happens for the generated Query object:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$query = TaskQuery::create()->filterByRank(); // proxies to filterByMyRankColumn()
|
||||
$query = TaskQuery::create()->orderByRank(); // proxies to orderByMyRankColumn()
|
||||
$tasks = TaskQuery::create()->findOneByRank(); // proxies to findOneByMyRankColumn()
|
||||
}}}
|
||||
|
||||
'''Tip''': The behavior adds columns but no index. Depending on your table structure, you might want to add a column index by hand to speed up queries on sorted lists.
|
||||
|
||||
== Complete API ==
|
||||
|
||||
Here is a list of the methods added by the behavior to the model objects:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// storage columns accessors
|
||||
int getRank()
|
||||
$object setRank(int $rank)
|
||||
// only for behavior with use_scope
|
||||
int getScopeValue()
|
||||
$object setScopeValue(int $scope)
|
||||
|
||||
// inspection methods
|
||||
bool isFirst()
|
||||
bool isLast()
|
||||
|
||||
// list traversal methods
|
||||
$object getNext()
|
||||
$object getPrevious()
|
||||
|
||||
// methods to insert an object in the list (require calling save() afterwards)
|
||||
$object insertAtRank($rank)
|
||||
$object insertAtBottom()
|
||||
$object insertAtTop()
|
||||
|
||||
// methods to move an object in the list (immediate, no need to save() afterwards)
|
||||
$object moveToRank($rank)
|
||||
$object moveUp()
|
||||
$object moveDown()
|
||||
$object moveToTop()
|
||||
$object moveToBottom()
|
||||
$object swapWith($object)
|
||||
|
||||
// method to remove an object from the list (requires calling save() afterwards)
|
||||
$object removeFromList()
|
||||
}}}
|
||||
|
||||
Here is a list of the methods added by the behavior to the query objects:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
query filterByRank($order, $scope = null)
|
||||
query orderByRank($order, $scope = null)
|
||||
$object findOneByRank($rank, $scope = null)
|
||||
coll findList($scope = null)
|
||||
int getMaxRank($scope = null)
|
||||
bool reorder($newOrder) // $newOrder is a $id => $rank associative array
|
||||
// only for behavior with use_scope
|
||||
array inList($scope)
|
||||
}}}
|
||||
|
||||
The behavior also adds a few methods to the Peer classes:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
int getMaxRank($scope = null)
|
||||
$object retrieveByRank($rank, $scope = null)
|
||||
array doSelectOrderByRank($order, $scope = null)
|
||||
bool reorder($newOrder) // $newOrder is a $id => $rank associative array
|
||||
// only for behavior with use_scope
|
||||
array retrieveList($scope)
|
||||
int countList($scope)
|
||||
int deleteList($scope)
|
||||
}}}
|
92
library/propel/docs/behavior/timestampable.txt
Normal file
92
library/propel/docs/behavior/timestampable.txt
Normal file
|
@ -0,0 +1,92 @@
|
|||
= Timestampable Behavior =
|
||||
|
||||
The `timestampable` behavior allows you to keep track of the date of creation and last update of your model objects.
|
||||
|
||||
== Basic Usage ==
|
||||
|
||||
In the `schema.xml`, use the `<behavior>` tag to add the `timestampable` behavior to a table:
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<behavior name="timestampable" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has two new columns, `created_at` and `updated_at`, that store a timestamp automatically updated on save:
|
||||
|
||||
{{{
|
||||
#!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
|
||||
}}}
|
||||
|
||||
The object query also has specific methods to retrieve recent objects and order them according to their update date:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$books = BookQuery::create()
|
||||
->recentlyUpdated() // adds a minimum value for the update date
|
||||
->lastUpdatedFirst() // orders the results by descending update date
|
||||
->find();
|
||||
}}}
|
||||
|
||||
You can use any of the following methods in the object query:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// limits the query to recent objects
|
||||
ModelCriteria recentlyCreated($nbDays = 7)
|
||||
ModelCriteria recentlyUpdated($nbDays = 7)
|
||||
// orders the results
|
||||
ModelCriteria lastCreatedFirst() // order by creation date desc
|
||||
ModelCriteria firstCreatedFirst() // order by creation date asc
|
||||
ModelCriteria lastUpdatedFirst() // order by update date desc
|
||||
ModelCriteria firstUpdatedFirst() // order by update date asc
|
||||
}}}
|
||||
|
||||
'''Tip''': You may need to keep the update date unchanged after an update on the object, for instance when you only update a calculated row. In this case, call the `keepUpdateDateUnchanged()` method on the object before saving it.
|
||||
|
||||
|
||||
== Parameters ==
|
||||
|
||||
You can change the name of the columns added by the behavior by setting the `create_column` and `update_column` parameters:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<column name="my_create_date" type="TIMESTAMP" />
|
||||
<column name="my_update_date" type="TIMESTAMP" />
|
||||
<behavior name="timestampable">
|
||||
<parameter name="create_column" value="my_create_date" />
|
||||
<parameter name="update_column" value="my_update_date" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$b = new Book();
|
||||
$b->setTitle('War And Peace');
|
||||
$b->save();
|
||||
echo $b->getMyCreateDate(); // 2009-10-02 18:14:23
|
||||
echo $b->getMyUpdateDate(); // 2009-10-02 18:14:23
|
||||
$b->setTitle('Anna Karenina');
|
||||
$b->save();
|
||||
echo $b->getMyCreateDate(); // 2009-10-02 18:14:23
|
||||
echo $b->getMyUpdateDate(); // 2009-10-02 18:14:25
|
||||
}}}
|
34
library/propel/docs/build.xml
Executable file
34
library/propel/docs/build.xml
Executable file
|
@ -0,0 +1,34 @@
|
|||
<project name="propel-docs" default="phpdoc">
|
||||
|
||||
<property name="runtime.dir" value="../runtime" />
|
||||
<property name="generator.dir" value="../generator" />
|
||||
|
||||
<target name="phpdoc">
|
||||
<phingcall target="phpdoc-runtime"/>
|
||||
<phingcall target="phpdoc-generator"/>
|
||||
</target>
|
||||
|
||||
<target name="phpdoc-runtime" description="build runtime docs">
|
||||
<phpdoc title="Propel Runtime" destdir="api/runtime" sourcecode="yes" output="HTML:Smarty:PHP">
|
||||
<fileset dir="${runtime.dir}/lib">
|
||||
<include name="**/*.php" />
|
||||
</fileset>
|
||||
</phpdoc>
|
||||
</target>
|
||||
|
||||
<target name="phpdoc-generator" description="build generator docs">
|
||||
<phpdoc title="Propel Generator" destdir="api/generator" sourcecode="yes" output="HTML:Smarty:PHP">
|
||||
<fileset dir="${generator.dir}/lib">
|
||||
<include name="**/*.php" />
|
||||
</fileset>
|
||||
<!--
|
||||
<projdocfileset dir="${generator.dir}">
|
||||
<include name="README" />
|
||||
<include name="INSTALL" />
|
||||
<include name="CHANGELOG" />
|
||||
</projdocfileset>
|
||||
-->
|
||||
</phpdoc>
|
||||
</target>
|
||||
|
||||
</project>
|
34
library/propel/docs/cookbook/Add-Custom-SQL.txt
Normal file
34
library/propel/docs/cookbook/Add-Custom-SQL.txt
Normal file
|
@ -0,0 +1,34 @@
|
|||
= Adding Additional SQL Files =
|
||||
|
||||
In many cases you may wish to have the ''insert-sql'' task perform additional SQL operations (e.g. add views, stored procedures, triggers, sample data, etc.). Rather than have to run additional SQL statements yourself every time you re-build your object model, you can have the Propel generator do this for you.
|
||||
|
||||
== 1. Create the SQL DDL files ==
|
||||
|
||||
Create any additional SQL files that you want executed against the database (after the base ''schema.sql'' file is applied).
|
||||
|
||||
For example, if we wanted to add a default value to a column that was unsupported in the schema (e.g. where value is a SQL function):
|
||||
|
||||
{{{
|
||||
#!sql
|
||||
-- (for postgres)
|
||||
ALTER TABLE my_table ALTER COLUMN my_column SET DEFAULT CURRENT_TIMESTAMP;
|
||||
}}}
|
||||
|
||||
Now we save that as '''my_column-default.sql''' in the same directory as the generated '''schema.sql''' file (usually in projectdir/build/sql/).
|
||||
|
||||
== 2. Tell Propel Generator about the new file ==
|
||||
|
||||
In that same directory (where your '''schema.sql''' is located), there is a '''sqldb.map''' file which contains a mapping of SQL DDL files to the database that they should be executed against. After running the propel generator, you will probably have a single entry in that file that looks like:
|
||||
|
||||
{{{
|
||||
schema.sql=your-db-name
|
||||
}}}
|
||||
|
||||
We want to simply add the new file we created to this file (future builds will preserve anything you add to this file). When we're done, the file will look like this:
|
||||
|
||||
{{{
|
||||
schema.sql=your-db-name
|
||||
my_column-default.sql=your-db-name
|
||||
}}}
|
||||
|
||||
Now when you execute the ''insert-sql'' Propel generator target, the '''my_column-default.sql''' file will be executed against the ''your-db-name'' database.
|
47
library/propel/docs/cookbook/Copying-Objects.txt
Normal file
47
library/propel/docs/cookbook/Copying-Objects.txt
Normal file
|
@ -0,0 +1,47 @@
|
|||
= Copying Persisted Objects =
|
||||
|
||||
Propel provides the {{{copy()}}} method to perform copies of mapped row in the database. Note that Propel does '''not''' override the {{{__clone()}}} method; this allows you to create local duplicates of objects that map to the same persisted database row (should you need to do this).
|
||||
|
||||
The {{{copy()}}} method by default performs shallow copies, meaning that any foreign key references will remain the same.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$a = new Author();
|
||||
$a->setFirstName("Aldous");
|
||||
$a->setLastName("Huxley");
|
||||
|
||||
$p = new Publisher();
|
||||
$p->setName("Harper");
|
||||
|
||||
$b = new Book();
|
||||
$b->setTitle("Brave New World");
|
||||
$b->setPublisher($p);
|
||||
$b->setAuthor($a);
|
||||
|
||||
$b->save(); // so that auto-increment IDs are created
|
||||
|
||||
$bcopy = $b->copy();
|
||||
var_export($bcopy->getId() == $b->getId()); // FALSE
|
||||
var_export($bcopy->getAuthorId() == $b->getAuthorId()); // TRUE
|
||||
var_export($bcopy->getAuthor() === $b->getAuthor()); // TRUE
|
||||
?>
|
||||
}}}
|
||||
|
||||
== Deep Copies ==
|
||||
|
||||
By calling {{{copy()}}} with a {{{TRUE}}} parameter, Propel will create a deep copy of the object; this means that any related objects will also be copied.
|
||||
|
||||
To continue with example from above:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$bdeep = $b->copy(true);
|
||||
var_export($bcopy->getId() == $b->getId()); // FALSE
|
||||
var_export($bcopy->getAuthorId() == $b->getAuthorId()); // FALSE
|
||||
var_export($bcopy->getAuthor() === $b->getAuthor()); // FALSE
|
||||
?>
|
||||
}}}
|
153
library/propel/docs/cookbook/Customizing-Build.txt
Normal file
153
library/propel/docs/cookbook/Customizing-Build.txt
Normal file
|
@ -0,0 +1,153 @@
|
|||
= Customizing Build =
|
||||
It is possible to customize the Propel build process by overriding values in your propel __build.properties__ file. For maximum flexibility, you can even create your own Phing __build.xml__ file.
|
||||
|
||||
== Customizing the build.properties ==
|
||||
The easiest way to customize your Propel build is to simply specify build properties in your project's __build.properties__ file.
|
||||
|
||||
=== Understanding Phing build properties ===
|
||||
''Properties'' are essentially variables. These variables can be specified on the commandline or in ''properties files''.
|
||||
|
||||
For example, here's how a property might be specified on the commandline:
|
||||
{{{
|
||||
$> phing -Dpropertyname=value
|
||||
}}}
|
||||
|
||||
More typically, properties are stored in files and loaded by Phing. For those not familiar with Java properties files, these files look like PHP INI files; the main difference is that values in properties files can be references to other properties (a feature that will probably exist in in INI files in PHP 5.1).
|
||||
|
||||
'''Importantly:''' properties, once loaded, are not overridden by properties with the same name unless explicitly told to do so. In the Propel build process, the order of precedence for property values is as follows:
|
||||
1. Commandline properties
|
||||
1. Project __build.properties__
|
||||
1. Top-level __build.properties__
|
||||
1. Top-level __default.properties__
|
||||
|
||||
This means, for example, that values specified in the project's __build.properties__ files will override those in the top-level __build.properties__ and __default.properties__ files.
|
||||
|
||||
=== Changing values ===
|
||||
To get an idea of what you can modify in Propel, simply look through the __build.properties__ and __default.properties__ files.
|
||||
|
||||
''Note, however, that some of the current values exist for legacy reasons and will be cleaned up in Propel 1.1.''
|
||||
|
||||
==== New build output directories ====
|
||||
This can easily be customized on a project-by-project basis. For example, here is a __build.properties__ file for the ''bookstore ''project that puts the generated classes in __/var/www/bookstore/classes__ and puts the generated SQL in __/var/www/bookstore/db/sql__:
|
||||
{{{
|
||||
propel.project = bookstore
|
||||
propel.database = sqlite
|
||||
propel.database.url = sqlite://localhost/./test/bookstore.db
|
||||
propel.targetPackage = bookstore
|
||||
|
||||
# directories
|
||||
prope.output.dir = /var/www/bookstore
|
||||
propel.php.dir = ${propel.output.dir}/classes
|
||||
propel.phpconf.dir = ${propel.output.dir}/conf
|
||||
propel.sql.dir = ${propel.output.dir}/db/sql
|
||||
}}}
|
||||
|
||||
The ''targetPackage'' property is also used in determining the path of the generated classes. In the example above, the __Book.php__ class will be located at __/var/www/bookstore/classes/bookstore/Book.php__. You can change this __bookstore__ subdir by altering the ''targetPackage'' property:
|
||||
{{{
|
||||
propel.targetPackage = propelom
|
||||
}}}
|
||||
|
||||
Now the class will be located at __/var/www/bookstore/classes/propelom/Book.php__
|
||||
|
||||
''Note that you can override the targetPackage property by specifying a package="" attribute in the <database> tag or even the <table> tag of the schema.xml.''
|
||||
|
||||
== Creating a custom build.xml file ==
|
||||
|
||||
If you want to make more major changes to the way the build script works, you can setup your own Phing build script. This actually is not a very scary task, and once you've managed to create a Phing build script, you'll probably want to create build targets for other aspects of your project (e.g. running batch unit tests is now supported in Phing 2.1-CVS).
|
||||
|
||||
To start with, I suggest taking a look at the __build-propel.xml__ script (the build.xml script is just a wrapper script). Note, however, that the __build-propel.xml__ script does a lot & has a lot of complexity that is designed to make it easy to configure using properties (so, don't be scared).
|
||||
|
||||
Without going into too much detail about how Phing works, the important thing is that Phing build scripts XML and they are grouped into ''targets'' which are kinda like functions. The actual work of the scripts is performed by ''tasks'', which are PHP5 classes that extend the base Phing ''Task'' class and implement its abstract methods. Propel provides some Phing tasks that work with templates to create the object model.
|
||||
|
||||
=== Step 1: register the needed tasks ===
|
||||
|
||||
The Propel tasks must be registered so that Phing can find them. This is done using the ''<taskdef>'' tag. You can see this near the top of the __build-propel.xml__ file.
|
||||
|
||||
For example, here is how we register the ''propel-om'' task, which is the task that creates the PHP classes for your object model:
|
||||
{{{
|
||||
<taskdef
|
||||
name="propel-om"
|
||||
classname="propel.phing.PropelOMTask"/>
|
||||
}}}
|
||||
|
||||
Simple enough. Phing will now associate the ''<propel-data-model>'' tag with the ''PropelOMTask'' class, which it expects to find at __propel/phing/PropelOMTask.php__ (on your ''include_path''). If Propel generator classes are not on your ''include_path'', you can specify that path in your ''<taskdef>'' tag:
|
||||
{{{
|
||||
<taskdef
|
||||
name="propel-om"
|
||||
classname="propel.phing.PropelOMTask"
|
||||
classpath="/path/to/propel-generator/classes"/>
|
||||
}}}
|
||||
|
||||
Or, for maximum re-usability, you can create a ''<path>'' object, and then reference it (this is the way __build-propel.xml__ does it):
|
||||
{{{
|
||||
<path id="propelclasses">
|
||||
<pathelement dir="/path/to/propel-generator/classes"/>
|
||||
</path>
|
||||
|
||||
<taskdef
|
||||
name="propel-om"
|
||||
classname="propel.phing.PropelOMTask"
|
||||
classpathRef="propelclasses"/>
|
||||
}}}
|
||||
|
||||
=== Step 2: invoking the new task ===
|
||||
|
||||
Now that the ''<propel-om>'' task has been registered with Phing, it can be invoked in your build file.
|
||||
{{{
|
||||
<propel-om
|
||||
outputDirectory="/var/www/bookstore/classes"
|
||||
targetDatabase="mysql"
|
||||
targetPackage="bookstore"
|
||||
templatePath="/path/to/propel-generator/templates"
|
||||
targetPlatform="php5">
|
||||
<schemafileset dir="/var/www/bookstore/db/model" includes="*schema.xml"/>
|
||||
</propel-om>
|
||||
}}}
|
||||
|
||||
In the example above, it's worth pointing out that the ''<propel-om>'' task can actually transform multiple __schema.xml__ files, which is why there is a ''<schemafileset>'' sub-element. Phing ''filesets'' are beyond the scope of this HOWTO, but hopefully the above example is obvious enough.
|
||||
|
||||
=== Step 3: putting it together into a build.xml file ===
|
||||
|
||||
Now that we've seen the essential elements of our custom build file, it's time to look at how to assemble them into a working whole:
|
||||
{{{
|
||||
<?xml version="1.0">
|
||||
<project name="propel" default="om">
|
||||
|
||||
<!-- set properties we use later -->
|
||||
<property name="propelgen.home" value="/path/to/propel-generator"/>
|
||||
<property name="out.dir" value="/var/www/bookstore"/>
|
||||
|
||||
<!-- register task -->
|
||||
<path id="propelclasses">
|
||||
<pathelement dir="${propelgen.home}/classes"/>
|
||||
</path>
|
||||
|
||||
<taskdef
|
||||
name="propel-om"
|
||||
classname="propel.phing.PropelOMTask"
|
||||
classpathRef="propelclasses"/>
|
||||
|
||||
|
||||
<!-- this [default] target performs the work -->
|
||||
<target name="om" description="build propel om">
|
||||
<propel-om
|
||||
outputDirectory="${out.dir}/classes"
|
||||
targetDatabase="mysql"
|
||||
targetPackage="bookstore"
|
||||
templatePath="${propelgen.home}/templates"
|
||||
targetPlatform="php5">
|
||||
<schemafileset dir="${out.dir}/db/model" includes="*schema.xml"/>
|
||||
</propel-om>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
}}}
|
||||
|
||||
If that build script was named __build.xml__ then it could be executed by simply running ''phing'' in the directory where it is located:
|
||||
{{{
|
||||
$> phing om
|
||||
}}}
|
||||
|
||||
Actually, specifying the ''om'' target is not necessary since it is the default.
|
||||
|
||||
Refer to the __build-propel.xml__ file for examples of how to use the other Propel Phing tasks -- e.g. ''<propel-sql>'' for generating the DDL SQL, ''<propel-sql-exec>'' for inserting the SQL, etc.
|
95
library/propel/docs/cookbook/Existing-Database.txt
Normal file
95
library/propel/docs/cookbook/Existing-Database.txt
Normal file
|
@ -0,0 +1,95 @@
|
|||
= Working With Existing Databases =
|
||||
|
||||
The following topics are targeted for developers who already have a working database solution in place, but would like to use Propel to work with the data. For this case, Propel provides a number of command-line utilities helping with migrations of data and data structures.
|
||||
|
||||
== Working with Database Structures ==
|
||||
|
||||
Propel uses an abstract XML schema file to represent databases (the [wiki:Documentation/1.5/Schema schema]). Propel builds the SQL specific to a database based on this schema. Propel also provides a way to reverse-engineer the generic schema file based on database metadata.
|
||||
|
||||
=== Creating an XML Schema from a DB Structure ===
|
||||
|
||||
To generate a schema file, create a new directory for your project & specify the connection information in your `build.properties` file for that project. For example, to create a new project, `legacyapp`, follow these steps:
|
||||
|
||||
1. Create the `legacyapp` project directory anywhere on your filesystem:
|
||||
{{{
|
||||
> mkdir legacyapp
|
||||
> cd legacyapp
|
||||
}}}
|
||||
1. Create a `build.properties` file in `legacyapp/` directory with the DB connection parameters for your existing database, e.g.:
|
||||
{{{
|
||||
propel.project = legacyapp
|
||||
|
||||
# The Propel driver to use for generating SQL, etc.
|
||||
propel.database = mysql
|
||||
|
||||
# This must be a PDO DSN
|
||||
propel.database.url = mysql:dbname=legacyapp
|
||||
propel.database.user = root
|
||||
# propel.database.password =
|
||||
}}}
|
||||
1. Run the `reverse` task to generate the `schema.xml`:
|
||||
{{{
|
||||
> propel-gen reverse
|
||||
}}}
|
||||
1. Pay attention to any errors/warnings issued by Phing during the task execution and then examine the generated `schema.xml` file to make any corrections needed.
|
||||
1. '''You're done! ''' Now you have a `schema.xml` file in the `legacyapp/` project directory. You can now run the default Propel build to generate all the classes.
|
||||
|
||||
The generated `schema.xml` file should be used as a guide, not a final answer. There are some datatypes that Propel may not be familiar with; also some datatypes are simply not supported by Propel (e.g. arrays in PostgreSQL). Unfamiliar datatypes will be reported as warnings and substituted with a default VARCHAR datatype.
|
||||
|
||||
Tip: The reverse engineering classes may not be able to provide the same level of detail for all databases. In particular, metadata information for SQLite is often very basic since SQLite is a typeless database.
|
||||
|
||||
=== Migrating Structure to a New RDBMS ===
|
||||
|
||||
Because Propel has both the ability to create XML schema files based on existing database structures and to create RDBMS-specific DDL SQL from the XML schema file, you can use Propel to convert one database into another.
|
||||
|
||||
To do this you would simply:
|
||||
1. Follow the steps above to create the `schema.xml` file from existing db.
|
||||
1. Then you would change the target database type and specify connection URL for new database in the project's `build.properties` file:
|
||||
{{{
|
||||
propel.database = pgsql
|
||||
propel.database.url = pgsql://unix+localhost/newlegacyapp
|
||||
}}}
|
||||
1. And then run the `sql` task to generate the new DDL:
|
||||
{{{
|
||||
> propel-gen sql
|
||||
}}}
|
||||
1. And (optionally) the `insert-sql` task to create the new database:
|
||||
{{{
|
||||
> propel-gen insert-sql
|
||||
}}}
|
||||
|
||||
== Working with Database Data ==
|
||||
|
||||
Propel also provides several tasks to facilitate data import/export. The most important of these are `datadump` and `datasql`. The first dumps data to XML and the second converts the XML data dump to a ready-to-insert SQL file.
|
||||
|
||||
Tip: Both of these tasks require that you already have generated the `schema.xml` for your database.
|
||||
|
||||
=== Dumping Data to XML ===
|
||||
|
||||
Once you have created (or reverse-engineered) your `schema.xml` file, you can run the `datadump` task to dump data from the database into a `data.xml` file.
|
||||
|
||||
{{{
|
||||
> propel-gen datadump
|
||||
}}}
|
||||
|
||||
The task transfers database records to XML using a simple format, where each row is an element, and each column is an attribute. So for instance, the XML representation of a row in a `publisher` table:
|
||||
|
||||
||'''publisher_id'''||'''name'''||
|
||||
||1||William Morrow||
|
||||
|
||||
... is rendered in the `data.xml` as follows:
|
||||
{{{
|
||||
<dataset name="all">
|
||||
...
|
||||
<Publisher PublisherId="1" Name="William Morrow"/>
|
||||
...
|
||||
</dataset>
|
||||
}}}
|
||||
|
||||
=== Creating SQL from XML ===
|
||||
|
||||
To create the SQL files from the XML, run the `datasql` task:
|
||||
{{{
|
||||
> propel-gen datasql
|
||||
}}}
|
||||
The generated SQL is placed in the `build/sql/` directory and will be inserted when you run the `insert-sql` task.
|
73
library/propel/docs/cookbook/LOB-Columns.txt
Normal file
73
library/propel/docs/cookbook/LOB-Columns.txt
Normal file
|
@ -0,0 +1,73 @@
|
|||
= Working with LOB Columns =
|
||||
|
||||
Propel uses PHP streams internally for storing ''Binary'' Locator Objects (BLOBs). This choice was made because PDO itself uses streams as a convention when returning LOB columns in a resultset and when binding values to prepared statements. Unfortunately, not all PDO drivers support this (see, for example, http://bugs.php.net/bug.php?id=40913); in those cases, Propel creates a {{{php://temp}}} stream to hold the LOB contents and thus provide a consistent API.
|
||||
|
||||
Note that CLOB (''Character'' Locator Objects) are treated as strings in Propel, as there is no convention for them to be treated as streams by PDO.
|
||||
|
||||
== Getting BLOB Values ==
|
||||
|
||||
BLOB values will be returned as PHP stream resources from the accessor methods. Alternatively, if the value is NULL in the database, then the accessors will return the PHP value NULL.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$media = MediaPeer::retrieveByPK(1);
|
||||
$fp = $media->getCoverImage();
|
||||
if ($fp !== null) {
|
||||
echo stream_get_contents($fp);
|
||||
}
|
||||
}}}
|
||||
|
||||
== Setting BLOB Values ==
|
||||
|
||||
When setting a BLOB column, you can either pass in a stream or the blob contents.
|
||||
|
||||
=== Setting using a stream ===
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$fp = fopen("/path/to/file.ext", "rb");
|
||||
|
||||
$media = new Media();
|
||||
$media->setCoverImage($fp);
|
||||
}}}
|
||||
|
||||
=== Setting using file contents ===
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$media = new Media();
|
||||
$media->setCoverImage(file_get_contents("/path/to/file.ext"));
|
||||
}}}
|
||||
|
||||
Regardless of which setting method you choose, the BLOB will always be represented internally as a stream resource -- ''and subsequent calls to the accessor methods will return a stream.''
|
||||
|
||||
For example:
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$media = new Media();
|
||||
$media->setCoverImage(file_get_contents("/path/to/file.ext"));
|
||||
|
||||
$fp = $media->getCoverImage();
|
||||
print gettype($fp); // "resource"
|
||||
}}}
|
||||
|
||||
=== Setting BLOB columns and isModified() ===
|
||||
|
||||
Note that because a stream contents may be externally modified, ''mutator methods for BLOB columns will always set the '''isModified()''' to report true'' -- even if the stream has the same identity as the stream that was returned.
|
||||
|
||||
For example:
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$media = MediaPeer::retrieveByPK(1);
|
||||
$fp = $media->getCoverImage();
|
||||
$media->setCoverImage($fp);
|
||||
|
||||
var_export($media->isModified()); // TRUE
|
||||
}}}
|
94
library/propel/docs/cookbook/Master-Slave.txt
Normal file
94
library/propel/docs/cookbook/Master-Slave.txt
Normal file
|
@ -0,0 +1,94 @@
|
|||
= Replication =
|
||||
|
||||
Propel can be used in a master-slave replication environment. These environments are set up to improve the performance of web applications by dispatching the database-load to multiple database-servers. While a single master database is responsible for all write-queries, multiple slave-databases handle the read-queries. The slaves are synchronised with the master by a fast binary log (depending on the database).
|
||||
|
||||
== Configuring Propel for Replication ==
|
||||
|
||||
* Set up a replication environment (see the Databases section below)
|
||||
* Use the latest Propel-Version from SVN
|
||||
* add a slaves-section to your {{{runtime-conf.xml}}} file
|
||||
* verify the correct setup by checking the masters log file (should not contain "select ..." statements)
|
||||
|
||||
You can configure Propel to support replication by adding a <slaves> element with nested <connection> element(s) to your {{{runtime-conf.xml}}}.
|
||||
|
||||
The <slaves> section is at the same level as the master <connection> and contains multiple nested <connection> elements with the same information as the top-level (master) <connection>. It is recommended that they are numbered. The follwing example shows a slaves section with a several slave connections configured where "localhost" is the master and "slave-server1" and "slave-server2" are the slave-database connections.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<log>
|
||||
<ident>propel-bookstore</ident>
|
||||
<name>console</name>
|
||||
<level>7</level>
|
||||
</log>
|
||||
<propel>
|
||||
<datasources default="bookstore">
|
||||
<datasource id="bookstore">
|
||||
<adapter>sqlite</adapter>
|
||||
<connection>
|
||||
<dsn>mysql:host=localhost;dbname=bookstore</dsn>
|
||||
<user>testuser</user>
|
||||
<password>password</password>
|
||||
</connection>
|
||||
<slaves>
|
||||
<connection>
|
||||
<dsn>mysql:host=slave-server1; dbname=bookstore</dsn>
|
||||
<user>testuser</user>
|
||||
<password>password</password>
|
||||
</connection>
|
||||
<connection>
|
||||
<dsn>mysql:host=slave-server2; dbname=bookstore</dsn>
|
||||
<user>testuser</user>
|
||||
<password>password</password>
|
||||
</connection>
|
||||
</slaves>
|
||||
</datasource>
|
||||
</datasources>
|
||||
</propel>
|
||||
</config>
|
||||
}}}
|
||||
|
||||
== Implementation ==
|
||||
|
||||
The replication functionality is implemented in the Propel connection configuration and initialization code and in the generated Peer and Object classes.
|
||||
|
||||
=== Propel::getConnection() ===
|
||||
|
||||
When requesting a connection from Propel ('''Propel::getConnection()'''), you can either specify that you want a READ connection (slave) or WRITE connection (master). Methods that are designed to perform READ operations, like the '''doSelect*()''' methods of your generated Peer classes, will always request a READ connection like so:
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$con = Propel::getConnection(MyPeer::DATABASE_NAME, Propel::CONNECTION_READ);
|
||||
}}}
|
||||
Other methods that are designed to perform write operations will explicitly request a Propel::CONNECTION_WRITE connection. The WRITE connections are also the default, however, so applications that make a call to '''Propel::getConnection()''' without specifying a connection mode will always get a master connection.
|
||||
|
||||
If you do have configured slave connections, Propel will choose a single random slave to use per request for any connections where the mode is Propel::CONNECTION_READ.
|
||||
|
||||
Both READ (slave) and WRITE (master) connections are only configured on demand. If all of your SQL statements are SELECT queries, Propel will never create a connection to the master database (unless, of course, you have configured Propel to always use the master connection -- see below).
|
||||
|
||||
'''Important:''' if you are using Propel to execute custom SQL queries in your application (and you want to make sure that Propel respects your replication setup), you will need to explicitly get the correct connection. For example:
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$con = Propel::getConnection(MyPeer::DATABASE_NAME, Propel::CONNECTION_READ);
|
||||
$stmt = $con->query('SELECT * FROM my');
|
||||
/* ... */
|
||||
}}}
|
||||
|
||||
=== Propel::setForceMasterConnection() ===
|
||||
|
||||
You can force Propel to always return a WRITE (master) connection from '''Propel::getConnection()''' by calling '''Propel::setForceMasterConnection(true);'''. This can be useful if you must be sure that you are getting the most up-to-date data (i.e. if there is some latency possible between master and slaves).
|
||||
|
||||
== Databases ==
|
||||
|
||||
=== MySql ===
|
||||
|
||||
http://dev.mysql.com/doc/refman/5.0/en/replication-howto.html
|
||||
|
||||
== References ==
|
||||
|
||||
* Henderson Carl (2006): Building Scalable Web Sites. The Flickr Way. O'Reilly. ISBN-596-10235-6.
|
||||
|
297
library/propel/docs/cookbook/Multi-Component.txt
Normal file
297
library/propel/docs/cookbook/Multi-Component.txt
Normal file
|
@ -0,0 +1,297 @@
|
|||
= Multi-Component Data Model =
|
||||
|
||||
Propel comes along with packaging capabilities that allow you to more easily integrate Propel into a packaged or modularized application.
|
||||
|
||||
== Muliple Schemas ==
|
||||
|
||||
You can use as many `schema.xml` files as you want. Schema files have to be named `(*.)schema.xml`, so names like `schema.xml`, `package1.schema.xml`, `core.package1.schema.xml` are all acceptable. These files ''have'' to be located in your project directory.
|
||||
|
||||
Each schema file has to contain a `<database>` element with a `name` attribute. This name references the connection settings to be used for this database (and configured in the `runtime-conf.xml`), so separated schemas can share a common database name.
|
||||
|
||||
Whenever you call a propel build taks, Propel will consider all these schema files and build the classes (or the SQL) for all the tables.
|
||||
|
||||
== Understanding Packages ==
|
||||
|
||||
In Propel, a ''package'' represents a group of models. This is a convenient way to organize your code in a modularized way, since classes and SQL files of a given package are be grouped together and separated from the other packages. By carefully choosing the package of each model, applications end up in smaller, independent modules that are easier to manage.
|
||||
|
||||
=== Package Cascade ===
|
||||
|
||||
The package is defined in a configuration cascade. You can set it up for the whole project, for all the tables of a schema, or for a single table.
|
||||
|
||||
For the whole project, the main package is set in the `build.properties`:
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
propel.targetPackage = my_project
|
||||
}}}
|
||||
|
||||
By default, all the tables of all the schemas in the project use this package. However, you can override the package for a given `<database>` by setting its `package` attribute:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<!-- in author.schema.xml -->
|
||||
<database package="author" name="bookstore">
|
||||
<table name="author">
|
||||
<!-- author columns -->
|
||||
</table>
|
||||
</database>
|
||||
|
||||
<!-- in book.schema.xml -->
|
||||
<database package="book" name="bookstore">
|
||||
<table name="book">
|
||||
<!-- book columns -->
|
||||
</table>
|
||||
<table name="review">
|
||||
<!-- review columns -->
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
In this example, thanks to the `package` attribute, the tables are grouped into the following packages:
|
||||
|
||||
* `my_project.author` package: `author` table
|
||||
* `my_project.book` package: `book` and `review` tables
|
||||
|
||||
'''Warning''': If you separate tables related by a foreign key into separate packages (like `book` and `author` in this example), you must enable the `packageObjectModel` build property to let Propel consider other packages for relations.
|
||||
|
||||
You can also override the `package` attribute at the `<table>` element level.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<!-- in author.schema.xml -->
|
||||
<database package="author" name="bookstore">
|
||||
<table name="author">
|
||||
<!-- author columns -->
|
||||
</table>
|
||||
</database>
|
||||
|
||||
<!-- in book.schema.xml -->
|
||||
<database package="book" name="bookstore">
|
||||
<table name="book">
|
||||
<!-- book columns -->
|
||||
</table>
|
||||
<table name="review" package="review">
|
||||
<!-- review columns -->
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
This ends up in the following package:
|
||||
|
||||
* `my_project.author` package: `author` table
|
||||
* `my_project.book` package: `book` table
|
||||
* `my_project.review` package: `review` table
|
||||
|
||||
Notice that tables can end up in separated packages even though they belong to the same schema file.
|
||||
|
||||
'''Tip''': You can use dots in a package name to add more package levels.
|
||||
|
||||
=== Packages and Generated Model Files ===
|
||||
|
||||
The `package` attribute of a table translates to the directory in which Propel generates the Model classes for this table.
|
||||
|
||||
For instance, if no `package` attribute is defined at the database of table level, Propel places all classes according to the `propel.targetPackage` from the `build.properties`:
|
||||
|
||||
{{{
|
||||
build/
|
||||
classes/
|
||||
my_project/
|
||||
om/
|
||||
map/
|
||||
Author.php
|
||||
AuthorPeer.php
|
||||
AuthorQuery.php
|
||||
Book.php
|
||||
BookPeer.php
|
||||
BookQuery.php
|
||||
Review.php
|
||||
ReviewPeer.php
|
||||
ReviewQuery.php
|
||||
}}}
|
||||
|
||||
You can further tweak the location where Propel puts the created files by changing the `propel.output.dir` build property. By default this property is set to:
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
propel.output.dir = ${propel.project.dir}/build
|
||||
}}}
|
||||
|
||||
You can change it to use any other directory as your build directory.
|
||||
|
||||
If you set up packages for `<database>` elements, Propel splits up the generated model classes into subdirectories named after the package attribute:
|
||||
|
||||
{{{
|
||||
build/
|
||||
classes/
|
||||
my_project/
|
||||
author/
|
||||
om/
|
||||
map/
|
||||
Author.php
|
||||
AuthorPeer.php
|
||||
AuthorQuery.php
|
||||
book/
|
||||
om/
|
||||
map/
|
||||
Book.php
|
||||
BookPeer.php
|
||||
BookQuery.php
|
||||
Review.php
|
||||
ReviewPeer.php
|
||||
ReviewQuery.php
|
||||
}}}
|
||||
|
||||
And of course, if you specialize the `package` attribute per table, you can have one table use its own package:
|
||||
|
||||
{{{
|
||||
build/
|
||||
classes/
|
||||
my_project/
|
||||
author/
|
||||
om/
|
||||
map/
|
||||
Author.php
|
||||
AuthorPeer.php
|
||||
AuthorQuery.php
|
||||
book/
|
||||
om/
|
||||
map/
|
||||
Book.php
|
||||
BookPeer.php
|
||||
BookQuery.php
|
||||
review/
|
||||
om/
|
||||
map/
|
||||
Review.php
|
||||
ReviewPeer.php
|
||||
ReviewQuery.php
|
||||
}}}
|
||||
|
||||
=== Packages And SQL Files ===
|
||||
|
||||
Propel also considers packages for SQL generation. In practice, Propel generates one SQL file per package. Each file contains the CREATE TABLE SQL statements necessary to create all the tables of a given package.
|
||||
|
||||
So by default, all the tables end up in a single SQL file:
|
||||
|
||||
{{{
|
||||
build/
|
||||
sql/
|
||||
schema.sql
|
||||
}}}
|
||||
|
||||
If you specialize the `package` for each `<database>` element, Propel uses it for SQL files:
|
||||
|
||||
{{{
|
||||
build/
|
||||
sql/
|
||||
author.schema.sql // contains CREATE TABLE author
|
||||
book.schema.sql // contains CREATE TABLE book and CREATE TABLE review
|
||||
}}}
|
||||
|
||||
And, as you probably expect it, a package overridden at the table level also acocunts for an independent SQL file:
|
||||
|
||||
{{{
|
||||
build/
|
||||
sql/
|
||||
author.schema.sql // contains CREATE TABLE author
|
||||
book.schema.sql // contains CREATE TABLE book
|
||||
review.schema.sql // contains CREATE TABLE review
|
||||
}}}
|
||||
|
||||
== Understanding The packageObjectModel Build Property ==
|
||||
|
||||
The `propel.packageObjectModel` build property enables the "packaged" build process. This modifies the build tasks behavior by joining `<database>` elements of the same name - but keeping their packages separate. That allows to split a large schema into several files, regardless of foreign key dependencies, since Propel will join all schemas using the same database name.
|
||||
|
||||
To switch this on, simply add the following line to the `build.properties` file in your project directory:
|
||||
{{{
|
||||
propel.packageObjectModel = true
|
||||
}}}
|
||||
|
||||
== The Bookstore Packaged Example ==
|
||||
|
||||
In the bookstore-packaged example you'll find the following schema files:
|
||||
|
||||
* author.schema.xml
|
||||
* book.schema.xml
|
||||
* club.schema.xml
|
||||
* media.schema.xml
|
||||
* publisher.schema.xml
|
||||
* review.schema.xml
|
||||
* log.schema.xml
|
||||
|
||||
Each schema file has to contain a `<database>` tag that has its `package` attribute set to the package name where ''all'' of the tables in this schema file/database belong to.
|
||||
|
||||
For example, in the bookstore-packaged example the `author.schema.xml` contains the following `<database>` tag:
|
||||
|
||||
{{{
|
||||
<database package="core.author" name="bookstore" [...]>
|
||||
}}}
|
||||
|
||||
That means, that the Author OM classes will be created in a subdirectory `core/author/` of the build output directory.
|
||||
|
||||
You can have more than one schema file that belong to one package. For example, in the the bookstore-packaged example both the `book.schema.xml` and `media.schema.xml` belong to the same package "core.book". The generated OM classes for these schemas will therefore end up in the same `core/book/` subdirectory.
|
||||
|
||||
=== The OM build ===
|
||||
|
||||
To run the packaged bookstore example build simply go to the `propel/test/fixtures/bookstore-packages/` directory and type:
|
||||
|
||||
{{{
|
||||
../../../generator/bin/propel-gen om
|
||||
}}}
|
||||
|
||||
This should run without any complaints. When you have a look at the projects/bookstore-packaged/build/classes directory, the following directory tree should have been created:
|
||||
{{{
|
||||
addon/
|
||||
club/
|
||||
BookClubList.php
|
||||
BookClubListPeer.php
|
||||
BookListRel.php
|
||||
BookListRelPeer.php
|
||||
core/
|
||||
author/
|
||||
Author.php
|
||||
AuthorPeer.php
|
||||
book/
|
||||
Book.php
|
||||
BookPeer.php
|
||||
|
||||
Media.php
|
||||
MediaPeer.php
|
||||
publisher/
|
||||
Publisher.php
|
||||
PublisherPeer.php
|
||||
review/
|
||||
Review.php
|
||||
ReviewPeer.php
|
||||
util/
|
||||
log/
|
||||
BookstoreLog.php
|
||||
BookstoreLogPeer.php
|
||||
}}}
|
||||
|
||||
(The additional subdirectories map/ and om/ in each of these directories have been omitted for clarity.)
|
||||
|
||||
== The SQL build ==
|
||||
|
||||
From the same schema files, run the SQL generation by calling:
|
||||
|
||||
{{{
|
||||
../../../generator/bin/propel-gen sql
|
||||
}}}
|
||||
|
||||
Then, have a look at the `build/sql/` directory: you will see that for each package (that is specified as a package attribute in the schema file database tags), one sql file has been created:
|
||||
|
||||
* addon.club.schema.sql
|
||||
* core.author.schema.sql
|
||||
* core.book.schema.sql
|
||||
* core.publisher.schema.sql
|
||||
* core.review.schema.sql
|
||||
* util.log.schema.sql
|
||||
|
||||
These files contain the CREATE TABLE SQL statements necessary for each package.
|
||||
|
||||
When you now run the insert-sql task by typing:
|
||||
{{{
|
||||
../../../generator/bin/propel-gen insert-sql
|
||||
}}}
|
||||
these SQL statements will be executed on a SQLite database located in the Propel/generator/test/ directory.
|
133
library/propel/docs/cookbook/Namespaces.txt
Normal file
133
library/propel/docs/cookbook/Namespaces.txt
Normal file
|
@ -0,0 +1,133 @@
|
|||
= How to Use PHP 5.3 Namespaces =
|
||||
|
||||
The generated model classes can use a namespace. It eases the management of large database models, and makes the Propel model classes integrate with PHP 5.3 applications in a clean way.
|
||||
|
||||
== Namespace Declaration And Inheritance ==
|
||||
|
||||
To define a namespace for a model class, you just need to specify it in a `namespace` attribute of the `<table>` element for a single table, or in the `<database>` element to set the same namespace to all the tables.
|
||||
|
||||
Here is an example schema using namespaces:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
|
||||
<database name="bookstore" defaultIdMethod="native" namespace="Bookstore">
|
||||
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" primaryString="true" />
|
||||
<column name="isbn" required="true" type="VARCHAR" size="24" phpName="ISBN" />
|
||||
<column name="price" required="false" type="FLOAT" />
|
||||
<column name="publisher_id" required="false" type="INTEGER" description="Foreign Key Publisher" />
|
||||
<column name="author_id" required="false" type="INTEGER" description="Foreign Key Author" />
|
||||
<foreign-key foreignTable="publisher" onDelete="setnull">
|
||||
<reference local="publisher_id" foreign="id" />
|
||||
</foreign-key>
|
||||
<foreign-key foreignTable="author" onDelete="setnull" onUpdate="cascade">
|
||||
<reference local="author_id" foreign="id" />
|
||||
</foreign-key>
|
||||
</table>
|
||||
|
||||
<table name="author">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER"/>
|
||||
<column name="first_name" required="true" type="VARCHAR" size="128" />
|
||||
<column name="last_name" required="true" type="VARCHAR" size="128" />
|
||||
<column name="email" type="VARCHAR" size="128" />
|
||||
</table>
|
||||
|
||||
<table name="publisher" namespace="Book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="name" required="true" type="VARCHAR" size="128" default="Penguin" />
|
||||
</table>
|
||||
|
||||
<table name="user" namespace="\Admin">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER"/>
|
||||
<column name="login" required="true" type="VARCHAR" size="128" />
|
||||
<column name="email" type="VARCHAR" size="128" />
|
||||
</table>
|
||||
|
||||
</database>
|
||||
}}}
|
||||
|
||||
The `<database>` element defines a `namespace` attribute. The `book` and `author` tables inherit their namespace from the database, therefore the generated classes for these tables will be `\Bookstore\Book` and `\Bookstore\Author`.
|
||||
|
||||
The `publisher` table defines a `namespace` attribute on ots own, which ''extends'' the database namespace. That means that the generated class will be `\Bookstore\Book\Publisher`.
|
||||
|
||||
As for the `user` table, it defines an absolute namespace (starting with a backslash), which ''overrides'' the database namespace. The generated class for the `user` table will be `Admin\User`.
|
||||
|
||||
'''Tip''': You can use subnamespaces (i.e. namespaces containing backslashes) in the `namespace` attribute.
|
||||
|
||||
== Using Namespaced Models ==
|
||||
|
||||
Namespaced models benefit from the Propel runtime autoloading just like the other model classes. You just need to alias them, or to use their fully qualified name.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// use an alias
|
||||
use Bookstore\Book;
|
||||
$book = new Book();
|
||||
|
||||
// or use fully qualified name
|
||||
$book = new \Bookstore\Book();
|
||||
}}}
|
||||
|
||||
Relation names forged by Propel don't take the namespace into account. That means that related getter and setters make no mention of it:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = new \Bookstore\Author();
|
||||
$book = new \Bookstore\Book();
|
||||
$book->setAuthor($author);
|
||||
$book->save();
|
||||
}}}
|
||||
|
||||
The namespace is used for the ActiveRecord class, but also for the Query and Peer classes. Just remember that when you use relation names ina query, the namespace should not appear:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = \Bookstore\AuthorQuery::create()
|
||||
->useBookQuery()
|
||||
->filterByPrice(array('max' => 10))
|
||||
->endUse()
|
||||
->findOne();
|
||||
}}}
|
||||
|
||||
Related tables can have different namespaces, it doesn't interfere with the functionality provided by the object model:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book = \Bookstore\BookQuery::create()
|
||||
->findOne();
|
||||
echo get_class($book->getPublisher());
|
||||
// \Bookstore\Book\Publisher
|
||||
}}}
|
||||
|
||||
'''Tip''': Using namespaces make generated model code incompatible with versions of PHP less than 5.3. Beware that you will not be able to use your model classes in an older PHP application.
|
||||
|
||||
== Using Namespaces As A Directory Structure ==
|
||||
|
||||
In a schema, you can define a `package` attribute on a `<database>` or a `<table>` tag to generate model classes in a subdirectory (see [wiki:Documentation/1.5/Multi-Component]). If you use namespaces to autoload your classes based on a SplClassAutoloader (see http://groups.google.com/group/php-standards), then you may find yourself repeating the `namespace` data in the `package` attribute:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="bookstore" defaultIdMethod="native"
|
||||
namespace="Foo/Bar" package="Foo.Bar">
|
||||
}}}
|
||||
|
||||
To avoid such repetitions, just set the `propel.namespace.autoPackage` setting to `true` in your `build.properties`:
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
propel.namespace.autoPackage = true
|
||||
}}}
|
||||
|
||||
Now Propel will automatically create a `package` attribute, and therefore distribute model classes in subdirectories, based on the `namespace` attribute, and you can omit the manual `package` attribute in the schema:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="bookstore" defaultIdMethod="native" namespace="Foo/Bar">
|
||||
}}}
|
183
library/propel/docs/cookbook/Nested-Set.txt
Normal file
183
library/propel/docs/cookbook/Nested-Set.txt
Normal file
|
@ -0,0 +1,183 @@
|
|||
= !NestedSet support in Propel =
|
||||
|
||||
'''Warning''': Since Propel 1.5, the support for nested sets was moved to the `nested_set` behavior. The method described here is deprecated.'''
|
||||
|
||||
== Description ==
|
||||
|
||||
With !NestedSet implementation, trees are stored using different approach in databases: [http://www.sitepoint.com/article/hierarchical-data-database]
|
||||
|
||||
Nested Set implementation requires three dedicated fields in table structure
|
||||
|
||||
* left
|
||||
* right
|
||||
|
||||
Plus an optional fields for multi nested set support
|
||||
* scope
|
||||
|
||||
''NB: fields name are free and must be defined in schema.xml''
|
||||
|
||||
To enable !NestedSet support in table, schema.xml must define some specific attributes:
|
||||
'''treeMode''' which must take the value '''!NestedSet'''
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="menu" idMethod="native" treeMode="NestedSet">
|
||||
}}}
|
||||
|
||||
Then, left and right field must be defined that way '''nestedSetLeftKey''' as a boolean value as '''nestedSetRightKey'''
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="lft" type="INTEGER" required="true" default="0" nestedSetLeftKey="true"/>
|
||||
<column name="rgt" type="INTEGER" required="true" default="0" nestedSetRightKey="true"/>
|
||||
}}}
|
||||
|
||||
For multi nestedset support, an other column must be defined with boolean attribute '''treeScopeKey''' set to true
|
||||
{{{
|
||||
#!xml
|
||||
<column name="scope" type="INTEGER" required="true" default="0" treeScopeKey="true"/>
|
||||
}}}
|
||||
|
||||
And then, let's the propel generator automagically create all the needed model and stub classes.
|
||||
|
||||
== !NestedSet usage in Propel ==
|
||||
|
||||
''ex:''
|
||||
'''schema.xml''' extract
|
||||
{{{
|
||||
#!xml
|
||||
<table name="menu" idMethod="native" treeMode="NestedSet">
|
||||
<column name="id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true"/>
|
||||
<column name="lft" type="INTEGER" required="true" default="0" nestedSetLeftKey="true"/>
|
||||
<column name="rgt" type="INTEGER" required="true" default="0" nestedSetRightKey="true"/>
|
||||
<column name="scope" type="INTEGER" required="true" default="0" treeScopeKey="true"/>
|
||||
<column name="text" type="VARCHAR" size="128" required="true" default=""/>
|
||||
<column name="link" type="VARCHAR" size="255" required="true" default=""/>
|
||||
<index name="lft">
|
||||
<index-column name="lft"/>
|
||||
</index>
|
||||
<index name="rgt">
|
||||
<index-column name="rgt"/>
|
||||
</index>
|
||||
<index name="scope">
|
||||
<index-column name="scope"/>
|
||||
</index>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
=== !NestedSet insertion ===
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
$root = new Menu();
|
||||
$root->setText('Google');
|
||||
$root->setLink('http://www.google.com');
|
||||
|
||||
$root->makeRoot();
|
||||
$root->save();
|
||||
|
||||
$menu = new Menu();
|
||||
$menu->setText('Google Mail');
|
||||
$menu->setLink('http://mail.google.com');
|
||||
$menu->insertAsLastChildOf($root);
|
||||
$menu->save();
|
||||
|
||||
$child = new Menu();
|
||||
$child->setText('Google Maps');
|
||||
$child->setLink('http://maps.google.com');
|
||||
$child->insertAsLastChildOf($root);
|
||||
$child->save();
|
||||
|
||||
$sibling = new Menu();
|
||||
$sibling->setText('Yahoo!');
|
||||
$sibling->setLink('http://www.yahoo.com');
|
||||
$sibling->insertAsNextSiblingOf($root);
|
||||
$sibling->save();
|
||||
|
||||
$child = new Menu();
|
||||
$child->setText('Yahoo! Mail');
|
||||
$child->setLink('http://mail.yahoo.com');
|
||||
$child->insertAsLastChildOf($sibling);
|
||||
$child->save();
|
||||
}}}
|
||||
|
||||
=== Multi !NestedSet insertion ===
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
|
||||
// Create first root node
|
||||
$root = new Menu();
|
||||
$root->setText('Google');
|
||||
$root->setLink('http://www.google.com');
|
||||
|
||||
$root->makeRoot();
|
||||
$root->setScopeIdValue(1); // Tree 1
|
||||
$root->save();
|
||||
|
||||
$menu = new Menu();
|
||||
$menu->setText('Google Mail');
|
||||
$menu->setLink('http://mail.google.com');
|
||||
$menu->insertAsLastChildOf($root);
|
||||
$menu->save();
|
||||
|
||||
// Create secund root node
|
||||
$root2 = new Menu();
|
||||
$root2->setText('Yahoo!');
|
||||
$root2->setLink('http://www.yahoo.com');
|
||||
|
||||
$root2->makeRoot();
|
||||
$root2->setScopeIdValue(2); // Tree 2
|
||||
$root2->save();
|
||||
|
||||
$menu = new Menu();
|
||||
$menu->setText('Yahoo! Mail');
|
||||
$menu->setLink('http://mail.yahoo.com');
|
||||
$menu->insertAsLastChildOf($root2);
|
||||
$menu->save();
|
||||
}}}
|
||||
|
||||
=== Tree retrieval ===
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class myMenuOutput extends RecursiveIteratorIterator {
|
||||
function __construct(Menu $m) {
|
||||
parent::__construct($m, self::SELF_FIRST);
|
||||
}
|
||||
|
||||
function beginChildren() {
|
||||
echo str_repeat("\t", $this->getDepth());
|
||||
}
|
||||
|
||||
function endChildren() {
|
||||
echo str_repeat("\t", $this->getDepth() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
$menu = MenuPeer::retrieveTree($scopeId);
|
||||
$it = new myMenuOutput($menu);
|
||||
foreach($it as $m) {
|
||||
echo $m->getText(), '[', $m->getLeftValue(), '-', $m->getRightValue(), "]\n";
|
||||
}
|
||||
}}}
|
||||
=== Tree traversal ===
|
||||
|
||||
!NestetSet implementation use the [http://somabo.de/talks/200504_php_quebec_spl_for_the_masses.pdf SPL RecursiveIterator] as suggested by soenke
|
||||
|
||||
== !NestedSet known broken behaviour ==
|
||||
|
||||
=== Issue description ===
|
||||
For every changes applied on the tree, several entries in the database can be involved. So all already loaded nodes have to be refreshed with their new left/right values.
|
||||
|
||||
=== InstancePool enabled ===
|
||||
In order to refresh all loaded nodes, an automatic internal call is made after each tree change to retrieve all instance in InstancePool and update them.
|
||||
And it works fine.
|
||||
|
||||
=== InstancePool disabled ===
|
||||
When InstancePool is disabled, their is no way to retrieve references to all already loaded node and get them updated.
|
||||
So in most case, all loaded nodes are not updated and it leads to an inconsistency state.
|
||||
So, workaround is to do an explicit reload for any node you use after tree change.
|
||||
|
||||
|
164
library/propel/docs/cookbook/Runtime-Introspection.txt
Normal file
164
library/propel/docs/cookbook/Runtime-Introspection.txt
Normal file
|
@ -0,0 +1,164 @@
|
|||
= Model Introspection At Runtime =
|
||||
|
||||
In addition to the object and peer classes used to do C.R.U.D. operations, Propel generates an object mapping for your tables to allow runtime introspection.
|
||||
|
||||
The intospection objects are instances of the map classes. Propel maps databases, tables, columns, validators, and relations into objects that you can easily use.
|
||||
|
||||
== Retrieving a TableMap ==
|
||||
|
||||
The starting point for runtime introspection is usually a table map. This objects stores every possible property of a table, as defined in the `schema.xml`, but accessible at runtime.
|
||||
|
||||
To retrieve a table map for a table, use the `getTableMap()` static method of the related peer class. For instance, to retrieve the table map for the `book` table, just call:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$bookTable = BookPeer::getTableMap();
|
||||
}}}
|
||||
|
||||
== TableMap properties ==
|
||||
|
||||
A `TableMap` object carries the same information as the schema. Check the following example to see how you can read the general properties of a table from its map:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
echo $bookTable->getName(); // 'table'
|
||||
echo $bookTable->getPhpName(); // 'Table'
|
||||
echo $bookTable->getPackage(); // 'bookstore'
|
||||
echo $bookTable->isUseIdGenerator(); // true
|
||||
}}}
|
||||
|
||||
Tip: A TableMap object also references the `DatabaseMap` that contains it. From the database map, you can also retrieve other table maps using the table name or the table phpName:
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$dbMap = $bookTable->getDatabaseMap();
|
||||
$authorTable = $dbMap->getTable('author');
|
||||
$authorTable = $dbMap->getTablebyPhpName('Author');
|
||||
}}}
|
||||
|
||||
To introspect the columns of a table, use any of the `getColumns()`, `getPrimaryKeys()`, and `getForeignKeys()` `TableMap` methods. They all return an array of `ColumnMap` objects.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$bookColumns = $bookTable->getColumns();
|
||||
foreach ($bookColumns as $column) {
|
||||
echo $column->getName();
|
||||
}
|
||||
}}}
|
||||
|
||||
Alternatively, if you know a column name, you can retrieve the corresponding ColumnMap directly using the of `getColumn($name)` method.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$bookTitleColumn = $bookTable->getColumn('title');
|
||||
}}}
|
||||
|
||||
The `DatabaseMap` object offers a shortcut to every `ColumnMap` object if you know the fully qualified column name:
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$bookTitleColumn = $dbMap->getColumn('book.TITLE');
|
||||
}}}
|
||||
|
||||
== ColumnMaps ==
|
||||
|
||||
A `ColumnMap` instance offers a lot of information about a table column. Check the following examples:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$bookTitleColumn->getTableName(); // 'book'
|
||||
$bookTitleColumn->getTablePhpName(); // 'Book'
|
||||
$bookTitleColumn->getType(); // 'VARCHAR'
|
||||
$bookTitleColumn->getSize(); // 255
|
||||
$bookTitleColumn->getDefaultValue(); // null
|
||||
$bookTitleColumn->isLob(); // false
|
||||
$bookTitleColumn->isTemporal(); // false
|
||||
$bookTitleColumn->isEpochTemporal(); // false
|
||||
$bookTitleColumn->isNumeric(); // false
|
||||
$bookTitleColumn->isText(); // true
|
||||
$bookTitleColumn->isPrimaryKey(); // false
|
||||
$bookTitleColumn->isForeignKey(); // false
|
||||
$bookTitleColumn->hasValidators(); // false
|
||||
}}}
|
||||
|
||||
`ColumnMap` objects also keep a reference to their parent `TableMap` object:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$bookTable = $bookTitleColumn->getTable();
|
||||
}}}
|
||||
|
||||
Foreign key columns give access to more information, including the related table and column:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$bookPublisherIdColumn = $bookTable->getColumn('publisher_id');
|
||||
echo $bookPublisherIdColumn->isForeignKey(); // true
|
||||
echo $bookPublisherIdColumn->getRelatedName(); // 'publisher.ID'
|
||||
echo $bookPublisherIdColumn->getRelatedTableName(); // 'publisher'
|
||||
echo $bookPublisherIdColumn->getRelatedColumnName(); // 'ID'
|
||||
$publisherTable = $bookPublisherIdColumn->getRelatedTable();
|
||||
$publisherRelation = $bookPublisherIdColumn->getRelation();
|
||||
}}}
|
||||
|
||||
== RelationMaps ==
|
||||
|
||||
To get an insight on all the relationships of a table, including the ones relying on a foreign key located in another table, you must use the `RelationMap` objects related to a table.
|
||||
|
||||
If you know its name, you can retrieve a `RelationMap` object using `TableMap::getRelation($relationName)`. Note that the relation name is the phpName of the related table, unless the foreign key defines a phpName in the schema. For instance, the name of the `RelationMap` object related to the `book.PUBLISHER_ID` column is 'Publisher'.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$publisherRelation = $bookTable->getRelation('Publisher');
|
||||
}}}
|
||||
|
||||
alternatively, you can access a `RelationMap` from a foreign key column using `ColumnMap::getRelation()`, as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$publisherRelation = $bookTable->getColumn('publisher_id')->getRelation();
|
||||
}}}
|
||||
|
||||
Once you have a `RelationMap` instance, inspect its properties using any of the following methods:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
echo $publisherRelation->getType(); // RelationMap::MANY_TO_ONE
|
||||
echo $publisherRelation->getOnDelete(); // 'SET NULL'
|
||||
$bookTable = $publisherRelation->getLocalTable();
|
||||
$publisherTable = $publisherRelation->getForeignTable();
|
||||
print_r($publisherRelation->getColumnMappings());
|
||||
// array('book.PUBLISHER_ID' => 'publisher.ID')
|
||||
print_r(publisherRelation->getLocalColumns());
|
||||
// array($bookPublisherIdColumn)
|
||||
print_r(publisherRelation->getForeignColumns());
|
||||
// array($publisherBookIdColumn)
|
||||
}}}
|
||||
|
||||
This also works for relationships referencing the current table:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$reviewRelation = $bookTable->getRelation('Review');
|
||||
echo $reviewRelation->getType(); // RelationMap::ONE_TO_MANY
|
||||
echo $reviewRelation->getOnDelete(); // 'CASCADE'
|
||||
$reviewTable = $reviewRelation->getLocalTable();
|
||||
$bookTable = $reviewRelation->getForeignTable();
|
||||
print_r($reviewRelation->getColumnMappings());
|
||||
// array('review.BOOK_ID' => 'book.ID')
|
||||
}}}
|
||||
|
||||
To retrieve all the relations of a table, call `TableMap::getRelations()`. You can then iterate over an array of `RelationMap` objects.
|
||||
|
||||
Tip: RelationMap objects are lazy-loaded, which means that the `TableMap` will not instanciate any relation object until you call `getRelations()`. This allows the `TableMap` to remain lightweight for when you don't use relationship introspection.
|
424
library/propel/docs/cookbook/Writing-Behavior.txt
Executable file
424
library/propel/docs/cookbook/Writing-Behavior.txt
Executable file
|
@ -0,0 +1,424 @@
|
|||
= How to Write A Behavior =
|
||||
|
||||
Behaviors are a good way to reuse code across models without requiring inheritance (a.k.a. horizontal reuse). This step-by-step tutorial explains how to port model code to a behavior, focusing on a simple example.
|
||||
|
||||
In the tutorial "[http://propel.posterous.com/getting-to-know-propel-15-keeping-an-aggregat Keeping an Aggregate Column up-to-date]", posted in the [http://propel.posterous.com/ Propel blog], the `TotalNbVotes` property of a `PollQuestion` object was updated each time a related `PollAnswer` object was saved, edited, or deleted. This "aggregate column" behavior was implemented by hand using hooks in the model classes. To make it truly reusable, the custom model code needs to be refactored and moved to a Behavior class.
|
||||
|
||||
== Boostrapping A Behavior ==
|
||||
|
||||
A behavior is a class that can alter the generated classes for a table of your model. It must only extend the [browser:branches/1.5/generator/lib/model/Behavior.php `Behavior`] class and implement special "hook" methods. Here is the class skeleton to start with for the `aggregate_column` behavior:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class AggregateColumnBehavior extends Behavior
|
||||
{
|
||||
// default parameters value
|
||||
protected $parameters = array(
|
||||
'name' => null,
|
||||
);
|
||||
}
|
||||
}}}
|
||||
|
||||
Save this class in a file called `AggregateColumnBehavior.php`, and set the path for the class file in the project `build.properties` (just replace directory separators with dots). Remember that the `build.properties` paths are relative to the include path:
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
propel.behavior.aggregate_column.class = path.to.AggregateColumnBehavior
|
||||
}}}
|
||||
|
||||
Test the behavior by adding it to a table of your model, for instance to a `poll_question` table:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="poll" defaultIdMethod="native">
|
||||
<table name="poll_question" phpName="PollQuestion">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="body" type="VARCHAR" size="100" />
|
||||
<behavior name="aggregate_column">
|
||||
<parameter name="name" value="total_nb_votes" />
|
||||
</behavior>
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
Rebuild your model, and check the generated `PollQuestionTableMap` class under the `map` subdirectory of your build class directory. This class carries the structure metadata for the `PollQuestion` ActiveRecord class at runtime. The class should feature a `getBehaviors()` method as follows, proving that the behavior was correctly applied:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class PollQuestionTableMap extends TableMap
|
||||
{
|
||||
// ...
|
||||
|
||||
public function getBehaviors()
|
||||
{
|
||||
return array(
|
||||
'aggregate_column' => array('name' => 'total_nb_votes', ),
|
||||
);
|
||||
} // getBehaviors()
|
||||
}
|
||||
}}}
|
||||
|
||||
== Adding A Column ==
|
||||
|
||||
The behavior works, but it still does nothing at all. Let's make it useful by allowing it to add a column. In the `AggregateColumnBehavior` class, just implement the `modifyTable()` method with the following code:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class AggregateColumnBehavior extends Behavior
|
||||
{
|
||||
// ...
|
||||
|
||||
public function modifyTable()
|
||||
{
|
||||
$table = $this->getTable();
|
||||
if (!$columnName = $this->getParameter('name')) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'You must define a \'name\' parameter for the \'aggregate_column\' behavior in the \'%s\' table',
|
||||
$table->getName()
|
||||
));
|
||||
}
|
||||
// add the aggregate column if not present
|
||||
if(!$table->containsColumn($columnName)) {
|
||||
$table->addColumn(array(
|
||||
'name' => $columnName,
|
||||
'type' => 'INTEGER',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
This method shows that a behavior class has access to the `<parameters>` defined for it in the `schema.xml` through the `getParameter()` command. Behaviors can also always access the `Table` object attached to them, by calling `getTable()`. A `Table` can check if a column exists and add a new one easily. The `Table` class is one of the numerous generator classes that serve to describe the object model at buildtime, together with `Column`, `ForeignKey`, `Index`, and a lot more classes. You can find all the buildtime model classes under the [browser:branches/1.5/generator/lib/model generator/lib/model] directory.
|
||||
|
||||
'''Tip''': Don't mix up the ''runtime'' database model (`DatabaseMap`, `TableMap`, `ColumnMap`, `ValidatorMap`, `RelationMap`) with the ''buildtime'' database model (`Database`, `Table`, `Column`, `Validator`, etc.). The buildtime model is very detailed, in order to ease the work of the builders that write the ActiveRecord and Query classes. On the other hand, the runtime model is optimized for speed, and carries minimal information to allow correct hydration and binding at runtime. Behaviors use the buildtime object model, because they are run at buildtime, so they have access to the most powerful model.
|
||||
|
||||
Now rebuild the model and the SQL, and sure enough, the new column is there. `BasePollQuestion` offers a `getTotalNbVotes()` and a `setTotalNbVotes()` method, and the table creation SQL now includes the additional `total_nb_votes` column:
|
||||
|
||||
{{{
|
||||
#!sql
|
||||
DROP TABLE IF EXISTS poll_question;
|
||||
CREATE TABLE poll_question
|
||||
(
|
||||
id INTEGER NOT NULL AUTO_INCREMENT,
|
||||
title VARCHAR(100),
|
||||
total_nb_votes INTEGER,
|
||||
PRIMARY KEY (id)
|
||||
)Type=InnoDB;
|
||||
}}}
|
||||
|
||||
'''Tip''': The behavior only adds the column if it's not present (`!$table->containsColumn($columnName)`). So if a user needs to customize the column type, or any other attribute, he can include a `<column>` tag in the table with the same name as defined in the behavior, and the `modifyTable()` will then skip the column addition.
|
||||
|
||||
== Adding A Method To The ActiveRecord Class ==
|
||||
|
||||
In the previous post, a method of the ActiveRecord class was in charge of updating the `total_nb_votes` column. A behavior can easily add such methods by implementing the `objectMethods()` method:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class AggregateColumnBehavior extends Behavior
|
||||
{
|
||||
// ...
|
||||
|
||||
public function objectMethods()
|
||||
{
|
||||
$script = '';
|
||||
$script .= $this->addUpdateAggregateColumn();
|
||||
return $script;
|
||||
}
|
||||
|
||||
protected function addUpdateAggregateColumn()
|
||||
{
|
||||
$sql = sprintf('SELECT %s FROM %s WHERE %s = ?',
|
||||
$this->getParameter('expression'),
|
||||
$this->getParameter('foreign_table'),
|
||||
$this->getParameter('foreign_column')
|
||||
);
|
||||
$table = $this->getTable();
|
||||
$aggregateColumn = $table->getColumn($this->getParameter('name'));
|
||||
$columnPhpName = $aggregateColumn->getPhpName();
|
||||
$localColumn = $table->getColumn($this->getParameter('local_column'));
|
||||
return "
|
||||
/**
|
||||
* Updates the aggregate column {$aggregateColumn->getName()}
|
||||
*
|
||||
* @param PropelPDO \$con A connection object
|
||||
*/
|
||||
public function update{$columnPhpName}(PropelPDO \$con)
|
||||
{
|
||||
\$sql = '{$sql}';
|
||||
\$stmt = \$con->prepare(\$sql);
|
||||
\$stmt->execute(array(\$this->get{$localColumn->getPhpName()}()));
|
||||
\$this->set{$columnPhpName}(\$stmt->fetchColumn());
|
||||
\$this->save(\$con);
|
||||
}
|
||||
";
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
The ActiveRecord class builder expects a string in return to the call to `Behavior::objectMethods()`, and appends this string to the generated code of the ActiveRecord class. Don't bother about indentation: builder classes know how to properly indent a string returned by a behavior. A good rule of thumb is to create one behavior method for each added method, to provide better readability.
|
||||
|
||||
Of course, the schema must be modified to supply the necessary parameters to the behavior:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="poll" defaultIdMethod="native">
|
||||
<table name="poll_question" phpName="PollQuestion">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="body" type="VARCHAR" size="100" />
|
||||
<behavior name="aggregate_column">
|
||||
<parameter name="name" value="total_nb_votes" />
|
||||
<parameter name="expression" value="count(nb_votes)" />
|
||||
<parameter name="foreign_table" value="poll_answer" />
|
||||
<parameter name="foreign_column" value="question_id" />
|
||||
<parameter name="local_column" value="id" />
|
||||
</behavior>
|
||||
</table>
|
||||
<table name="poll_answer" phpName="PollAnswer">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="question_id" required="true" type="INTEGER" />
|
||||
<column name="body" type="VARCHAR" size="100" />
|
||||
<column name="nb_votes" type="INTEGER" />
|
||||
<foreign-key foreignTable="poll_question" onDelete="cascade">
|
||||
<reference local="question_id" foreign="id" />
|
||||
</foreign-key>
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
Now if you rebuild the model, you will see the new `updateTotalNbVotes()` method in the generated `BasePollQuestion` class:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class BasePollQuestion extends BaseObject
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Updates the aggregate column total_nb_votes
|
||||
*
|
||||
* @param PropelPDO $con A connection object
|
||||
*/
|
||||
public function updateTotalNbVotes(PropelPDO $con)
|
||||
{
|
||||
$sql = 'SELECT count(nb_votes) FROM poll_answer WHERE question_id = ?';
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute(array($this->getId()));
|
||||
$this->setTotalNbVotes($stmt->fetchColumn());
|
||||
$this->save($con);
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
Behaviors offer similar hook methods to allow the addition of methods to the query classes (`queryMethods()`) and to the peer classes (`peerMethods()`). And if you need to add attributes, just implement one of the `objectAttributes()`, `queryAttributes()`, or `peerAttributes()` methods.
|
||||
|
||||
== Using a Template For Generated Code ==
|
||||
|
||||
The behavior's `addUpdateAggregateColumn()` method is somehow hard to read, because of the large string containing the PHP code canvas for the added method. Propel behaviors can take advantage of Propel's simple templating system to use an external file as template for the code to insert.
|
||||
|
||||
Let's refactor the `addUpdateAggregateColumn()` method to take advantage of this feature:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class AggregateColumnBehavior extends Behavior
|
||||
{
|
||||
// ...
|
||||
|
||||
protected function addUpdateAggregateColumn()
|
||||
{
|
||||
$sql = sprintf('SELECT %s FROM %s WHERE %s = ?',
|
||||
$this->getParameter('expression'),
|
||||
$this->getParameter('foreign_table'),
|
||||
$this->getParameter('foreign_column')
|
||||
);
|
||||
$table = $this->getTable();
|
||||
$aggregateColumn = $table->getColumn($this->getParameter('name'));
|
||||
return $this->renderTemplate('objectUpdateAggregate', array(
|
||||
'aggregateColumn' => $aggregateColumn,
|
||||
'columnPhpName' => $aggregateColumn->getPhpName(),
|
||||
'localColumn' => $table->getColumn($this->getParameter('local_column')),
|
||||
'sql' => $sql,
|
||||
));
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
The method no longer returns a string created by hand, but a ''rendered template''. Propel templates are simple PHP files executed in a sandbox - they have only access to the variables declared as second argument of the `renderTemplate()` call.
|
||||
|
||||
Now create a `templates/` directory in the same directory as the `AggregateColumnBehavior` class file, and add in a `objectUpdateAggregate.php` file with the following code:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
/**
|
||||
* Updates the aggregate column <?php echo $aggregateColumn->getName() ?>
|
||||
*
|
||||
* @param PropelPDO $con A connection object
|
||||
*/
|
||||
public function update<?php echo $columnPhpName ?>(PropelPDO $con)
|
||||
{
|
||||
$sql = '<?php echo $sql ?>';
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute(array($this->get<?php echo $localColumn->getPhpName() ?>()));
|
||||
$this->set<?php echo $columnPhpName ?>($stmt->fetchColumn());
|
||||
$this->save($con);
|
||||
}
|
||||
}}}
|
||||
|
||||
No need to escape dollar signs anymore: this syntax allows for a cleaner separation, and is very convenient for large behaviors.
|
||||
|
||||
== Adding Another Behavior From A Behavior ==
|
||||
|
||||
This is where it's getting tricky. In the [http://propel.posterous.com/getting-to-know-propel-15-keeping-an-aggregat blog post] describing the column aggregation technique, the calls to the `updateTotalNbVotes()` method come from the `postSave()` and `postDelete()` hooks of the `PollAnswer` class. But the current behavior is applied to the `poll_question` table, how can it modify the code of a class based on another table?
|
||||
|
||||
The short answer is: it can't. To modify the classes built for the `poll_answer` table, a behavior must be registered on the `poll_answer` table. But a behavior is just like a column or a foreign key: it has an object counterpart in the buildtime database model. So the trick here is to modify the `AggregateColumnBehavior::modifyTable()` method to ''add a new behavior'' to the foreign table. This second behavior will be in charge of implementing the `postSave()` and `postDelete()` hooks of the `PollAnswer` class.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class AggregateColumnBehavior extends Behavior
|
||||
{
|
||||
// ...
|
||||
|
||||
public function modifyTable()
|
||||
{
|
||||
// ...
|
||||
|
||||
// add a behavior to the foreign table to autoupdate the aggregate column
|
||||
$foreignTable = $table->getDatabase()->getTable($this->getParameter('foreign_table'));
|
||||
if (!$foreignTable->hasBehavior('concrete_inheritance_parent')) {
|
||||
require_once 'AggregateColumnRelationBehavior.php';
|
||||
$relationBehavior = new AggregateColumnRelationBehavior();
|
||||
$relationBehavior->setName('aggregate_column_relation');
|
||||
$relationBehavior->addParameter(array(
|
||||
'name' => 'foreign_table',
|
||||
'value' => $table->getName()
|
||||
));
|
||||
$relationBehavior->addParameter(array(
|
||||
'name' => 'foreign_column',
|
||||
'value' => $this->getParameter('name')
|
||||
));
|
||||
$foreignTable->addBehavior($relationBehavior);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
In practice, everything now happens as if the `poll_answer` had its own behavior:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="poll" defaultIdMethod="native">
|
||||
<!-- ... -->
|
||||
<table name="poll_answer" phpName="PollAnswer">
|
||||
<!-- ... -->
|
||||
<behavior name="aggregate_column_relation">
|
||||
<parameter name="foreign_table" value="poll_question" />
|
||||
<parameter name="foreign_column" value="total_nb_votes" />
|
||||
</behavior>
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
Adding a behavior to a `Table` instance, as well as adding a `Parameter` to a `Behavior` instance, is quite straightforward. And since the second behavior class file is required in the `modifyTable()` method, there is no need to add a path for it in the `build.properties`.
|
||||
|
||||
== Adding Code For Model Hooks ==
|
||||
|
||||
The new `AggregateColumnRelationBehavior` is yet to write. It must implement a call to `PollQuestion::updateTotalNbVotes()` in the `postSave()` and `postDelete()` hooks.
|
||||
|
||||
Adding code to hooks from a behavior is just like adding methods: add a method with the right hook name returning a code string, and the code will get appended at the right place. Unsurprisingly, the behavior hook methods for `postSave()` and `postDelete()` are called `postSave()` and `postDelete()`:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class AggregateColumnBehavior extends Behavior
|
||||
{
|
||||
// default parameters value
|
||||
protected $parameters = array(
|
||||
'foreign_table' => null,
|
||||
'foreignColumn' => null,
|
||||
);
|
||||
|
||||
public function postSave()
|
||||
{
|
||||
$table = $this->getTable();
|
||||
$foreignTable = $table->getDatabase()->getTable($this->getParameter('foreign_table'));
|
||||
$foreignColumn = $foreignTable->getColumn($this->getParameter('foreign_column'));
|
||||
$foreignColumnPhpName = $foreignColumn->getPhpName();
|
||||
return "\$this->updateRelated{$foreignColumnPhpName}(\$con)";
|
||||
}
|
||||
|
||||
public function postDelete()
|
||||
{
|
||||
return $this->postSave();
|
||||
}
|
||||
|
||||
public function objectMethods()
|
||||
{
|
||||
$script = '';
|
||||
$script .= $this->addUpdateRelatedAggregateColumn();
|
||||
return $script;
|
||||
}
|
||||
|
||||
protected function addUpdateRelatedAggregateColumn()
|
||||
{
|
||||
$table = $this->getTable();
|
||||
$foreignTable = $table->getDatabase()->getTable($this->getParameter('foreign_table'));
|
||||
$foreignTablePhpName = foreignTable->getPhpName();
|
||||
$foreignColumn = $foreignTable->getColumn($this->getParameter('foreign_column'));
|
||||
$foreignColumnPhpName = $foreignColumn->getPhpName();
|
||||
return "
|
||||
/**
|
||||
* Updates an aggregate column in the foreign {$foreignTable->getName()} table
|
||||
*
|
||||
* @param PropelPDO \$con A connection object
|
||||
*/
|
||||
protected function updateRelated{$foreignColumnPhpName}(PropelPDO \$con)
|
||||
{
|
||||
if (\$parent{$foreignTablePhpName} = \$this->get{$foreignTablePhpName}()) {
|
||||
\$parent{$foreignTablePhpName}->update{$foreignColumnPhpName}(\$con);
|
||||
}
|
||||
}
|
||||
";
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
The `postSave()` and `postDelete()` behavior hooks will not add code to the ActiveRecord `postSave()` and `postDelete()` methods - to allow users to further implement these methods - but instead it adds code directly to the `save()` and `delete()` methods, inside a transaction. Check the generated `BasePollAnswer` class for the added code in these methods:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// aggregate_column_relation behavior
|
||||
$this->updateRelatedTotalNbVotes($con);
|
||||
}}}
|
||||
|
||||
You will also see the new `updateRelatedTotalNbVotes()` method added by `AggregateColumnBehavior::objectMethods()`:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
/**
|
||||
* Updates an aggregate column in the foreign poll_question table
|
||||
*
|
||||
* @param PropelPDO $con A connection object
|
||||
*/
|
||||
protected function updateRelatedTotalNbVotes(PropelPDO $con)
|
||||
{
|
||||
if ($parentPollQuestion = $this->getPollQuestion()) {
|
||||
$parentPollQuestion->updateTotalNbVotes($con);
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
== What's Left ==
|
||||
|
||||
These are the basics of behavior writing: implement one of the methods documented in the [wiki:Documentation/1.5/Behaviors#WritingaBehavior behaviors chapter] of the Propel guide, and return strings containing the code to be added to the ActiveRecord, Query, and Peer classes. In addition to the behavior code, you should always write unit tests - all the behaviors bundled with Propel have full unit test coverage. And to make your behavior usable by others, documentation is highly recommended. Once again, Propel core behaviors are fully documented, to let users understand the behavior usage without having to peek into the code.
|
||||
|
||||
As for the `AggregateColumnBehavior`, the job is not finished. The [http://propel.posterous.com/getting-to-know-propel-15-keeping-an-aggregat blog post] emphasized the need for hooks in the Query class, and these are not yet implemented in the above code. Besides, the post kept quiet about one use case that left the aggregate column not up to date (when a question is detached from a poll without deleting it). Lastly, the parameters required for this behavior are currently a bit verbose, especially concerning the need to define the foreign table and the foreign key - this could be simplified thanks to the knowledge of the object model that behaviors have.
|
||||
|
||||
All this is left to the reader as an exercise. Fortunately, the final behavior is part of the Propel core behaviors, so the [browser:branches/1.5/generator/lib/behavior/aggregate_column code], [browser:branches/1.5/test/testsuite/generator/behavior/aggregate_column unit tests], and [wiki:Documentation/1.5/Behaviors/aggregate_column documentation] are all ready to help you to further understand the power of Propel's behavior system.
|
164
library/propel/docs/guide/01-Installation.txt
Normal file
164
library/propel/docs/guide/01-Installation.txt
Normal file
|
@ -0,0 +1,164 @@
|
|||
= Installing Propel =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
Propel is available as a [http://pear.php.net/manual/en/installation.getting.php PEAR] package, as a "traditional" tgz or zip package, and as a checkout from a Subversion repository. Whatever installation method you may choose, getting Propel to work is pretty straightforward.
|
||||
|
||||
== Prerequisites ==
|
||||
|
||||
Propel requirements are very light, allowing it to run on most PHP platforms:
|
||||
|
||||
* [http://www.php.net/ PHP 5.2.4] or newer, with the DOM (libxml2) module enabled
|
||||
* A supported database (MySQL, MS SQL Server, PostgreSQL, SQLite, Oracle)
|
||||
|
||||
'''Tip''': Propel uses the PDO and SPL components, which are bundled and enabled by default in PHP5.
|
||||
|
||||
== Propel Components ==
|
||||
|
||||
The Propel library is made of two components: a '''generator''', and a '''runtime library'''. These components are not co-dependent, and can be installed independently from each other.
|
||||
|
||||
The generator is needed to build the object model, but is not required for running applications that use Propel.
|
||||
|
||||
The runtime classes provide the shared functionality that is used by the Propel-generated object model classes. These are necessary to run applications that use Propel to access the database.
|
||||
|
||||
Usually, both the generator and the runtime components are installed on development environments, while the actual test or production servers need only the runtime components installed. For your first contact with Propel, just install both.
|
||||
|
||||
== Installing Propel ==
|
||||
|
||||
=== Installing Propel From PEAR ===
|
||||
|
||||
In order to install the Propel packages, you must add the `pear.propelorm.org` channel to your PEAR environment. Once the channel is discovered, you can install the generator package, or the runtime package, or both. Use the '-a' option to let PEAR download and install dependencies.
|
||||
|
||||
{{{
|
||||
#!sh
|
||||
> pear channel-discover pear.propelorm.org
|
||||
> pear install -a propel/propel_generator
|
||||
> pear install -a propel/propel_runtime
|
||||
}}}
|
||||
|
||||
Propel is now installed, and you can test it by following the instructions of the '''Testing Propel Installation''' section at the end of this page.
|
||||
|
||||
Tip: If you want to install non-stable versions of Propel, change your `preferred_state` PEAR environment variable before installoing the Propel packages. Valid states include 'stable', 'beta', 'alpha', and 'devel':
|
||||
|
||||
{{{
|
||||
#!sh
|
||||
> pear config-set preferred_state beta
|
||||
}}}
|
||||
|
||||
=== Dependencies for Tarball and Subversion Versions ===
|
||||
|
||||
The Propel generator uses [http://phing.info/ Phing 2.3.3] to manage command line tasks; both the generator and the runtime classes use [http://pear.php.net/package/Log/ PEAR Log] to log events.
|
||||
|
||||
If you choose to install Propel via PEAR, these components will be automatically installed as dependencies. If you choose to install Propel from a tarball or a Subversion checkout, you'll have to install them manually:
|
||||
|
||||
{{{
|
||||
#!sh
|
||||
> pear channel-discover pear.phing.info
|
||||
> pear install phing/phing
|
||||
> pear install Log
|
||||
}}}
|
||||
|
||||
Refer to their respective websites for alternative installation strategies for Phing and Log.
|
||||
|
||||
=== Installing Propel From Subversion ===
|
||||
|
||||
Installing from SVN trunk ensures that you have the most up-to-date source code.
|
||||
|
||||
{{{
|
||||
#!sh
|
||||
> svn checkout http://svn.propelorm.org/branches/1.5 /usr/local/propel
|
||||
}}}
|
||||
|
||||
This will export both the generator and runtime components to your local `propel` directory. In addition, you'll also get Propel documentation and unit tests - that's why this method is the preferred installation method for Propel contributors.
|
||||
|
||||
Once this is done, you'll need to setup your PHP environment to use this library - see the '''Setting Up PHP for Propel''' section below.
|
||||
|
||||
Note: `branches/1.5` is currently more uptodate code than `trunk`; trunk is what will become 2.0, however it has had very little work done to it in a long time.
|
||||
|
||||
=== Installing Propel From a Tarball ===
|
||||
|
||||
Download a tarball of each of the Propel components from the Propel website, and uncompress them into the location that best suits your need. For instance, in Linux:
|
||||
|
||||
{{{
|
||||
#!sh
|
||||
> cd /usr/local
|
||||
> mkdir propel
|
||||
> cd propel
|
||||
> wget http://pear.propelorm.org/get/propel_generator-1.5.0.tgz
|
||||
> tar zxvf propel_generator-1.5.0.tgz
|
||||
> wget http://pear.propelorm.org/get/propel_runtime-1.5.0.tgz
|
||||
> tar zxvf propel_runtime-1.5.0.tgz
|
||||
}}}
|
||||
|
||||
Once this is done, you'll need to setup your PHP environment to use this library.
|
||||
|
||||
== Setting Up PHP for the Propel Generator ==
|
||||
|
||||
The following instructions are only required if you installed Propel from a tarball, or from Subversion.
|
||||
|
||||
The Propel generator component bundles a `propel-gen` sh script (and a `propel-gen.bat` script for Windows). This script simplifies the commandline invocation of the Propel generator by hiding any references to Phing.
|
||||
|
||||
You can call it directly from the command line:
|
||||
|
||||
{{{
|
||||
#!sh
|
||||
> /usr/local/propel/generator/bin/propel-gen
|
||||
}}}
|
||||
|
||||
In order to allow an easier execution the script, you can also:
|
||||
|
||||
* add the propel generator's `bin/` directory to your PATH,
|
||||
* or copy the `propel-gen` script to a location on your PAH,
|
||||
* or (on Linux systems) create a symlink. For example:
|
||||
|
||||
{{{
|
||||
#!sh
|
||||
> cd /usr/local/bin
|
||||
> ln -s /usr/local/propel/generator/bin/propel-gen propel-gen
|
||||
}}}
|
||||
|
||||
== Testing Propel Installation ==
|
||||
|
||||
You can test that the '''Propel generator''' component is properly installed by calling the `propel-gen` script from the CLI:
|
||||
|
||||
{{{
|
||||
#!sh
|
||||
> propel-gen
|
||||
}}}
|
||||
|
||||
The script should output a few lines before displaying a 'BUILD FAILED' message, which is normal - you haven't defined a database model yet.
|
||||
|
||||
You can test that the '''Propel runtime''' component is properly installed by requiring the `Propel.php` script, as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// PEAR installation
|
||||
require_once 'propel/Propel.php';
|
||||
|
||||
// manual installation
|
||||
require_once '/usr/local/propel/runtime/lib/Propel.php';
|
||||
|
||||
// Propel setup code ... (to be discussed later)
|
||||
}}}
|
||||
|
||||
At this point, Propel should be setup and ready to use. You can follow the steps in the [wiki:Documentation/1.5/BuildTime Build Guide] to try it out.
|
||||
|
||||
== Troubleshooting ==
|
||||
|
||||
=== PHP Configuration ===
|
||||
|
||||
Propel requires the following settings in `php.ini`:
|
||||
|
||||
||'''Variable'''||'''Value'''||
|
||||
||ze1_compatibility_mode||Off||
|
||||
||magic_quotes_gpc||Off||
|
||||
||magic_quotes_sybase||Off||
|
||||
|
||||
=== PEAR Directory In Include Path ===
|
||||
|
||||
If you choose to install Propel via PEAR, and if it's your first use of PEAR, the PEAR directory may not be on your PHP `include_path`. Check the PEAR documentation for details on how to do that.
|
||||
|
||||
=== Getting Help ===
|
||||
|
||||
If you can't manage to install Propel, don't hesitate to ask for help. See [wiki:Support] for details on getting help.
|
350
library/propel/docs/guide/02-BuildTime.txt
Normal file
350
library/propel/docs/guide/02-BuildTime.txt
Normal file
|
@ -0,0 +1,350 @@
|
|||
= The Build Time =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
The initial step in every Propel project is the "build". During build time, a developer describes the structure of the datamodel in a XML file called the "schema". From this schema, Propel generates PHP classes, called "model classes", made of object-oriented PHP code optimized for a given RMDBS. The model classes are the primary interface to find and manipulate data in the database in Propel.
|
||||
|
||||
The XML schema can also be used to generate SQL code to setup your database. Alternatively, you can generate the schema from an existing database (this is described in another chapter of the documentation - see the [wiki:Documentation/1.5/Existing-Database reverse engineering chapter] for more details).
|
||||
|
||||
During build time, a developer also defines the connection settings for communicating with the database.
|
||||
|
||||
To illustrate Propel's build abilities, this chapter uses the data structure of a bookstore as an example. It is made of three tables: a `book` table, with a foreign key to two other tables, `author` and `publisher`.
|
||||
|
||||
== Describing Your Database as XML Schema ==
|
||||
|
||||
Propel generates PHP classes based on a ''relational'' description of your data model. This "schema" uses XML to describe tables, columns and relationships. The schema syntax closely follows the actual structure of the database.
|
||||
|
||||
Create a `bookstore` directory. This will be the root of the bookstore project.
|
||||
|
||||
=== Database Connection Name ===
|
||||
|
||||
Create a file called `schema.xml` in the new `bookstore/` directory.
|
||||
|
||||
The root tag of the XML schema is the `<database>` tag:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<database name="bookstore" defaultIdMethod="native">
|
||||
<!-- table definitions go here -->
|
||||
</database>
|
||||
}}}
|
||||
|
||||
The `name` attribute defines the name of the connection that Propel uses for the tables in this schema. It is not necessarily the name of the actual database. In fact, Propel uses a second file to link a connection name with real connection settings (like databae name, user and password). This `runtime-conf.xml` file will be explained later in this chapter.
|
||||
|
||||
The `defaultIdMethod` attribute indicates that the tables in this schema use the database's "native" auto-increment/sequence features to handle id columns that are set to auto-increment.
|
||||
|
||||
'''Tip''': You can define several schemas for a single project. Just make sure that each of the schema filenames end with `schema.xml`.
|
||||
|
||||
=== Tables And Columns ===
|
||||
|
||||
Within the `<database>` tag, Propel expects one `<table>` tag for each table:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<database name="bookstore" defaultIdMethod="native">
|
||||
<table name="book" phpName="Book">
|
||||
<!-- column and foreign key definitions go here -->
|
||||
</table>
|
||||
<table name="author" phpName="Author">
|
||||
<!-- column and foreign key definitions go here -->
|
||||
</table>
|
||||
<table name="publisher" phpName="Publisher">
|
||||
<!-- column and foreign key definitions go here -->
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
This time, the `name` attributes are the real table names. The `phpName` is the name that Propel will use for the generated PHP class. By default, Propel uses a CamelCase version of the table name as its phpName - that means that you could omit the `phpName` attribute in the example above.
|
||||
|
||||
Within each set of `<table>` tags, define the columns that belong to that table:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<database name="bookstore" defaultIdMethod="native">
|
||||
<table name="book" phpName="Book">
|
||||
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="title" type="varchar" size="255" required="true" />
|
||||
<column name="isbn" type="varchar" size="24" required="true" phpName="ISBN"/>
|
||||
<column name="publisher_id" type="integer" required="true"/>
|
||||
<column name="author_id" type="integer" required="true"/>
|
||||
</table>
|
||||
<table name="author" phpName="Author">
|
||||
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="first_name" type="varchar" size="128" required="true"/>
|
||||
<column name="last_name" type="varchar" size="128" required="true"/>
|
||||
</table>
|
||||
<table name="publisher" phpName="Publisher">
|
||||
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
|
||||
<column name="name" type="varchar" size="128" required="true" />
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
Each column has a `name` (the one used by the database), and an optional `phpName` attribute. Once again, the Propel default behavior is to use a CamelCase version of the `name` as `phpName` when not specified.
|
||||
|
||||
Each column also requires a `type`. The XML schema is database agnostic, so the column types and attributes are probably not exactly the same as the one you use in your own database. But Propel knows how to map the schema types with SQL types for many database vendors. Existing Propel column types are boolean, tinyint, smallint, integer, bigint, double, float, real, decimal, char, varchar, longvarchar, date, time, timestamp, blob, and clob. Some column types use a `size` (like `varchar` and `int`), some have unlimited size (`longvarchar`, `clob`, `blob`).
|
||||
|
||||
As for the other column attributes, `required`, `primaryKey`, and `autoIncrement`, they mean exactly what their names suppose.
|
||||
|
||||
'''Tip''': Propel supports namespaces (for PHP > 5.3). If you specify a `namespace` attribute in a `<table>` element, the generated PHP classes for this table will use this namespace.
|
||||
|
||||
=== Foreign Keys ===
|
||||
|
||||
A table can have several `<foreign-key>` tags, describing foreign keys to foreign tables. Each `<foreign-key>` tag consists of one or more mappings between a local column and a foreign column.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<database name="bookstore" defaultIdMethod="native">
|
||||
<table name="book" phpName="Book">
|
||||
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="title" type="varchar" size="255" required="true" />
|
||||
<column name="isbn" type="varchar" size="24" required="true" phpName="ISBN"/>
|
||||
<column name="publisher_id" type="integer" required="true"/>
|
||||
<column name="author_id" type="integer" required="true"/>
|
||||
<foreign-key foreignTable="publisher" phpName="Publisher" refPhpName="Book">
|
||||
<reference local="publisher_id" foreign="id"/>
|
||||
</foreign-key>
|
||||
<foreign-key foreignTable="author">
|
||||
<reference local="author_id" foreign="id"/>
|
||||
</foreign-key>
|
||||
</table>
|
||||
<table name="author" phpName="Author">
|
||||
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="first_name" type="varchar" size="128" required="true"/>
|
||||
<column name="last_name" type="varchar" size="128" required="true"/>
|
||||
</table>
|
||||
<table name="publisher" phpName="Publisher">
|
||||
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
|
||||
<column name="name" type="varchar" size="128" required="true" />
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
A foreign key represents a relationship. Just like a table or a column, a relationship has a `phpName`. By default, Propel uses the `phpName` of the foreign table as the `phpName` of the relation. The `refPhpName` defines the name of the relation as seen from the foreign table.
|
||||
|
||||
There are many more attributes and elements available to describe a datamodel. Propel's documentation provides a complete [wiki:Documentation/1.5/Schema reference of the schema syntax], together with a [source:branches/1.5/generator/resources/dtd/database.dtd DTD] and a [source:branches/1.5/generator/resources/xsd/database.xsd XSD] schema for its validation.
|
||||
|
||||
== Building The Model ==
|
||||
|
||||
=== Setting Up Build Configuration ===
|
||||
|
||||
The build process is highly customizable. Whether you need the generated classes to inherit one of your classes rather than Propel's base classes, or to enable/disable some methods in the generated classes, pretty much every customization is possible. Of course, Propel provides sensible defaults, so that you actually need to define only two settings for the build process to start: the RDBMS you are going to use, and a name for your project.
|
||||
|
||||
Propel expects the build configuration to be stored in a file called `build.properties`, and stored at the same level as the `schema.xml`. Here is an example for a MySQL database:
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
# Database driver
|
||||
propel.database = mysql
|
||||
|
||||
# Project name
|
||||
propel.project = bookstore
|
||||
}}}
|
||||
|
||||
Use your own database vendor driver, chosen among pgsql, mysql, sqlite, mssql, and oracle.
|
||||
|
||||
You can learn more about the available build settings and their possible values in the [wiki:Documentation/1.5/BuildConfiguration build configuration reference].
|
||||
|
||||
=== Using the `propel-gen` Script To Build The Model ===
|
||||
|
||||
The Propel generator uses the `propel-gen` script, as seen in the previous chapter. This executable expects a command name as its argument.
|
||||
|
||||
Open a terminal and browse to the `bookstore/` directory, where you saved the two previous files (`schema.xml`, and `build.properties`). Then use the `propel-gen` script to call the "Object Model generator" command using its shortcut - "om":
|
||||
|
||||
{{{
|
||||
> cd /path/to/bookstore
|
||||
> propel-gen om
|
||||
}}}
|
||||
|
||||
You should normally see a some colored lines appear in the terminal, logging all the class generation, and ending with "BUILD FINISHED". If not, look for red lines in the log and follow the directions in the error messages.
|
||||
|
||||
=== Generated Object Model ===
|
||||
|
||||
The "om" command added a new directory in the `bookstore/` project, called `build/`. The generated model classes are located under the `classes/bookstore/` subdirectory:
|
||||
|
||||
{{{
|
||||
> cd /path/to/bookstore
|
||||
> cd build/classes/bookstore/
|
||||
> ls
|
||||
om/
|
||||
map/
|
||||
Author.php
|
||||
AuthorPeer.php
|
||||
AuthorQuery.php
|
||||
Book.php
|
||||
BookPeer.php
|
||||
BookQuery.php
|
||||
Publisher.php
|
||||
PublisherPeer.php
|
||||
PublisherQuery.php
|
||||
}}}
|
||||
|
||||
For every table in the database, Propel creates 3 PHP classes:
|
||||
|
||||
* a ''model'' class (e.g. `Book`), which represents a row in the database;
|
||||
* a ''peer'' class (e.g. `BookPeer`), offering static constants and methods mostly for compatibility with previous Propel versions;
|
||||
* a ''query'' class (e.g. `BookQuery`), used to operate on a table to retrieve and update rows
|
||||
|
||||
Propel uses the `phpName` attribute of each table as the base for the PHP class names.
|
||||
|
||||
All these classes are empty, but they inherit from `Base` classes that you will find under the `om/` directory:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
require 'om/BaseBook.php';
|
||||
|
||||
class Book extends BaseBook
|
||||
{
|
||||
}
|
||||
}}}
|
||||
|
||||
These empty classes are called ''stub'' classes. This is where you will add your own model code. These classes are generated only once by Propel ; on the other hand, the ''base'' classes they extend are overwritten every time you call the `om` command, and that happens a lot in the course of a project, because the schema evolves with your needs.
|
||||
|
||||
In addition to these classes, Propel generates one `TableMap` class for each table under the `map/` directory. You will probably never use the map classes directly, but Propel needs them to get metadata information about the table structure at runtime.
|
||||
|
||||
'''Tip''': Never add any code of your own to the classes generated by Propel in the `om/` and `map/` directories; this code would be lost next time you call the `propel-gen` script.
|
||||
|
||||
Basically, all that means is that despite the fact that Propel generates ''seven'' classes for each table, you should only care about two of them: the model class and the query class.
|
||||
|
||||
== Building The Database ==
|
||||
|
||||
To save you the burden of defining your model twice, Propel can initialize a database based on the schema, by creating the tables and foreign keys.
|
||||
|
||||
=== Building The SQL File ===
|
||||
|
||||
Once again, use the `propel-gen` script to generate the SQL files necessary to create the tables, this time with the "sql" command:
|
||||
|
||||
{{{
|
||||
> cd /path/to/bookstore
|
||||
> propel-gen sql
|
||||
}}}
|
||||
|
||||
The generated SQL definition can be found in the `build/sql/schema.sql` file. The code is optimized for the database driver defined in the `build.properties`.
|
||||
|
||||
=== Using The SQL File ===
|
||||
|
||||
Create the database and setup the access permissions using your favorite database client. For instance, to create the `my_db_name` database with MySQL, type:
|
||||
|
||||
{{{
|
||||
> mysqladmin -u root -p create my_db_name
|
||||
}}}
|
||||
|
||||
Now you can use the generated code directly:
|
||||
|
||||
{{{
|
||||
> mysql -u root -p my_db_name < build/sql/schema.sql
|
||||
}}}
|
||||
|
||||
'''Tip''': The `schema.sql` file will DROP any existing table before creating them, which will effectively erase your database.
|
||||
|
||||
Depending on which RDBMS you are using, it may be normal to see some errors (e.g. "unable to DROP...") when you first run this command. This is because some databases have no way of checking to see whether a database object exists before attempting to DROP it (MySQL is a notable exception). It is safe to disregard these errors, and you can always run the script a second time to make sure that the errors are no longer present.
|
||||
|
||||
=== Inserting SQL With `propel-gen` ===
|
||||
|
||||
As an alternative to using the generated sql code directly, you can ask Propel to insert it directly into your database. Start by defining the database connection settings in the `build.properties`, as follows:
|
||||
|
||||
{{{
|
||||
# Connection parameters
|
||||
propel.database.url = mysql:host=localhost;dbname=my_db_name
|
||||
propel.database.user = my_db_user
|
||||
propel.database.password = my_db_password
|
||||
|
||||
# Other examples:
|
||||
# propel.database.url = sqlite:/path/to/bookstore.db
|
||||
# propel.database.url = pgsql:host=localhost dbname=my_db_name user=my_db_user password=my_db_password
|
||||
}}}
|
||||
|
||||
The `propel.database.url` setting should be a PDO DSN (see the [http://www.php.net/pdo PDO documentation] for more information about vendor-specific DSN). The `user` and `password` are only necessary for the `mysql` and `oracle` drivers.
|
||||
|
||||
Then use the `propel-gen` script with the "insert-sql" command to connect to the database and inject the generated SQL code:
|
||||
|
||||
{{{
|
||||
> cd /path/to/bookstore
|
||||
> propel-gen insert-sql
|
||||
}}}
|
||||
|
||||
== Runtime Connection Settings ==
|
||||
|
||||
The database and PHP classes are now ready to be used. But they don't know yet how to communicate with each other at runtime. You must add a configuration file so that the generated object model classes and the shared Propel runtime classes can connect to the database, and log the Propel activity.
|
||||
|
||||
=== Writing The XML Runtime Configuration ===
|
||||
|
||||
Create a file called `runtime-conf.xml` at the root of the `bookstore` project, using the following content:
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config>
|
||||
<!-- Uncomment this if you have PEAR Log installed
|
||||
<log>
|
||||
<type>file</type>
|
||||
<name>/path/to/propel.log</name>
|
||||
<ident>propel-bookstore</ident>
|
||||
<level>7</level>
|
||||
</log>
|
||||
-->
|
||||
<propel>
|
||||
<datasources default="bookstore">
|
||||
<datasource id="bookstore">
|
||||
<adapter>mysql</adapter> <!-- sqlite, mysql, myssql, oracle, or pgsql -->
|
||||
<connection>
|
||||
<dsn>mysql:host=localhost;dbname=my_db_name</dsn>
|
||||
<user>my_db_user</user>
|
||||
<password>my_db_password</password>
|
||||
</connection>
|
||||
</datasource>
|
||||
</datasources>
|
||||
</propel>
|
||||
</config>
|
||||
}}}
|
||||
|
||||
Notice how the `id` attribute of the `<datasource>` tag matches the connection name defined in the `<database>` tag of the `schema.xml`. This is how Propel maps a database description to a connection.
|
||||
|
||||
Replace the `<adapter>` and the `<connection>` settings wit hthe ones of your database.
|
||||
|
||||
See the [wiki:Documentation/1.5/RuntimeConfiguration runtime configuration reference] for a more detailed explanation of this file.
|
||||
|
||||
'''Tip''': If you uncomment the `<log>` section, Propel will attempt to instantiate the `Log` class (from the [http://pear.php.net/package/Log/ PEAR Log] package) with the specified parameters and use that to log queries. Propel's statement logging happens at the DEBUG level (7); errors and warnings are logged at the appropriate (non-debug) level.
|
||||
|
||||
=== Building the Runtime Configuration ===
|
||||
|
||||
For performance reasons, Propel prefers to use a PHP version of the connection settings rather than the XML file you just defined. So you must use the `propel-gen` script one last time to build the PHP version of the `runtime-conf.xml` configuration:
|
||||
|
||||
{{{
|
||||
> cd /path/to/bookstore
|
||||
> propel-gen convert-conf
|
||||
}}}
|
||||
|
||||
The resulting file can be found under `build/conf/bookstore-conf.php`, where "bookstore" is the name of the project you defined in `build.properties`.
|
||||
|
||||
'''Tip''': As you saw, a Propel project setup requires that you call three commands with the `propel-gen` script: `om`, `sql`, and `convert-conf`. This is so usual that if you call the `propel-gen` script with no parameter, it will execute these three commands in a row:
|
||||
|
||||
{{{
|
||||
> cd /path/to/bookstore
|
||||
> propel-gen
|
||||
}}}
|
||||
|
||||
== Setting Up Propel ==
|
||||
|
||||
This is the final step: initialize Propel in your PHP script. You may wish to do this step in an init or setup script that is included at the beginning of your PHP scripts.
|
||||
|
||||
Here is a sample initialization file:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// Include the main Propel script
|
||||
require_once 'propel/Propel.php';
|
||||
|
||||
// Initialize Propel with the runtime configuration
|
||||
Propel::init("/path/to/bookstore/build/conf/bookstore-conf.php");
|
||||
|
||||
// Add the generated 'classes' directory to the include path
|
||||
set_include_path("/path/to/bookstore/build/classes" . PATH_SEPARATOR . get_include_path());
|
||||
}}}
|
||||
|
||||
Now you are ready to start using your model classes!
|
319
library/propel/docs/guide/03-Basic-CRUD.txt
Normal file
319
library/propel/docs/guide/03-Basic-CRUD.txt
Normal file
|
@ -0,0 +1,319 @@
|
|||
= Basic C.R.U.D. Operations =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
In this chapter, you will learn how to perform basic C.R.U.D. (Create, Retrieve, Update, Delete) operations on your database using Propel.
|
||||
|
||||
== Creating Rows ==
|
||||
|
||||
To add new data to the database, instantiate a Propel-generated object and then call the `save()` method. Propel will generate the appropriate INSERT SQL from the instantiated object.
|
||||
|
||||
But before saving it, you probably want to define the value for its columns. For that purpose, Propel has generated a `setXXX()` method for each of the columns of the table in the model object. So in its simplest form, inserting a new row looks like the following:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
/* initialize Propel, etc. */
|
||||
|
||||
$author = new Author();
|
||||
$author->setFirstName('Jane');
|
||||
$author->setLastName('austen');
|
||||
$author->save();
|
||||
}}}
|
||||
|
||||
The column names used in the `setXXX()` methods correspond to the `phpName` attribute of the `<column>` tag in your schema, or to a CamelCase version of the column name if the `phpName` is not set.
|
||||
|
||||
In the background, the call to `save()` results in the following SQL being executed on the database:
|
||||
{{{
|
||||
#!sql
|
||||
INSERT INTO author (first_name, last_name) VALUES ('Jane', 'Austen');
|
||||
}}}
|
||||
|
||||
== Reading Object Properties ==
|
||||
|
||||
Propel maps the columns of a table into properties of the generated objects. For each property, you can use a generated getter to access it.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
echo $author->getId(); // 1
|
||||
echo $author->getFirstName(); // 'Jane'
|
||||
echo $author->getLastName(); // 'austen'
|
||||
}}}
|
||||
|
||||
The `id` column was set automatically by the database, since the `schema.xml` defines it as an `autoIncrement` column. The value is very easy to retrieve once the object is saved: just call the getter on the column phpName.
|
||||
|
||||
These calls don't issue a database query, since the `Author` object is already loaded in memory.
|
||||
|
||||
== Retrieving Rows ==
|
||||
|
||||
Retrieving objects from the database, also referred to as ''hydrating'' objects, is essentially the process of executing a SELECT query against the database and populating a new instance of the appropriate object with the contents of each returned row.
|
||||
|
||||
In Propel, you use the generated Query objects to select existing rows from the database.
|
||||
|
||||
=== Retrieving by Primary Key ===
|
||||
|
||||
The simplest way to retrieve a row from the database, is to use the generated `findPK()` method. It simply expects the value of the primary key of the row to be retrieved.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$q = new AuthorQuery();
|
||||
$firstAuthor = $q->findPK(1);
|
||||
// now $firstBook is an Author object, or NULL if no match was found.
|
||||
}}}
|
||||
|
||||
This issues a simple SELECT SQL query. For instance, for MySQL:
|
||||
|
||||
{{{
|
||||
#!sql
|
||||
SELECT author.id, author.first_name, author.last_name
|
||||
FROM `author`
|
||||
WHERE author.id = 1
|
||||
LIMIT 1;
|
||||
}}}
|
||||
|
||||
When the primary key consists of more than one column, `findPK()` accepts multiple parameters, one for each primary key column.
|
||||
|
||||
'''Tip''': Every generated Query objects offers a factory method called `create()`. This methods creates a new instance of the query, and allows you to write queries in a single line:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$firstAuthor = AuthorQuery::create()->findPK(1);
|
||||
}}}
|
||||
|
||||
You can also select multiple objects based on their primary keys, by calling the generated `findPKs()` method. It takes an array of primary keys as a parameter:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$selectedAuthors = AuthorQuery::create()->findPKs(array(1,2,3,4,5,6,7));
|
||||
// $selectedAuthors is a collection of Author objects
|
||||
}}}
|
||||
|
||||
=== Querying the Database ===
|
||||
|
||||
To retrieve rows other than by the primary key, use the Query's `find()` method.
|
||||
|
||||
An empty Query object carries no condition, and returns all the rows of the table
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$authors = AuthorQuery::create()->find();
|
||||
// $authors contains a collection of Author objects
|
||||
// one object for every row of the author table
|
||||
foreach($authors as $author) {
|
||||
echo $author->getFirstName();
|
||||
}
|
||||
}}}
|
||||
|
||||
To add a simple condition on a given column, use one of the generated `filterByXXX()` methods of the Query object, where `XXX` is a column phpName. Since `filterByXXX()` methods return the current query object, you can continue to add conditions or end the query with the result of the method call. For instance, to filter by first name:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$authors = AuthorQuery::create()
|
||||
->filterByFirstName('Jane')
|
||||
->find();
|
||||
}}}
|
||||
|
||||
When you pass a value to a `filterByXXX()` method, Propel uses the column type to escape this value in PDO. This protects you from SQL injection risks.
|
||||
|
||||
You can also easily limit and order the results on a query. Once again, the Query methods return the current Query object, so you can easily chain them:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$authors = AuthorQuery::create()
|
||||
->orderByLastName()
|
||||
->limit(10)
|
||||
->find();
|
||||
}}}
|
||||
|
||||
`find()` always returns a collection of objects, even if there is only one result. If you know that you need a single result, use `findOne()` instead of `find()`. It will add the limit and return a single object instead of an array:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = AuthorQuery::create()
|
||||
->filterByFirstName('Jane')
|
||||
->findOne();
|
||||
}}}
|
||||
|
||||
'''Tip''': Propel provides magic methods for this simple use case. So you can write the above query as:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = AuthorQuery::create()->findOneByFirstName('Jane');
|
||||
}}}
|
||||
|
||||
The Propel Query API is very powerful. The next chapter will teach you to use it to add conditions on related objects. If you can't wait, jump to the [wiki:Documentation/1.5/ModelCriteria Query API reference].
|
||||
|
||||
=== Using Custom SQL ===
|
||||
|
||||
The `Query` class provides a relatively simple approach to constructing a query. Its database neutrality and logical simplicity make it a good choice for expressing many common queries. However, for a very complex query, it may prove more effective (and less painful) to simply use a custom SQL query to hydrate your Propel objects.
|
||||
|
||||
As Propel uses PDO to query the underlying database, you can always write custom queries using the PDO syntax. For instance, if you have to use a sub-select:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$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' => 'Tolstoy');
|
||||
}}}
|
||||
|
||||
With only a little bit more work, you can also populate `Book` objects from the resulting statement. Create a new `PropelObjectCollection` for the `Book` model, and call the `format()` method using the statement:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$coll = new PropelObjectCollection();
|
||||
$coll->setModelName('Book');
|
||||
$books = $coll->format($stmt);
|
||||
// $books contains a collection of Book objects
|
||||
}}}
|
||||
|
||||
There are a few important things to remember when using custom SQL to populate Propel:
|
||||
* The resultset columns must be numerically indexed
|
||||
* The resultset must contain all columns in the object
|
||||
* The resultset must have columns ''in the same order'' as they are defined in the `schema.xml` file
|
||||
|
||||
== Updating Objects ==
|
||||
|
||||
Updating database rows basically involves retrieving objects, modifying the contents, and then saving them. In practice, for Propel, this is a combination of what you've already seen in the previous sections:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = AuthorQuery::create()->findOneByFirstName('Jane');
|
||||
$author->setLastName('Austen');
|
||||
$author->save();
|
||||
}}}
|
||||
|
||||
Alternatively, you can update several rows based on a Query using the query object's `update()` method:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
AuthorQuery::create()
|
||||
->filterByFirstName('Jane')
|
||||
->update(array('LastName' => 'Austen'));
|
||||
}}}
|
||||
|
||||
This last method is better for updating several rows at once, or if you didn't retrieve the objects before.
|
||||
|
||||
== Deleting Objects ==
|
||||
|
||||
Deleting objects works the same as updating them. You can either delete an existing object:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = AuthorQuery::create()->findOneByFirstName('Jane');
|
||||
$author->delete();
|
||||
}}}
|
||||
|
||||
Or use the `delete()` method in the query:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
AuthorQuery::create()
|
||||
->filterByFirstName('Jane')
|
||||
->delete();
|
||||
}}}
|
||||
|
||||
'''Tip''': A deleted object still lives in the PHP code. It is marked as deleted and cannot be saved anymore, but you can still read its properties:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
echo $author->isDeleted(); // true
|
||||
echo $author->getFirstName(); // 'Jane'
|
||||
}}}
|
||||
|
||||
== Termination Methods ==
|
||||
|
||||
The Query methods that don't return the current query object are called "Termination Methods". You've alread seen come of them: `find()`, `findOne()`, `update()`, `delete()`. There are two more termination methods that you should know about:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// count() returns the number of results of the query.
|
||||
$nbAuthors = AuthorQuery::create()->count();
|
||||
// You could also count the number of results from a find(), but that would be less effective,
|
||||
// since it implies hydrating objects just to count them
|
||||
|
||||
// paginate() returns a paginated list of results
|
||||
$authorPager = AuthorQuery::create()->paginate($page = 1, $maxPerPage = 10);
|
||||
// This method will compute an offset and a limit
|
||||
// based on the number of the page and the max number of results per page.
|
||||
// The result is a PropelModelPager object, over which you can iterate:
|
||||
foreach ($authorPager as $author) {
|
||||
echo $author->getFirstName();
|
||||
}
|
||||
// a pager object gives more information
|
||||
echo $pager->getNbResults(); // total number of results if not paginated
|
||||
echo $pager->haveToPaginate(); // return true if the total number of results exceeds the maximum per page
|
||||
echo $pager->getFirstIndex(); // index of the first result in the page
|
||||
echo $pager->getLastIndex(); // index of the last result in the page
|
||||
$links = $pager->getLinks(5); // array of page numbers around the current page; useful to display pagination controls
|
||||
}}}
|
||||
|
||||
== 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
|
||||
<?php
|
||||
$authors = AuthorQuery::create()
|
||||
->limit(5)
|
||||
->find();
|
||||
foreach ($authors as $author) {
|
||||
echo $authors->getFirstName();
|
||||
}
|
||||
}}}
|
||||
|
||||
The 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 when retrieving a large number of results. Available through the `setFormatter()` method of Model Queries, on-demand hydration is very easy to trigger:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$authors = AuthorQuery::create()
|
||||
->limit(50000)
|
||||
->setFormatter(ModelCriteria::FORMAT_ON_DEMAND) // just add this line
|
||||
->find();
|
||||
foreach ($authors as $author) {
|
||||
echo $author->getFirstName();
|
||||
}
|
||||
}}}
|
||||
|
||||
In this example, Propel will hydrate the `Author` 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 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 [wiki:Documentation/1.5/ModelCriteria Query API reference] describes each formatter, and how to use it.
|
||||
|
||||
== Propel Instance Pool ==
|
||||
|
||||
Propel keeps a list of the objects that you already retrieved in memory to avoid calling the same request twice in a PHP script. This list is called the instance pool, and is automatically populated from your past requests:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// first call
|
||||
$author1 = AuthorQuery::create()->findPk(1);
|
||||
// Issues a SELECT query
|
||||
...
|
||||
// second call
|
||||
$author2 = AuthorQuery::create()->findPk(1);
|
||||
// Skips the SQL query and returns the existing $author1 object
|
||||
}}}
|
386
library/propel/docs/guide/04-Relationships.txt
Normal file
386
library/propel/docs/guide/04-Relationships.txt
Normal file
|
@ -0,0 +1,386 @@
|
|||
= Basic Relationships =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
The definition of foreign keys in your schema allows Propel to add smart methods to the generated model and query objects. In practice, these generated methods mean that you will never actually have to deal with primary and foreign keys yourself. It makes the task of dealing with relations extremely straightforward.
|
||||
|
||||
== Inserting A Related Row ==
|
||||
|
||||
Propel creates setters for related objects that simplify the foreign key handling. You don't actually have to define a foreign key value. Instead, just set a related object, as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = new Author();
|
||||
$author->setFirstName("Leo");
|
||||
$author->setLastName("Tolstoy");
|
||||
$author->save();
|
||||
|
||||
$book = new Book();
|
||||
$book->setTitle("War & Peace");
|
||||
// associate the $author object with the current $book
|
||||
$book->setAuthor($author);
|
||||
$book->save();
|
||||
}}}
|
||||
|
||||
Propel generates the `setAuthor()` method based on the `phpName` attribute of the `<foreign-key>` element in the schema. When the attribute is not set, Propel uses the `phpName` of the related table instead.
|
||||
|
||||
Internally, the call to `Book::setAuthor($author)` translates into `Book::setAuthorId($author->getId())`. But you don't actually have to save a Propel object before associating it to another. In fact, Propel automatically "cascades" INSERT statements when a new object has other related objects added to it.
|
||||
|
||||
For one-to-many relationships - meaning, from the other side of a many-to-one relationship - the process is a little different. In the previous example, one `Book` has one `Author`, but one `Author` has many `Books`. From the `Author` point of view, a one-to-many relationships relates it to `Book`. So Propel doesn't generate an `Author::setBook()`, but rather an `Author::addBook()`:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book = new Book();
|
||||
$book->setTitle("War & Peace");
|
||||
// associate the $author object with the current $book
|
||||
$book->save();
|
||||
|
||||
$author = new Author();
|
||||
$author->setFirstName("Leo");
|
||||
$author->setLastName("Tolstoy");
|
||||
$author->addBook($book);
|
||||
$author->save();
|
||||
}}}
|
||||
|
||||
The result is the same in the database - the `author_id` column of the `book` row is correctly set to the `id` of the `author` row.
|
||||
|
||||
== Save Cascade ==
|
||||
|
||||
As a matter of fact, you don't need to `save()` an object before relating it. Propel knows which objects are related to each other, and is capable of saving all the unsaved objects if they are related to each other.
|
||||
|
||||
The following example shows how to create new `Author` and `Publisher` objects, which are then added to a new `Book` object; all 3 objects are saved when the `Book::save()` method is eventually invoked.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
/* initialize Propel, etc. */
|
||||
|
||||
$author = new Author();
|
||||
$author->setFirstName("Leo");
|
||||
$author->setLastName("Tolstoy");
|
||||
// no need to save the author yet
|
||||
|
||||
$publisher = new Publisher();
|
||||
$publisher->setName("Viking Press");
|
||||
// no need to the publisher yet
|
||||
|
||||
$book = new Book();
|
||||
$book->setTitle("War & Peace");
|
||||
$book->setIsbn("0140444173");
|
||||
$book->setPublisher($publisher);
|
||||
$book->setAuthor($author);
|
||||
$book->save(); // saves all 3 objects!
|
||||
}}}
|
||||
|
||||
In practice, Propel '''cascades''' the `save()` action to the related objects.
|
||||
|
||||
== Reading Related Object Properties ==
|
||||
|
||||
Just like the related object setters, Propel generates a getter for every relation:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book = BookQuery()::create()->findPk(1);
|
||||
$author = $book->getAuthor();
|
||||
echo $author->getFirstName(); // 'Leo'
|
||||
}}}
|
||||
|
||||
Since a relationship can also be seen from the other end, Propel allows the foreign table to retrieve the related objects as well:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = AuthorQuery::create()->findPk(1);
|
||||
$books = $author->getBooks();
|
||||
foreach ($books as $book) {
|
||||
echo $book->getTitle();
|
||||
}
|
||||
}}}
|
||||
|
||||
Notice that Propel generated a `getBooks()` method returning an array of `Book` objects, rather than a `getBook()` method. This is because the definition of a foreign key defines a many-to-one relationship, seen from the other end as a one-to-many relationship.
|
||||
|
||||
'''Tip''': Propel also generates a `countBooks()` methods to get the number of related objects without hydrating all the `Book` objects. for performance reasons, you should prefer this method to `count($author->getBooks())`.
|
||||
|
||||
Getters for one-to-many relationship accept an optional query object. This allows you to hydrate related objects, or retrieve only a subset of the related objects, or to reorder the list of results:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$query = BookQuery::create()
|
||||
->orderByTitle()
|
||||
->joinWith('Book.Publisher');
|
||||
$books = $author->getBooks($query);
|
||||
}}}
|
||||
|
||||
== Using Relationships In A Query ==
|
||||
|
||||
=== Finding Records Related To Another One ===
|
||||
|
||||
If you need to find objects related to a model object that you already have, you can take advantage of the generated `filterByXXX()` methods in the query objects, where `XXX` is a relation name:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$author = AuthorQuery::create()->findPk(1);
|
||||
$books = BookQuery::create()
|
||||
->filterByAuthor($author)
|
||||
->orderByTitle()
|
||||
->find();
|
||||
}}}
|
||||
|
||||
You don't need to specify that the `author_id` column of the `Book` object should match the `id` column of the `Author` object. Since you already defined the foreign key mapping in your schema, Propel knows enough to figure it out.
|
||||
|
||||
=== Embedding Queries ===
|
||||
|
||||
In SQL queries, relationships often translate to a JOIN statement. Propel abstracts this relational logic in the query objects, by allowing you to ''embed'' a related query into another.
|
||||
|
||||
In practice, Propel generates one `useXXXQuery()` method for every relation in the Query objects. So the `BookQuery` class offers a `useAuthorQuery()` and a `usePublisherQuery()` method. These methods return a new Query instance of the related query class, that you can eventually merge into the main query by calling `endUse()`.
|
||||
|
||||
To illustrate this, let's see how to write the following SQL query with the Propel Query API:
|
||||
|
||||
{{{
|
||||
#!sql
|
||||
SELECT book.*
|
||||
FROM book INNER JOIN author ON book.AUTHOR_ID = author.ID
|
||||
WHERE book.ISBN = '0140444173' AND author.FIRST_NAME = 'Leo'
|
||||
ORDER BY book.TITLE ASC
|
||||
LIMIT 10;
|
||||
}}}
|
||||
|
||||
That would simply give:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$books = BookQuery::create()
|
||||
->filterByISBN('0140444173')
|
||||
->useAuthorQuery() // returns a new AuthorQuery instance
|
||||
->filterByFirstName('Leo') // this is an AuthorQuery method
|
||||
->endUse() // merges the Authorquery in the main Bookquery and returns the BookQuery
|
||||
->orderByTitle()
|
||||
->limit(10)
|
||||
->find();
|
||||
}}}
|
||||
|
||||
Propel knows the columns to use in the `ON` clause from the definition of foreign keys in the schema. The ability to use methods of a related Query object allows you to keep your model logic where it belongs.
|
||||
|
||||
Of course, you can embed several queries to issue a query of any complexity level:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// Find all authors of books published by Viking Press
|
||||
$authors = AuthorQuery::create()
|
||||
->useBookQuery()
|
||||
->usePublisherQuery()
|
||||
->filterByName('Viking Press')
|
||||
->endUse()
|
||||
->endUse()
|
||||
->find();
|
||||
}}}
|
||||
|
||||
You can see how the indentation of the method calls provide a clear explanation of the embedding logic. That's why it is a good practice to format your Propel queries with a single method call per line, and to add indentation every time a `useXXXQuery()` method is used.
|
||||
|
||||
== Many-to-Many Relationships ==
|
||||
|
||||
Databases typically use a cross-reference table, or junction table, to materialize the relationship. 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. To inform Propel about the many-to-many relationship, set the `isCrossRef` attribute of the cross reference table to true:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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>
|
||||
}}}
|
||||
|
||||
Once you rebuild your model, the relationship is seen as a one-to-many relationship from both the `User` and the `Group` models. That means that you can deal with adding and reading relationships the same way as you usually do:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$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();
|
||||
}}}
|
||||
|
||||
The same happens for reading related objects ; Both ends see the relationship as a one-to-many relationship:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$users = $group->getUsers();
|
||||
$nbUsers = $group->countUsers();
|
||||
$groups = $user->getGroups();
|
||||
$nbGroups = $user->countGroups();
|
||||
}}}
|
||||
|
||||
Just like regular related object getters, these generated methods accept an optional query object, to further filter the results.
|
||||
|
||||
To facilitate queries, Propel also adds new methods to the `UserQuery` and `GroupQuery` classes:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$users = UserQuery::create()
|
||||
->filterByGroup($group)
|
||||
->find();
|
||||
$groups = GroupQuery::create()
|
||||
->filterByUser($user)
|
||||
->find();
|
||||
}}}
|
||||
|
||||
== One-to-One Relationships ==
|
||||
|
||||
Propel supports the special case of one-to-one relationships. These relationships are defined when the primary key is also a foreign key. For example :
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="bookstore_employee" description="Employees of a bookstore">
|
||||
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="name" type="VARCHAR" size="32"/>
|
||||
</table>
|
||||
|
||||
<table name="bookstore_employee_account" description="Bookstore employees' login credentials">
|
||||
<column name="employee_id" type="INTEGER" primaryKey="true"/>
|
||||
<column name="login" type="VARCHAR" size="32"/>
|
||||
<column name="password" type="VARCHAR" size="100"/>
|
||||
<foreign-key foreignTable="bookstore_employee">
|
||||
<reference local="employee_id" foreign="id"/>
|
||||
</foreign-key>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Because the primary key of the `bookstore_employee_account` is also a foreign key to the `bookstore_employee` table, Propel interprets this as a one-to-one relationship and will generate singular methods for both sides of the relationship (`BookstoreEmployee::getBookstoreEmployeeAccount()`, and `BookstoreEmployeeAccount::getBookstoreEmployee()`).
|
||||
|
||||
== On-Update and On-Delete Triggers =
|
||||
|
||||
Propel also supports the ''ON UPDATE'' and ''ON DELETE'' aspect of foreign keys. These properties can be specified in the `<foreign-key>` tag using the `onUpdate` and `onDelete` attributes. Propel supports values of `CASCADE`, `SETNULL`, and `RESTRICT` for these attributes. For databases that have native foreign key support, these trigger events will be specified at the datbase level when the foreign keys are created. For databases that do not support foreign keys, this functionality will be emulated by Propel.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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"/>
|
||||
<foreign-key foreignTable="book" onDelete="CASCADE">
|
||||
<reference local="book_id" foreign="id"/>
|
||||
</foreign-key>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
In the example above, the `review` rows will be automatically removed if the related `book` row is deleted.
|
||||
|
||||
== Minimizing Queries ==
|
||||
|
||||
Even if you use a foreign query, Propel will issue new queries when you fetch related objects:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book = BookQuery::create()
|
||||
->useAuthorQuery()
|
||||
->filterByFirstName('Leo')
|
||||
->endUse()
|
||||
->findOne();
|
||||
$author = $book->getAuthor(); // Needs another database query
|
||||
}}}
|
||||
|
||||
Propel allows you to retrieve the main object together with related objects in a single query. You just the `with()` method to specify which objects the main object should be hydrated with.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book = BookQuery::create()
|
||||
->useAuthorQuery()
|
||||
->filterByFirstName('Leo')
|
||||
->endUse()
|
||||
->with('Author')
|
||||
->findOne();
|
||||
$author = $book->getAuthor(); // Same result, with no supplementary query
|
||||
}}}
|
||||
|
||||
Since the call to `with()` adds the columns of the related object to the SELECT part of the query, and uses these columns to populate the related object, that means that a query using `with()` is slower and consumes more memory. So use it only when you actually need the related objects afterwards.
|
||||
|
||||
If you don't want to add a filter on a related object but still need to hydrate it, calling `useXXXQuery()`, `endUse()`, and then `with()` can be a little cumbersome. For this case, Propel provides a proxy method called `joinWith()`. It expects a string made of the initial query name and the foreign query name. For instance:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book = BookQuery::create()
|
||||
->joinWith('Book.Author')
|
||||
->findOne();
|
||||
$author = $book->getAuthor(); // Same result, with no supplementary query
|
||||
}}}
|
||||
|
||||
`with()` and `joinWith()` are not limited to immediate relationships. As a matter of fact, just like you can nest `use()` calls, you can call `with()` several times to populate a chain of objects:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$review = ReviewQuery::create()
|
||||
->joinWith('Review.Book')
|
||||
->joinWith('Book.Author')
|
||||
->joinWith('Book.Publisher')
|
||||
->findOne();
|
||||
$book = $review->getBook() // No additional query needed
|
||||
$author = $book->getAuthor(); // No additional query needed
|
||||
$publisher = $book->getPublisher(); // No additional query needed
|
||||
}}}
|
||||
|
||||
So `with()` is very useful to minimize the number of database queries. As soon as you see that the number of queries necessary to perform an action is proportional to the number of results, adding a `with()` call is the trick to get down to a more reasonnable query count.
|
||||
|
||||
'''Tip''': `with()` also works for left joins on one-to-many relationships, but you musn't use a `limit()` in the query in this case. This is because Propel has no way to determine the actual number of rows of the main object in such a case.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// this works
|
||||
$authors = AuthorQuery::create()
|
||||
->leftJoinWith('Author.Book')
|
||||
->find();
|
||||
// this does not work
|
||||
$authors = AuthorQuery::create()
|
||||
->leftJoinWith('Author.Book')
|
||||
->limit(5)
|
||||
->find();
|
||||
}}}
|
||||
|
||||
However, it is quite easy to achieve hydration of related objects with only one additional query:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// $authors is a PropelObjectCollection
|
||||
$authors = AuthorQuery::create()->find();
|
||||
$authors->populateRelation('Book');
|
||||
// now you can iterate over each author's book without further queries
|
||||
foreach ($authors as $author) {
|
||||
foreach ($authors->getBooks() as $book) { // no database query, the author already has a Books collection
|
||||
// do stuff with $book and $author
|
||||
}
|
||||
}
|
||||
}}}
|
253
library/propel/docs/guide/05-Validators.txt
Normal file
253
library/propel/docs/guide/05-Validators.txt
Normal file
|
@ -0,0 +1,253 @@
|
|||
= Validators =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
Validators help you to validate an input before perstisting it to the database. In Propel, validators are rules describing what type of data a column accepts. Validators are referenced in the `schema.xml` file, using `<validator>` tags.
|
||||
|
||||
Validators are applied at the PHP level, they are not created as constraints on the database itself. That means that if you also use another language to work with the database, the validator rules will not be enforced.
|
||||
You can also apply multiple rule entries per validator entry in the schema.xml file.
|
||||
|
||||
== Overview ==
|
||||
|
||||
In the following example, the `username` column is defined to have a minimum length of 4 characters:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="user">
|
||||
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="username" type="VARCHAR" size="34" required="true" />
|
||||
<validator column="username">
|
||||
<rule name="minLength" value="4" message="Username must be at least ${value} characters !" />
|
||||
</validator>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Every column rule is represented by a `<rule>` tag. A `<validator>` is a set of `<rule>` tags bound to a column.
|
||||
|
||||
At runtime, you can validate an instance of the model by calling the `validate()` method:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$user = new User();
|
||||
$user->setUsername("foo"); // only 3 in length, which is too short...
|
||||
if ($objUser->validate()) {
|
||||
// no validation errors, so the data can be persisted
|
||||
$user->save();
|
||||
} else {
|
||||
// Something went wrong.
|
||||
// Use the validationFailures to check what
|
||||
$failures = $objUser->getValidationFailures();
|
||||
foreach($failures as $failure) {
|
||||
echo $objValidationFailure->getMessage() . "<br />\n";
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
`validate()` returns a boolean. If the validation failed, you can access the array `ValidationFailed` objects by way of the `getValidationFailures()` method. Each `ValidationFailed` instance gives access to the column, the messagen and the validator that caused the failure.
|
||||
|
||||
== Core Validators ==
|
||||
|
||||
Propel bundles a set of validatorts that should help you deal with the most common cases.
|
||||
|
||||
=== !MatchValidator ===
|
||||
|
||||
The `MatchValidator` is used to run a regular expression of choice against the column. Note that this is a `preg`, not `ereg` (check [http://www.php.net/preg_match the preg_match documentation] for more information about regexps).
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<validator column="username">
|
||||
<!-- allow strings that match the email adress pattern -->
|
||||
<rule
|
||||
name="match"
|
||||
value="/^([a-zA-Z0-9])+([\.a-zA-Z0-9_-])*@([a-zA-Z0-9])+(\.[a-zA-Z0-9_-]+)+$/"
|
||||
message="Please enter a valid email address." />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
=== !NotMatchValidator ===
|
||||
|
||||
Opposite of `MatchValidator, this validator returns false if the regex returns true
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="ISBN" type="VARCHAR" size="20" required="true" />
|
||||
<validator column="ISBN">
|
||||
<!-- disallow everything that's not a digit or minus -->
|
||||
<rule
|
||||
name="notMatch"
|
||||
value="/[^\d-]+/"
|
||||
message="Please enter a valid ISBN" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
=== !MaxLengthValidator ===
|
||||
|
||||
When you want to limit the size of the string to be inserted in a column, use the `MaxLengthValidator`. Internally, it uses `strlen()` to get the length of the string. For instance, some database completely ignore the lentgh of `LONGVARCHAR` columns; you can enforce it using a validator:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="comment" type="LONGVARCHAR" required="true" />
|
||||
<validator column="comment">
|
||||
<rule
|
||||
name="maxLength"
|
||||
value="1024"
|
||||
message="Comments can be no larger than ${value} in size" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
'''Tip''': If you have specified the `size` attribute in the `<column>` tag, you don't have to specify the `value` attribute in the validator rule again, as this is done automatically.
|
||||
|
||||
=== !MinLengthValidator ===
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="username" type="VARCHAR" size="34" required="true" />
|
||||
<validator column="username">
|
||||
<rule
|
||||
name="minLength"
|
||||
value="4"
|
||||
message="Username must be at least ${value} characters !" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
=== !MaxValueValidator ===
|
||||
|
||||
To limit the value of an integer column, use the `MaxValueValidator`. Note that this validator uses a non-strict comparison ('less than or equal'):
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="security_level" type="INTEGER" required="true" />
|
||||
<validator column="security_level">
|
||||
<rule
|
||||
name="maxValue"
|
||||
value="1000"
|
||||
message="Maximum security level is ${value} !" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
=== !MinValueValidator ===
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="cost" type="INTEGER" required="true" />
|
||||
<validator column="cost">
|
||||
<rule
|
||||
name="minValue"
|
||||
value="0"
|
||||
message="Products can cost us negative $ can they?" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
'''Tip''': You can run multiple validators against a single column.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="security_level" type="INTEGER" required="true" default="10" />
|
||||
<validator column="security_level" translate="none">
|
||||
<rule
|
||||
name="minValue"
|
||||
value="0"
|
||||
message="Invalid security level, range: 0-10" />
|
||||
<rule
|
||||
name="maxValue"
|
||||
value="10"
|
||||
message="Invalid security level, range: 0-10" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
=== !RequiredValidator ===
|
||||
|
||||
This validtor checks the same rule as a `required=true` on the column at the database level. However it will not give you a clean error to work with.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="username" type="VARCHAR" size="25" required="true" />
|
||||
<validator column="username">
|
||||
<rule
|
||||
name="required"
|
||||
message="Username is required." />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
=== !UniqueValidator ===
|
||||
|
||||
To check whether the value already exists in the table, use the `UniqueValidator`:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="username" type="VARCHAR" size="25" required="true" />
|
||||
<validator column="username">
|
||||
<rule
|
||||
name="unique"
|
||||
message="Username already exists !" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
=== !ValidValuesValidator ===
|
||||
|
||||
This rule restricts the valid values to a list delimited by a pipe ('|').
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="address_type" type="VARCHAR" required="true" default="delivery" />
|
||||
<validator column="address_type">
|
||||
<rule
|
||||
name="validValues"
|
||||
value="account|delivery"
|
||||
message="Please select a valid address type." />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
=== !TypeValidator ===
|
||||
|
||||
Restrict values to a certain PHP type using the `TypeValidator`:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column name="username" type="VARCHAR" size="25" required="true" />
|
||||
<validator column="username">
|
||||
<rule
|
||||
name="type"
|
||||
value="string"
|
||||
message="Username must be a string" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
== Adding A Custom Validator ==
|
||||
|
||||
You can easily add a custom validator. A validator is a class extending `BasicValidator` providing a public `isValid()` method. For instance:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
require_once 'propel/validator/BasicValidator.php';
|
||||
|
||||
/**
|
||||
* A simple validator for email fields.
|
||||
*
|
||||
* @package propel.validator
|
||||
*/
|
||||
class EmailValidator implements BasicValidator
|
||||
{
|
||||
public function isValid(ValidatorMap $map, $str)
|
||||
{
|
||||
return preg_match('/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i', $str) !== 0;
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
The `ValidatorMap` instance passed as parameter gives you access to the rules attribute as defined in the `<rule>` tag. So `$map->getValue()` returns the `value` attribute.
|
||||
|
||||
'''Tip''': Make sure that `isValid()` returns a boolean, so really true or false. Propel is very strict about this. Returning a mixed value just won't do.
|
||||
|
||||
To enable the new validator on a column, add a corresponding `<rule>` in your schema and use 'class' as the rule `name`.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<validator column="<column_name>">
|
||||
<rule name="class" class="my.dir.EmailValidator" message="Invalid e-mail address!" />
|
||||
</validator>
|
||||
}}}
|
||||
|
||||
The `class` attribute of the `<rule>` tag should contain a path to the validator class accessible from the include_path, where the directory separator is replaced by a dot.
|
291
library/propel/docs/guide/06-Transactions.txt
Normal file
291
library/propel/docs/guide/06-Transactions.txt
Normal file
|
@ -0,0 +1,291 @@
|
|||
= Transactions =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
Database transactions are the key to assure the data integrity and the performance of database queries. Propel uses transactions internally, and provides a simple API to use them in your own code.
|
||||
|
||||
'''Tip''': If the [http://en.wikipedia.org/wiki/ACID ACID] acronym doesn't ring a bell, you should probably learn some [http://en.wikipedia.org/wiki/Database_transaction fundamentals about database transactions] before reading further.
|
||||
|
||||
== Wrapping Queries Inside a Transaction ==
|
||||
|
||||
Propel uses PDO as database abstraction layer, and therefore uses [http://www.php.net/manual/en/pdo.transactions.php PDO's built-in support for database transactions]. The syntax is the same, as you can see in the classical "money transfer" example:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
public function transferMoney($fromAccountNumber, $toAccountNumber, $amount)
|
||||
{
|
||||
// get the PDO connection object from Propel
|
||||
$con = Propel::getConnection(AccountPeer::DATABASE_NAME);
|
||||
|
||||
$fromAccount = AccountPeer::retrieveByPk($fromAccountNumber, $con);
|
||||
$toAccount = AccountPeer::retrieveByPk($toAccountNumber, $con);
|
||||
|
||||
$con->beginTransaction();
|
||||
|
||||
try {
|
||||
// remove the amount from $fromAccount
|
||||
$fromAccount->setValue($fromAccount->getValue() - $amount);
|
||||
$fromAccount->save($con);
|
||||
// add the amount to $toAccount
|
||||
$toAccount->setValue($toAccount->getValue() + $amount);
|
||||
$toAccount->save($con);
|
||||
|
||||
$con->commit();
|
||||
} catch (Exception $e) {
|
||||
$con->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
The transaction statements are `beginTransaction()`, `commit()` and `rollback()`, which are methods of the PDO connection object. Transaction methods are typically used inside a `try/catch` block. The exception is rethrown after rolling back the transaction: That ensures that the user knows that something wrong happenned.
|
||||
|
||||
In this example, if something wrong happens while saving either one of the two accounts, an `Exception` is thrown, and the whole operation is rolled back. That means that the transfer is cancelled, with an insurance that the money hasn't vanished (that's the A in ACID, which stands for "Atomicity"). If both account modifications work as expected, the whole transaction is committed, meaning that the data changes enclosed in the transaction are persisted in the database.
|
||||
|
||||
Tip: In order to build a transaction, you need a connection object. The connection object for a Propel model is always available through `Propel::getConnection([ModelName]Peer::DATABASE_NAME)`.
|
||||
|
||||
== Denormalization And Transactions ==
|
||||
|
||||
Another example of the use of transactions is for [http://en.wikipedia.org/wiki/Denormalization denormalized schemas].
|
||||
|
||||
For instance, suppose that you have an `Author` model with a one to many relationship to a `Book` model. every time you need to display the number of books written by an author, you call `countBooks()` on the author object, which issues a new query to the database:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<ul>
|
||||
<?php foreach ($authors as $author): ?>
|
||||
<li><?php echo $author->getName() ?> (<?php echo $author->countBooks() ?> books)</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
}}}
|
||||
|
||||
If you have a large number of authors and books, this simple code snippet can be a real performance blow to your application. The usual way to optimize it is to ''denormalize'' your schema by storing the number of books by each author in a new `nb_books` column, in the `author` table.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
|
||||
<column name="title" type="VARCHAR" required="true" />
|
||||
<column name="nb_books" type="INTEGER" default="0" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
You must update this new column every time you save or delete a `Book` object; this will make write queries a little slower, but read queries much faster. Fortunately, Propel model objects support pre- and post- hooks for the `save()` and `delete()` methods, so this is quite easy to implement:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class Book extends BaseBook
|
||||
{
|
||||
public function postSave(PropelPDO $con)
|
||||
{
|
||||
$this->updateNbBooks($con);
|
||||
}
|
||||
|
||||
public function postDelete(PropelPDO $con)
|
||||
{
|
||||
$this->updateNbBooks($con);
|
||||
}
|
||||
|
||||
public function updateNbBooks(PropelPDO $con)
|
||||
{
|
||||
$author = $this->getAuthor();
|
||||
$nbBooks = $author->countBooks($con);
|
||||
$author->setNbBooks($nbBooks);
|
||||
$author->save($con);
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
The `BaseBook::save()` method wraps the actual database INSERT/UPDATE query inside a transaction, together with any other query registered in a pre- or post- save hook. That means that when you save a book, the `postSave()` code is executed in the same transaction as the actual `$book->save()` method. Everything happens as is the code was the following:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class Book extends BaseBook
|
||||
{
|
||||
public function save(PropelPDO $con)
|
||||
{
|
||||
$con->beginTransaction();
|
||||
|
||||
try {
|
||||
// insert/update query for the current object
|
||||
$this->doSave($con);
|
||||
|
||||
// postSave hook
|
||||
$author = $this->getAuthor();
|
||||
$nbBooks = $author->countBooks($con);
|
||||
$author->setNbBooks($nbBooks);
|
||||
$author->save($con);
|
||||
|
||||
$con->commit();
|
||||
} catch (Exception $e) {
|
||||
$con->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
In this example, the `nb_books` column of the `author` table will always we synchronized with the number of books. If anything happens during the transaction, the saving of the book is rolled back, as well as the `nb_books` column update. The transaction serves to preserve data consistency in a denormalized schema ("Consistency" stands for the C in ACID).
|
||||
|
||||
'''Tip''': Check the [wiki:Documentation/1.5/Behaviors behaviors documentation] for details about the pre- and post- hooks in Propel model objects.
|
||||
|
||||
== Nested Transactions ==
|
||||
|
||||
Some RDBMS offer the ability to nest transactions, to allow partial rollback of a set of transactions. PDO does not provide this ability at the PHP level; nevertheless, Propel emulates nested transactions for all supported database engines:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
function deleteBooksWithNoPrice(PropelPDO $con)
|
||||
{
|
||||
$con->beginTransaction();
|
||||
try {
|
||||
$c = new Criteria();
|
||||
$c->add(BookPeer::PRICE, null, Criteria::ISNULL);
|
||||
BookPeer::doDelete($c, $con);
|
||||
$con->commit();
|
||||
} catch (Exception $e) {
|
||||
$con->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteAuthorsWithNoEmail(PropelPDO $con)
|
||||
{
|
||||
$con->beginTransaction();
|
||||
try {
|
||||
$c = new Criteria();
|
||||
$c->add(AuthorPeer::EMAIL, null, Criteria::ISNULL);
|
||||
AuthorPeer::doDelete($c, $con);
|
||||
$con->commit();
|
||||
} catch (Exception $e) {
|
||||
$con->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup(PropelPDO $con)
|
||||
{
|
||||
$con->beginTransaction();
|
||||
try {
|
||||
deleteBooksWithNoPrice($con);
|
||||
deleteAuthorsWithNoEmail($con);
|
||||
$con->commit();
|
||||
} catch (Exception $e) {
|
||||
$con->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
All three functions alter data in a transaction, ensuring data integrity for each. In addition, the `cleanup()` function actually executes two nested transactions inside one main transaction.
|
||||
|
||||
Propel deals with this case by seeing only the outermost transaction, and ignoring the `beginTransaction()`, `commit()` and `rollback()` statements of nested transactions. If nothing wrong happens, then the last `commit()` call (after both `deleteBooksWithNoPrice()` and `deleteAuthorsWithNoEmail()` end) triggers the actual database commit. However, if an exception is thrown in either one of these nested transactions, it is escalated to the main `catch` statement in `cleanup()` so that the entire transaction (starting at the main `beginTransaction()`) is rolled back.
|
||||
|
||||
So you can use transactions everywhere it's necessary in your code, without worrying about nesting them. Propel will always commit or rollback everything altogether, whether the RDBMS supports nested transactions or not.
|
||||
|
||||
'''Tip''': This allows you to wrap all your application code inside one big transaction for a better integrity.
|
||||
|
||||
== Using Transactions To Boost Performance ==
|
||||
|
||||
A database transaction has a cost in terms of performance. In fact, for simple data manipulation, the cost of the transaction is more important than the cost of the query itself. Take the following example:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(BookPeer::DATABASE_NAME);
|
||||
for ($i=0; $i<2002; $i++)
|
||||
{
|
||||
$book = new Book();
|
||||
$book->setTitle($i . ': A Space Odyssey');
|
||||
$book->save($con);
|
||||
}
|
||||
}}}
|
||||
|
||||
As explained earlier, Propel wraps every save operation inside a transaction. In terms of execution time, this is very expensive. Here is how the above code would translate to MySQL in an InnodDB table:
|
||||
|
||||
{{{
|
||||
#!sql
|
||||
BEGIN;
|
||||
INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'0: A Space Odyssey');
|
||||
COMMIT;
|
||||
BEGIN;
|
||||
INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'1: A Space Odyssey');
|
||||
COMMIT;
|
||||
BEGIN;
|
||||
INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'2: A Space Odyssey');
|
||||
COMMIT;
|
||||
...
|
||||
}}}
|
||||
|
||||
You can take advantage of Propel's nested transaction capabilities to encapsulate the whole loop inside one single transaction. This will reduce the execution time drastically:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(BookPeer::DATABASE_NAME);
|
||||
$con->beginTransaction();
|
||||
for ($i=0; $i<2002; $i++)
|
||||
{
|
||||
$book = new Book();
|
||||
$book->setTitle($i . ': A Space Odyssey');
|
||||
$book->save($con);
|
||||
}
|
||||
$con->commit();
|
||||
}}}
|
||||
|
||||
The transactions inside each `save()` will become nested, and therefore not translated into actual database transactions. Only the outmost transaction will become a database transaction. So this will translate to MySQL as:
|
||||
|
||||
{{{
|
||||
#!sql
|
||||
BEGIN;
|
||||
INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'0: A Space Odyssey');
|
||||
INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'1: A Space Odyssey');
|
||||
INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'2: A Space Odyssey');
|
||||
...
|
||||
COMMIT;
|
||||
}}}
|
||||
|
||||
In practice, encapsulating a large amount of simple queries inside a single transaction significantly improves performance.
|
||||
|
||||
Tip: Until the final `commit()` is called, most database engines lock updated rows, or even tables, to prevent any query outside the transaction from seeing the partially committed data (this is how transactions preserve Isolation, which is the I in ACID). That means that large transactions will queue every other queries for potentially a long time. Consequently, use large transactions only when concurrency is not a requirement.
|
||||
|
||||
== Why Is The Connection Always Passed As Parameter? ==
|
||||
|
||||
All the code examples in this chapter show the connection object passed a a parameter to Propel methods that trigger a database query:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(AccountPeer::DATABASE_NAME);
|
||||
$fromAccount = AccountPeer::retrieveByPk($fromAccountNumber, $con);
|
||||
$fromAccount->setValue($fromAccount->getValue() - $amount);
|
||||
$fromAccount->save($con);
|
||||
}}}
|
||||
|
||||
The same code works without explicitely passing the connection object, because Propel knows how to get the right connection from a Model:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$fromAccount = AccountPeer::retrieveByPk($fromAccountNumber);
|
||||
$fromAccount->setValue($fromAccount->getValue() - $amount);
|
||||
$fromAccount->save();
|
||||
}}}
|
||||
|
||||
However, it's a good practice to pass the connection explicitely, and for three reasons:
|
||||
|
||||
* Propel doesn't need to look for a connection object, and this results in a tiny boost in performance.
|
||||
* You can use a specific connection, which is required in distributed (master/slave) environments, in order to distinguish read and write operations.
|
||||
* Most importantly, transactions are tied to a single connection. You can't enclose two queries using different connections in a single transaction. So it's very useful to identify the connection you want to use for every query, as Propel will throw an exception if you use the wrong connection.
|
||||
|
||||
== Limitations ==
|
||||
|
||||
* Currently there is no support for row locking (e.g. `SELECT blah FOR UPDATE`).
|
||||
* You must rethrow the exception caught in the `catch` statement of nested transactions, otherwise there is a risk that the global rollback doesn't occur.
|
||||
* True nested transactions, with partial rollback, are only possible in MSSQL, and can be emulated in other RDBMS through savepoints. This feature may be added to Propel in the future, but for the moment, only the outermost PHP transaction triggers a database transaction.
|
||||
* If you rollback a partially executed transaction and ignore the exception thrown, there are good chances that some of your objects are out of sync with the database. The good practice is to always let a transaction exception escalate until it stops the script execution.
|
||||
|
280
library/propel/docs/guide/07-Behaviors.txt
Normal file
280
library/propel/docs/guide/07-Behaviors.txt
Normal file
|
@ -0,0 +1,280 @@
|
|||
= Behaviors =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
Behaviors are a great way to package model extensions for reusability. They are the powerful, versatile, fast, and help you organize your code in a better way.
|
||||
|
||||
== Pre and Post Hooks For `save()` And `delete()` Methods ==
|
||||
|
||||
The `save()` and `delete()` methods of your generated objects are easy to override. In fact, Propel looks for one of the following methods in your objects and executes them when needed:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// save() hooks
|
||||
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)
|
||||
// delete() hooks
|
||||
preDelete() // code executed before deleting an object
|
||||
postDelete() // code executed after deleting an object
|
||||
}}}
|
||||
|
||||
For example, you may want to keep track of the creation date of every row in the `book` table. In order to achieve this behavior, you can add a `created_at` column to the table in `schema.xml`:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
...
|
||||
<column name="created_at" type="timestamp" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Then, you can force the update of the `created_at` column before every insertion as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class Book extends BaseBook
|
||||
{
|
||||
public function preInsert(PropelPDO $con = null)
|
||||
{
|
||||
$this->setCreatedAt(time());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
Whenever you call `save()` on a new object, Propel now executes the `preInsert()` method on this objects and therefore update the `created_at` column:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$b = new Book();
|
||||
$b->setTitle('War And Peace');
|
||||
$b->save();
|
||||
echo $b->getCreatedAt(); // 2009-10-02 18:14:23
|
||||
}}}
|
||||
|
||||
If you implement `preInsert()`, `preUpdate()`, `preSave()` or `preDelete()`, these methods must return a boolean value. This determines whether the action (save or delete) may proceed.
|
||||
|
||||
'''Tip''': Since this feature adds a small overhead to write operations, you can deactivate it completely in your build properties by setting `propel.addHooks` to `false`.
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
# -------------------
|
||||
# TEMPLATE VARIABLES
|
||||
# -------------------
|
||||
propel.addHooks = false
|
||||
}}}
|
||||
|
||||
== Introducing Behaviors ==
|
||||
|
||||
When several of your custom model classes end up with similar methods added, it is time to refactor the common code.
|
||||
|
||||
For example, you may want to add the same ability you gave to `Book` to all the other objects in your model. Let's call this the "Timestampable behavior", because then all of your rows have a timestamp marking their creation. In order to achieve this behavior, you have to repeat the same operations on every table. First, add a `created_at` column to the other tables:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
...
|
||||
<column name="created_at" type="timestamp" />
|
||||
</table>
|
||||
<table name="author">
|
||||
...
|
||||
<column name="created_at" type="timestamp" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Then, add a `preInsert()` hook to the object stub classes:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class Book extends BaseBook
|
||||
{
|
||||
public function preInsert()
|
||||
{
|
||||
$this->setCreatedAt(time());
|
||||
}
|
||||
}
|
||||
|
||||
class Author extends BaseAuthor
|
||||
{
|
||||
public function preInsert()
|
||||
{
|
||||
$this->setCreatedAt(time());
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
Even if the code of this example is very simple, the repetition of code is already too much. Just imagine a more complex behavior, and you will understand that using the copy-and-paste technique soon leads to a maintenance nightmare.
|
||||
|
||||
Propel offers three ways to achieve the refactoring of the common behavior. The first one is to use a custom builder during the build process. This can work if all of your models share one single behavior. The second way is to use table inheritance. The inherited methods then offer limited capabilities. And the third way is to use Propel behaviors. This is the right way to refactor common model logic.
|
||||
|
||||
Behaviors are special objects that use events called during the build process to enhance the generated model classes. Behaviors can add attributes and methods to both the Peer and model classes, they can modify the course of some of the generated methods, and they can even modify the structure of a database by adding columns or tables.
|
||||
|
||||
For instance, Propel bundles a behavior called `timestampable`, which does exatcly the same thing as described above. But instead of adding columns and methods by hand, all you have to do is to declare it in a `<behavior>` tag in your `schema.xml`, as follows:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
...
|
||||
<behavior name="timestampable" />
|
||||
</table>
|
||||
<table name="author">
|
||||
...
|
||||
<behavior name="timestampable" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Then rebuild your model, and there you go: two columns, `created_at` and `updated_at`, were automatically added to both the `book` and `author` tables. Besides, the generated `BaseBook` and `BaseAuthor` classes already contain the code necessary to auto-set the current time on creation and on insertion.
|
||||
|
||||
== Bundled Behaviors ==
|
||||
|
||||
Propel currently bundles several behaviors. Check the behavior documentation for details on usage:
|
||||
|
||||
* [wiki:Documentation/1.5/Behaviors/aggregate_column aggregate_column]
|
||||
* [wiki:Documentation/1.5/Behaviors/alternative_coding_standards alternative_coding_standards]
|
||||
* [wiki:Documentation/1.5/Behaviors/auto_add_pk auto_add_pk]
|
||||
* [wiki:Documentation/1.5/Behaviors/timestampable timestampable]
|
||||
* [wiki:Documentation/1.5/Behaviors/sluggable sluggable]
|
||||
* [wiki:Documentation/1.5/Behaviors/soft_delete soft_delete]
|
||||
* [wiki:Documentation/1.5/Behaviors/sortable sortable]
|
||||
* [wiki:Documentation/1.5/Behaviors/nested_set nested_set]
|
||||
* [wiki:Documentation/1.5/Behaviors/query_cache query_cache]
|
||||
* And [wiki:Documentation/1.5/Inheritance#ConcreteTableInheritance concrete_inheritance], documented in the Inheritance Chapter even if it's a behavior
|
||||
|
||||
Behaviors bundled with Propel require no further installation and work out of the box.
|
||||
|
||||
== Customizing Behaviors ==
|
||||
|
||||
Behaviors often offer some parameters to tweak their effect. For instance, the `timestampable` behavior allows you to customize the names of the columns added to store the creation date and the update date. The behavior customization occurs in the `schema.xml`, inside `<parameter>` tags nested in the `<behavior>` tag. So let's set the behavior to use `created_on` instead of `created_at` for the creation date column name (and same for the update date column):
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
...
|
||||
<behavior name="timestampable">
|
||||
<parameter name="create_column" value="created_on" />
|
||||
<parameter name="update_column" value="updated_on" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
If the columns already exist in your schema, a behavior is smart enough not to add them one more time.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
...
|
||||
<column name="created_on" type="timestamp" />
|
||||
<column name="updated_on" type="timestamp" />
|
||||
<behavior name="timestampable">
|
||||
<parameter name="create_column" value="created_on" />
|
||||
<parameter name="update_column" value="updated_on" />
|
||||
</behavior>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
== Using Third-Party Behaviors ==
|
||||
|
||||
As a Propel behavior can be packaged into a single class, behaviors are quite easy to reuse and distribute across several projects. All you need to do is to copy the behavior file into your project, and declare it in `build.properties`, as follows:
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
# ----------------------------------
|
||||
# B E H A V I O R S E T T I N G S
|
||||
# ----------------------------------
|
||||
|
||||
propel.behavior.timestampable.class = propel.engine.behavior.timestampable.TimestampableBehavior
|
||||
# Add your custom behavior pathes here
|
||||
propel.behavior.formidable.class = path.to.FormidableBehavior
|
||||
}}}
|
||||
|
||||
Propel will then find the `FormidableBehavior` class whenever you use the `formidable` behavior in your schema:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="author">
|
||||
...
|
||||
<behavior name="timestampable" />
|
||||
<behavior name="formidable" />
|
||||
</table>
|
||||
}}}
|
||||
|
||||
'''Tip''': If you use autoloading during the build process, and if the behavior classes benefit from the autoloading, then you don't even need to declare the path to the behavior class.
|
||||
|
||||
== Applying a Behavior To All Tables ==
|
||||
|
||||
You can add a `<behavior>` tag directly under the `<database>` tag. That way, the behavior will be applied to all the tables of the database.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="propel">
|
||||
<behavior name="timestampable" />
|
||||
<table name="book">
|
||||
...
|
||||
</table>
|
||||
<table name="author">
|
||||
...
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
In this example, both the `book` and `author` table benefit from the `timestampable` behavior, and therefore automatically update their `created_at` and `updated_at` columns upon saving.
|
||||
|
||||
Going one step further, you can even apply a behavior to all the databases of your project, provided the behavior doesn't need parameters - or can use default parameters. To add a behavior to all databases, simply declare it in the project's `build.properties` under the `propel.behavior.default` key, as follows:
|
||||
|
||||
{{{
|
||||
#!ini
|
||||
propel.behavior.default = soft_delete, timestampable
|
||||
}}}
|
||||
|
||||
== Writing a Behavior ==
|
||||
|
||||
Behaviors can modify their table, and even add another table, by implementing the `modifyTable` method. In this method, use `$this->getTable()` to retrieve the table buildtime model and manipulate it.
|
||||
|
||||
Behaviors can add code to the generated model object by implementing one of the following methods:
|
||||
|
||||
{{{
|
||||
objectAttributes() // add attributes to the object
|
||||
objectMethods() // add methods to the object
|
||||
preInsert() // add code to be executed before insertion of a new object
|
||||
postInsert() // add code to be executed after insertion of a new object
|
||||
preUpdate() // add code to be executed before update of an existing object
|
||||
postUpdate() // add code to be executed after update of an existing object
|
||||
preSave() // add code to be executed before saving an object (new or existing)
|
||||
postSave() // add code to be executed after saving an object (new or existing)
|
||||
preDelete() // add code to be executed before deleting an object
|
||||
postDelete() // add code to be executed after deleting an object
|
||||
objectCall() // add code to be executed inside the object's __call()
|
||||
objectFilter(&$script) // do whatever you want with the generated code, passed as reference
|
||||
}}}
|
||||
|
||||
Behaviors can also add code to the generated query objects by implementing one of the following methods:
|
||||
|
||||
{{{
|
||||
queryAttributes() // add attributes to the query class
|
||||
queryMethods() // add methods to the query class
|
||||
preSelectQuery() // add code to be executed before selection of a existing objects
|
||||
preUpdateQuery() // add code to be executed before update of a existing objects
|
||||
postUpdateQuery() // add code to be executed after update of a existing objects
|
||||
preDeleteQuery() // add code to be executed before deletion of a existing objects
|
||||
postDeleteQuery() // add code to be executed after deletion of a existing objects
|
||||
queryFilter(&$script) // do whatever you want with the generated code, passed as reference
|
||||
}}}
|
||||
|
||||
Behaviors can also add code to the generated peer objects by implementing one of the following methods:
|
||||
|
||||
{{{
|
||||
staticAttributes() // add static attributes to the peer class
|
||||
staticMethods() // add static methods to the peer class
|
||||
preSelect() // adds code before every select query
|
||||
peerFilter(&$script) // do whatever you want with the generated code, passed as reference
|
||||
}}}
|
||||
|
||||
Check the behaviors bundled with Propel to see how to implement your own behavior.
|
448
library/propel/docs/guide/08-Logging.txt
Normal file
448
library/propel/docs/guide/08-Logging.txt
Normal file
|
@ -0,0 +1,448 @@
|
|||
= Logging And Debugging =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
Propel provides tools to monitor and debug your model. Whether you need to check the SQL code of slow queries, or to look for error messages previously thrown, Propel is your best friend for finding and fixing problems.
|
||||
|
||||
== Propel Logs ==
|
||||
|
||||
Propel uses the logging facility configured in `runtime-conf.xml` to record errors, warnings, and debug information.
|
||||
|
||||
By default Propel will attempt to use the Log framework that is distributed with PEAR. If you are not familiar with it, check its [http://www.indelible.org/php/Log/guide.html online documentation]. It is also easy to configure Propel to use your own logging framework -- or none at all.
|
||||
|
||||
=== Logger Configuration ===
|
||||
|
||||
The Propel log handler is configured in the `<log>` section of your project's `runtime-conf.xml` file. Here is the accepted format for this section with the default values that Propel uses:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<config>
|
||||
<log>
|
||||
<type>file</type>
|
||||
<name>./propel.log</name>
|
||||
<ident>propel</ident>
|
||||
<level>7</level> <!-- PEAR_LOG_DEBUG -->
|
||||
<conf></conf>
|
||||
</log>
|
||||
<propel>
|
||||
...
|
||||
</propel>
|
||||
</config>
|
||||
}}}
|
||||
|
||||
Using these parameters, Propel creates a ''file'' Log handler in the background, and keeps it for later use:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
Propel::$logger = Log::singleton($type = 'file', $name = './propel.log', $ident = 'propel', $conf = array(), $level = PEAR_LOG_DEBUG);
|
||||
}}}
|
||||
|
||||
The meaning of each of the `<log>` nested elements may vary, depending on which log handler you are using. Refer to the [http://www.indelible.org/php/Log/guide.html#standard-log-handlers PEAR::Log] documentation for more details on log handlers configuration and options.
|
||||
|
||||
Note that the `<level>` tag needs to correspond to the integer represented by one of the `PEAR_LOG_*` constants:
|
||||
|
||||
||'''Constant'''||'''Value'''||'''Description'''
|
||||
||PEAR_LOG_EMERG||0||System is unusable||
|
||||
||PEAR_LOG_ALERT||1||Immediate action required||
|
||||
||PEAR_LOG_CRIT||2||Critical conditions||
|
||||
||PEAR_LOG_ERR||3||Error conditions||
|
||||
||PEAR_LOG_WARNING||4||Warning conditions||
|
||||
||PEAR_LOG_NOTICE||5||Normal but significant||
|
||||
||PEAR_LOG_INFO||6||Informational||
|
||||
||PEAR_LOG_DEBUG||7||Debug-level messages||
|
||||
|
||||
=== Logging Messages ===
|
||||
|
||||
Use the static `Propel::log()` method to log a message using the configured log handler:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$myObj = new MyObj();
|
||||
$myObj->setName('foo');
|
||||
Propel::log('uh-oh, something went wrong with ' . $myObj->getName(), Propel::LOG_ERROR);
|
||||
}}}
|
||||
|
||||
You can log your own messages from the generated model objects by using their `log()` method, inherited from `BaseObject`:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$myObj = new MyObj();
|
||||
$myObj->log('uh-oh, something went wrong', Propel::LOG_ERROR);
|
||||
}}}
|
||||
|
||||
The log messages will show up in the log handler defined in `runtime-conf.xml` (`propel.log` file by default) as follows:
|
||||
|
||||
{{{
|
||||
Oct 04 00:00:18 [error] uh-oh, something went wrong with foo
|
||||
Oct 04 00:00:18 [error] MyObj: uh-oh, something went wrong
|
||||
}}}
|
||||
|
||||
Tip: All serious errors coming from the Propel core do not only issue a log message, they are also thrown as `PropelException`.
|
||||
|
||||
=== Using An Alternative PEAR Log Handler ===
|
||||
|
||||
In many cases you may wish to integrate Propel's logging facility with the rest of your web application. In `runtime-conf.xml`, you can customize a different PEAR logger. Here are a few examples:
|
||||
|
||||
'''Example 1:''' Using 'display' container (for output to HTML)
|
||||
{{{
|
||||
#!xml
|
||||
<log>
|
||||
<type>display</type>
|
||||
<level>6</level> <!-- PEAR_LOG_INFO -->
|
||||
</log>
|
||||
}}}
|
||||
|
||||
'''Example 2:''' Using 'syslog' container
|
||||
{{{
|
||||
#!xml
|
||||
<log>
|
||||
<type>syslog</type>
|
||||
<name>8</name> <!-- LOG_USER -->
|
||||
<ident>propel</ident>
|
||||
<level>6</level>
|
||||
</log>
|
||||
}}}
|
||||
|
||||
=== Using A Custom Logger ===
|
||||
|
||||
If you omit the `<log>` section of your `runtime-conf.xml`, then Propel will not setup ''any'' logging for you. In this case, you can set a custom logging facility and pass it to Propel at runtime.
|
||||
|
||||
Here's an example of how you could configure your own logger and then set Propel to use it:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
require_once 'MyLogger.php';
|
||||
$logger = new MyLogger();
|
||||
require_once 'propel/Propel.php';
|
||||
Propel::setLogger($logger);
|
||||
Propel::init('/path/to/runtime-conf.php');
|
||||
}}}
|
||||
|
||||
Your custom logger could be any object that implements a basic logger interface. Check the `BasicLogger` interface provided with the Propel runtime to see the methods that a logger must implement in order to be compatible with Propel. You do not actually have to implement this interface, but all the specified methods must be present in your container.
|
||||
|
||||
Let's see an example of a simple log container suitable for use with Propel:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class MyLogger implements BasicLogger
|
||||
{
|
||||
public function emergency($m)
|
||||
{
|
||||
$this->log($m, Propel::LOG_EMERG);
|
||||
}
|
||||
public function alert($m)
|
||||
{
|
||||
$this->log($m, Propel::LOG_ALERT);
|
||||
}
|
||||
public function crit($m)
|
||||
{
|
||||
$this->log($m, Propel::LOG_CRIT);
|
||||
}
|
||||
public function err($m)
|
||||
{
|
||||
$this->log($m, Propel::LOG_ERR);
|
||||
}
|
||||
public function warning($m)
|
||||
{
|
||||
$this->log($m, Propel::LOG_WARNING);
|
||||
}
|
||||
public function notice($m)
|
||||
{
|
||||
$this->log($m, Propel::LOG_NOTICE);
|
||||
}
|
||||
public function info($m)
|
||||
{
|
||||
$this->log($m, Propel::LOG_INFO);
|
||||
}
|
||||
public function debug($m)
|
||||
{
|
||||
$this->log($m, Propel::LOG_DEBUG);
|
||||
}
|
||||
|
||||
public function log($message, $priority)
|
||||
{
|
||||
$color = $this->priorityToColor($priority);
|
||||
echo '<p style="color: ' . $color . '">$message</p>';
|
||||
}
|
||||
|
||||
private function priorityToColor($priority)
|
||||
{
|
||||
switch($priority) {
|
||||
case Propel::LOG_EMERG:
|
||||
case Propel::LOG_ALERT:
|
||||
case Propel::LOG_CRIT:
|
||||
case Propel::LOG_ERR:
|
||||
return 'red';
|
||||
break;
|
||||
case Propel::LOG_WARNING:
|
||||
return 'orange';
|
||||
break;
|
||||
case Propel::LOG_NOTICE:
|
||||
return 'green';
|
||||
break;
|
||||
case Propel::LOG_INFO:
|
||||
return 'blue';
|
||||
break;
|
||||
case Propel::LOG_DEBUG:
|
||||
return 'grey';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
Tip: There is also a bundled `MojaviLogAdapter` class which allows you to use a Mojavi logger with Propel.
|
||||
|
||||
== Debugging Database Activity ==
|
||||
|
||||
By default, Propel uses `PropelPDO` for database connections. This class, which extends PHP's `PDO`, offers a debug mode to keep track of all the database activity, including all the executed queries.
|
||||
|
||||
=== Enabling The Debug Mode ===
|
||||
|
||||
The debug mode is disabled by default, but you can enable it at runtime as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(MyObjPeer::DATABASE_NAME);
|
||||
$con->useDebug(true);
|
||||
}}}
|
||||
|
||||
You can also disable the debug mode at runtime, by calling `PropelPDO::useDebug(false)`. Using this method, you can choose to enable the debug mode for only one particular query, or for all queries.
|
||||
|
||||
Alternatively, you can ask Propel to always enable the debug mode for a particular connection by using the `DebugPDO` class instead of the default `PropelPDO` class. This is accomplished in the `runtime-conf.xml` file, in the `<classname>` tag of a given datasource connection (see the [wiki:Documentation/1.5/RuntimeConfiguration runtime configuration reference] for more details).
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<propel>
|
||||
<datasources default="bookstore">
|
||||
<datasource id="bookstore">
|
||||
<adapter>sqlite</adapter>
|
||||
<connection>
|
||||
<!-- the classname that Propel should instantiate, must be PropelPDO subclass -->
|
||||
<classname>DebugPDO</classname>
|
||||
}}}
|
||||
|
||||
'''Tip''': You can use your own connection class there, but make sure that it extends `PropelPDO` and not only `PDO`. Propel requires certain fixes to PDO API that are provided by `PropelPDO`.
|
||||
|
||||
=== Counting Queries ===
|
||||
|
||||
In debug mode, `PropelPDO` keeps track of the number of queries that are executed. Use `PropelPDO::getQueryCount()` to retrieve this number:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(MyObjPeer::DATABASE_NAME);
|
||||
$myObjs = MyObjPeer::doSelect(new Criteria(), $con);
|
||||
echo $con->getQueryCount(); // 1
|
||||
}}}
|
||||
|
||||
Tip: You cannot use persistent connections if you want the query count to work. Actually, the debug mode in general requires that you don't use persistent connections in order for it to correctly log bound values and count executed statements.
|
||||
|
||||
=== Retrieving The Latest Executed Query ===
|
||||
|
||||
For debugging purposes, you may need the SQL code of the latest executed query. It is available at runtime in debug mode using `PropelPDO::getLastExecutedQuery()`, as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(MyObjPeer::DATABASE_NAME);
|
||||
$myObjs = MyObjPeer::doSelect(new Criteria(), $con);
|
||||
echo $con->getLastExecutedQuery(); // 'SELECT * FROM my_obj';
|
||||
}}}
|
||||
|
||||
Tip: You can also get a decent SQL representation of the criteria being used in a SELECT query by using the `Criteria->toString()` method.
|
||||
|
||||
Propel also keeps track of the queries executed directly on the connection object, and displays the bound values correctly.
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(MyObjPeer::DATABASE_NAME);
|
||||
$stmt = $con->prepare('SELECT * FROM my_obj WHERE name = :p1');
|
||||
$stmt->bindValue(':p1', 'foo');
|
||||
$stmt->execute();
|
||||
echo $con->getLastExecutedQuery(); // 'SELECT * FROM my_obj where name = "foo"';
|
||||
}}}
|
||||
|
||||
'''Tip''': The debug mode is intended for development use only. Do not use it in production environment, it logs too much information for a production server, and adds a small overhead to the database queries.
|
||||
|
||||
== Full Query Logging ==
|
||||
|
||||
The combination of the debug mode and a logging facility provides a powerful debugging tool named ''full query logging''. If you have properly configured a log handler, enabling the debug mode (or using `DebugPDO`) automatically logs the executed queries into Propel's default log file:
|
||||
|
||||
{{{
|
||||
Oct 04 00:00:18 propel-bookstore [debug] INSERT INTO publisher (`ID`,`NAME`) VALUES (NULL,'William Morrow')
|
||||
Oct 04 00:00:18 propel-bookstore [debug] INSERT INTO author (`ID`,`FIRST_NAME`,`LAST_NAME`) VALUES (NULL,'J.K.','Rowling')
|
||||
Oct 04 00:00:18 propel-bookstore [debug] 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)
|
||||
Oct 04 00:00:18 propel-bookstore [debug] INSERT INTO review (`ID`,`REVIEWED_BY`,`REVIEW_DATE`,`RECOMMENDED`,`BOOK_ID`) VALUES (NULL,'Washington Post','2009-10-04',1,52)
|
||||
...
|
||||
Oct 04 00:00:18 propel-bookstore [debug] SELECT bookstore_employee_account.EMPLOYEE_ID, bookstore_employee_account.LOGIN FROM `bookstore_employee_account` WHERE bookstore_employee_account.EMPLOYEE_ID=25
|
||||
}}}
|
||||
|
||||
By default, Propel logs all SQL queries, together with the date of the query and the name of the connection.
|
||||
|
||||
=== Setting The Data To Log ===
|
||||
|
||||
The full query logging feature can be configured either in the `runtime-conf.xml` configuration file, or using the runtime configuration API.
|
||||
|
||||
In `runtime-conf.xml`, tweak the feature by adding a `<debugpdo>` tag under `<propel>`:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<log>
|
||||
...
|
||||
</log>
|
||||
<propel>
|
||||
<datasources default="bookstore">
|
||||
...
|
||||
</datasources>
|
||||
<debugpdo>
|
||||
<logging>
|
||||
<details>
|
||||
<method>
|
||||
<enabled>true</enabled>
|
||||
</method>
|
||||
<time>
|
||||
<enabled>true</enabled>
|
||||
</time>
|
||||
<mem>
|
||||
<enabled>true</enabled>
|
||||
</mem>
|
||||
</details>
|
||||
</logging>
|
||||
</debugpdo>
|
||||
</propel>
|
||||
</config>
|
||||
}}}
|
||||
|
||||
To accomplish the same configuration as above at runtime, change the settings in your main include file, after `Propel::init()`, as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$config = Propel::getConfiguration(PropelConfiguration::TYPE_OBJECT);
|
||||
$config->setParameter('debugpdo.logging.details.method.enabled', true);
|
||||
$config->setParameter('debugpdo.logging.details.time.enabled', true);
|
||||
$config->setParameter('debugpdo.logging.details.mem.enabled', true);
|
||||
}}}
|
||||
|
||||
Let's see a few of the provided parameters.
|
||||
|
||||
=== Logging More Connection Messages ===
|
||||
|
||||
`PropelPDO` can log queries, but also connection events (open and close), and transaction events (begin, commit and rollback). Since Propel can emulate nested transactions, you may need to know when an actual `COMMIT` or `ROLLBACK` is issued.
|
||||
|
||||
To extend which methods of `PropelPDO` do log messages in debug mode, customize the `'debugpdo.logging.methods'` parameter, as follows:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$allMethods = array(
|
||||
'PropelPDO::__construct', // logs connection opening
|
||||
'PropelPDO::__destruct', // logs connection close
|
||||
'PropelPDO::exec', // logs a query
|
||||
'PropelPDO::query', // logs a query
|
||||
'PropelPDO::prepare', // logs the preparation of a statement
|
||||
'PropelPDO::beginTransaction', // logs a transaction begin
|
||||
'PropelPDO::commit', // logs a transaction commit
|
||||
'PropelPDO::rollBack', // logs a transaction rollBack (watch out for the capital 'B')
|
||||
'DebugPDOStatement::execute', // logs a query from a prepared statement
|
||||
'DebugPDOStatement::bindValue' // logs the value and type for each bind
|
||||
);
|
||||
$config = Propel::getConfiguration(PropelConfiguration::TYPE_OBJECT);
|
||||
$config->setParameter('debugpdo.logging.methods', $allMethods);
|
||||
}}}
|
||||
|
||||
By default, only the messages coming from `PropelPDO::exec`, `PropelPDO::query`, and `DebugPDOStatement::execute` are logged.
|
||||
|
||||
=== Logging Execution Time And Memory ===
|
||||
|
||||
In debug mode, Propel counts the time and memory necessary for each database query. This very valuable data can be added to the log messages on demand, by adding the following configuration:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$config = Propel::getConfiguration(PropelConfiguration::TYPE_OBJECT);
|
||||
$config->setParameter('debugpdo.logging.details.time.enabled', true);
|
||||
$config->setParameter('debugpdo.logging.details.mem.enabled', true);
|
||||
}}}
|
||||
|
||||
Enabling the options shown above, you get log output along the lines of:
|
||||
|
||||
{{{
|
||||
Feb 23 16:41:04 Propel [debug] time: 0.000 sec | mem: 1.4 MB | SET NAMES 'utf8'
|
||||
Feb 23 16:41:04 Propel [debug] time: 0.002 sec | mem: 1.6 MB | SELECT COUNT(tags.NAME) FROM tags WHERE tags.IMAGEID = 12
|
||||
Feb 23 16:41:04 Propel [debug] time: 0.012 sec | mem: 2.4 MB | SELECT tags.NAME, image.FILENAME FROM tags LEFT JOIN image ON tags.IMAGEID = image.ID WHERE image.ID = 12
|
||||
}}}
|
||||
|
||||
The order in which the logging details are enabled is significant, since it determines the order in which they will appear in the log file.
|
||||
|
||||
=== Complete List Of Logging Options ===
|
||||
|
||||
The following settings can be customized at runtime or in the configuration file:
|
||||
|
||||
||'''Parameter'''||'''Default'''||'''Meaning'''||
|
||||
||`debugpdo.logging.enabled`||`true`||Should any logging take place||
|
||||
||`debugpdo.logging.innerglue`||`": "`||String to use for combining the title of a detail and its value||
|
||||
||`debugpdo.logging.outerglue`||`" | "`||String to use for combining details together on a log line||
|
||||
||`debugpdo.logging.realmemoryusage`||`false`||Parameter to [http://www.php.net/manual/en/function.memory-get-usage.php memory_get_usage()] and [http://www.php.net/manual/en/function.memory-get-peak-usage.php memory_get_peak_usage()] calls||
|
||||
||`debugpdo.logging.methods`||[http://propel.propelorm.org/browser/branches/1.5/runtime/classes/propel/util/DebugPDO.php#L151 array(...)]||An array of method names `Class::method`) to be included in method call logging||
|
||||
||`debugpdo.logging.details.slow.enabled`||`false`||Enables flagging of slow method calls||
|
||||
||`debugpdo.logging.details.slow.threshold`||`0.1`||Method calls taking more seconds than this threshold are considered slow||
|
||||
||`debugpdo.logging.details.time.enabled`||`false`||Enables logging of method execution times||
|
||||
||`debugpdo.logging.details.time.precision`||`3`||Determines the precision of the execution time logging||
|
||||
||`debugpdo.logging.details.time.pad`||`10`||How much horizontal space to reserve for the execution time on a log line||
|
||||
||`debugpdo.logging.details.mem.enabled`||`false`||Enables logging of the instantaneous PHP memory consumption||
|
||||
||`debugpdo.logging.details.mem.precision`||`1`||Determines the precision of the memory consumption logging||
|
||||
||`debugpdo.logging.details.mem.pad`||`9`||How much horizontal space to reserve for the memory consumption on a log line||
|
||||
||`debugpdo.logging.details.memdelta.enabled`||`false`||Enables logging differences in memory consumption before and after the method call||
|
||||
||`debugpdo.logging.details.memdelta.precision`||`1`||Determines the precision of the memory difference logging||
|
||||
||`debugpdo.logging.details.memdelta.pad`||`10`||How much horizontal space to reserve for the memory difference on a log line||
|
||||
||`debugpdo.logging.details.mempeak.enabled`||`false`||Enables logging the peak memory consumption thus far by the currently executing PHP script||
|
||||
||`debugpdo.logging.details.mempeak.precision`||`1`||Determines the precision of the memory peak logging||
|
||||
||`debugpdo.logging.details.mempeak.pad`||`9`||How much horizontal space to reserve for the memory peak on a log line||
|
||||
||`debugpdo.logging.details.querycount.enabled`||`false`||Enables logging of the number of queries performed by the DebugPDO instance thus far||
|
||||
||`debugpdo.logging.details.querycount.pad`||`2`||How much horizontal space to reserve for the query count on a log line||
|
||||
||`debugpdo.logging.details.method.enabled`||`false`||Enables logging of the name of the method call||
|
||||
||`debugpdo.logging.details.method.pad`||`28`||How much horizontal space to reserve for the method name on a log line||
|
||||
|
||||
=== Changing the Log Level ===
|
||||
|
||||
By default the connection log messages are logged at the `Propel::LOG_DEBUG` level. This can be changed by calling the `setLogLevel()` method on the connection object:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(MyObjPeer::DATABASE_NAME);
|
||||
$con->setLogLevel(Propel::LOG_INFO);
|
||||
}}}
|
||||
|
||||
Now all queries and bind param values will be logged at the INFO level.
|
||||
|
||||
=== Configuring a Different Full Query Logger ===
|
||||
|
||||
By default the `PropelPDO` connection logs queries and binds param values using the `Propel::log()` static method. As explained above, this method uses the log storage configured by the `<log>` tag in the `runtime-conf.xml` file.
|
||||
|
||||
If you would like the queries to be logged using a different logger (e.g. to a different file, or with different ident, etc.), you can set a logger explicitly on the connection at runtime, using `Propel::setLogger()`:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$con = Propel::getConnection(MyObjPeer::DATABASE_NAME);
|
||||
$logger = Log::factory('syslog', LOG_LOCAL0, 'propel', array(), PEAR_LOG_INFO);
|
||||
$con->setLogger($logger);
|
||||
}
|
||||
}}}
|
||||
|
||||
This will not affect the general Propel logging, but only the full query logging. That way you can log the Propel error and warnings in one file, and the SQL queries in another file.
|
329
library/propel/docs/guide/09-Inheritance.txt
Normal file
329
library/propel/docs/guide/09-Inheritance.txt
Normal file
|
@ -0,0 +1,329 @@
|
|||
= Inheritance =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
Developers often need one model table to extend another model table. Inheritance being an object-oriented notion, it doesn't have a true equivalent in the database world, so this is something an ORM must emulate. Propel offers two types of table inheritance: [http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html Single Table Inheritance], which is the most efficient implementations from a SQL and query performance perspective, but is limited to a small number of inherited fields ; and [http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html Concrete Table Inheritance], which provides the most features but adds a small overhead on write queries.
|
||||
|
||||
== Single Table Inheritance ==
|
||||
|
||||
In this implementation, one table is used for all subclasses. This has the implication that your table must have all columns needed by the main class and subclasses. Propel will create stub subclasses.
|
||||
|
||||
Let's illustrate this idea with an example. Consider an object model with three classes, `Book`, `Essay`, and `Comic` - the first class being parent of the other two. With single table inheritance, the data of all three classes is stored in one table, named `book`.
|
||||
|
||||
=== Schema Definition ===
|
||||
|
||||
A table using Single Table Inheritance requires a column to identify which class should be used to represent the ''table'' row. Classically, this column is named `class_key` - but you can choose whatever name fits your taste. The column needs the `inheritance="single"` attribute to make Propel understand that it's the class key column. Note that this 'key' column must be a real column in the table.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="title" type="VARCHAR" size="100"/>
|
||||
<column name="class_key" type="INTEGER" inheritance="single">
|
||||
<inheritance key="1" class="Book"/>
|
||||
<inheritance key="1" class="Essay" extends="Book"/>
|
||||
<inheritance key="2" class="Comic" extends="Book"/>
|
||||
</column>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
Once you rebuild your model, Propel generated all three model classes (`Book`, `Essay`, and `Comic`) and three query classes (`BookQuery`, `EssayQuery`, and `ComicQuery`). The `Essay` and `Comic` classes extend the `Book` class, the `EssayQuery` and `ComicQuery` classes extend `BookQuery`.
|
||||
|
||||
'''Tip''': An inherited class can extend another inherited class. That mean that you can add a `Manga` kind of book that extends `Comic` instead of `Book`.
|
||||
|
||||
=== Using Inherited Objects ===
|
||||
|
||||
Use inherited objects just like you use regular Propel model objects:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$book = new Book();
|
||||
$book->setTitle('War And Peace');
|
||||
$book->save();
|
||||
$essay = new Essay();
|
||||
$essay->setTitle('On the Duty of Civil Disobedience');
|
||||
$essay->save();
|
||||
$comic = new Comic();
|
||||
$comic->setTitle('Little Nemo In Slumberland');
|
||||
$comic->save();
|
||||
}}}
|
||||
|
||||
Inherited objects share the same properties and methods by default, but you can add your own logic to each of the generated classes.
|
||||
|
||||
Behind the curtain, Propel sets the `class_key` column based on the model class. So the previous code stores the following rows in the database:
|
||||
|
||||
{{{
|
||||
id | title | class_key
|
||||
---|-----------------------------------|----------
|
||||
1 | War And Peace | Book
|
||||
2 | On the Duty of Civil Disobedience | Essay
|
||||
3 | Little Nemo In Slumberland | Comic
|
||||
}}}
|
||||
|
||||
Incidentally, that means that you can add new classes manually, even if they are not defined as `<inheritance>` tags in the `schema.xml`:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class Novel extends Book
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setClassKey('Novel');
|
||||
}
|
||||
}
|
||||
$novel = new Novel();
|
||||
$novel->setTitle('Harry Potter');
|
||||
$novel->save();
|
||||
}}}
|
||||
|
||||
=== Retrieving Inherited objects ===
|
||||
|
||||
In order to retrieve books, use the Query object of the main class, as you would usually do. Propel will hydrate children objects instead of the parent object when necessary:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$books = BookQuery::create()->find();
|
||||
foreach ($books as $book) {
|
||||
echo get_class($book) . ': ' . $book->getTitle() . "\n";
|
||||
}
|
||||
// Book: War And Peace
|
||||
// Essay: On the Duty of Civil Disobedience
|
||||
// Comic: Little Nemo In Slumberland
|
||||
// Novel: Harry Potter
|
||||
}}}
|
||||
|
||||
If you want to retrieve only objects of a certain class, use the inherited query classes:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
$comic = ComicQuery::create()
|
||||
->findOne();
|
||||
echo get_class($comic) . ': ' . $comic->getTitle() . "\n";
|
||||
// Comic: Little Nemo In Slumberland
|
||||
}}}
|
||||
|
||||
'''Tip''': You can override the base peer's `getOMClass()` to return the classname to use based on more complex logic (or query).
|
||||
|
||||
=== Abstract Entities ===
|
||||
|
||||
If you wish to enforce using subclasses of an entity, you may declare a table "abstract" in your XML data model:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book" abstract="true">
|
||||
...
|
||||
}}}
|
||||
|
||||
That way users will only be able to instanciate `Essay` or `Comic` books, but not `Book`.
|
||||
|
||||
== Concrete Table Inheritance ==
|
||||
|
||||
Concrete Table Inheritance uses one table for each class in the hierarchy. Each table contains columns for the class and all its ancestors, so any fields in a superclass are duplicated across the tables of the subclasses.
|
||||
|
||||
Propel implements Concrete Table Inheritance through a behavior.
|
||||
|
||||
=== Schema Definition ===
|
||||
|
||||
Once again, this is easier to understand through an example. In a Content Management System, content types are often organized in a hierarchy, each subclass adding more fields to the superclass. So let's consider the following schema, where the `article` and `video` tables use the same fields as the main `content` tables, plus additional fields:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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">
|
||||
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="title" type="VARCHAR" size="100"/>
|
||||
<column name="body" 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="video">
|
||||
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
|
||||
<column name="title" type="VARCHAR" size="100"/>
|
||||
<column name="resource_link" 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>
|
||||
}}}
|
||||
|
||||
Since the columns of the main table are copied to the child tables, this schema is a simple implementation of Concrete Table Inheritance. This is something that you can write by hand, but the repetition makes it tedious. Instead, you should let the `concrete_inheritance` behavior do it for you:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<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>
|
||||
}}}
|
||||
|
||||
'''Tip''': The `concrete_inheritance` behavior copies columns, foreign keys, indices and validators.
|
||||
|
||||
=== Using Inherited Model Classes ===
|
||||
|
||||
For each of the tables in the schema above, Propel generates a Model class:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?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();
|
||||
}}}
|
||||
|
||||
And since the `concrete_inheritance` behavior tag defines a parent table, the `Article` and `Video` classes extend the `Content` class (same for the generated Query classes):
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
// methods of the parent model are accessible to the child models
|
||||
class Content extends BaseContent
|
||||
{
|
||||
public function getCategoryName()
|
||||
{
|
||||
return $this->getCategory()->getName();
|
||||
}
|
||||
}
|
||||
echo $art->getCategoryName(); // 'Movie'
|
||||
echo $vid->getCategoryName(); // 'Movie'
|
||||
|
||||
// methods of the parent query are accessible to the child query
|
||||
class ContentQuery extends BaseContentQuery
|
||||
{
|
||||
public function filterByCategoryName($name)
|
||||
{
|
||||
return $this
|
||||
->useCategoryQuery()
|
||||
->filterByName($name)
|
||||
->endUse();
|
||||
}
|
||||
}
|
||||
$articles = ArticleQuery::create()
|
||||
->filterByCategoryName('Movie')
|
||||
->find();
|
||||
}}}
|
||||
|
||||
That makes of Concrete Table Inheritance a powerful way to organize your model logic and to avoid repetition, both in the schema and in the model code.
|
||||
|
||||
=== Data Replication ===
|
||||
|
||||
By default, 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
|
||||
<?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)
|
||||
}}}
|
||||
|
||||
Propel also creates a one-to-one relationship between a object and its parent copy. That's why the schema definition above doesn't define any primary key for the `article` and `video` tables: the `concrete_inheritance` behavior creates the `id` primary key which is also a foreign key to the parent `id` column. So once you have a parent object, getting the child object is just one method call away:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
class Article extends BaseArticle
|
||||
{
|
||||
public function getPreview()
|
||||
{
|
||||
return $this->getContent();
|
||||
}
|
||||
}
|
||||
class Movie extends BaseMovie
|
||||
{
|
||||
public function getPreview()
|
||||
{
|
||||
return $this->getResourceLink();
|
||||
}
|
||||
}
|
||||
$conts = ContentQuery::create()->find();
|
||||
foreach ($conts as $content) {
|
||||
echo $content->getTitle() . "(". $content->getCategoryName() ")/n"
|
||||
if ($content->hasChildObject()) {
|
||||
echo ' ' . $content->getChildObject()->getPreview(), "\n";
|
||||
}
|
||||
// Avatar Makes Best Opening Weekend in the History (Movie)
|
||||
// With $232.2 million worldwide total, Avatar had one of the best-opening
|
||||
// weekends in the history of cinema.
|
||||
// Avatar Trailer (Movie)
|
||||
// http://www.avatarmovie.com/index.html
|
||||
}}}
|
||||
|
||||
The `hasChildObject()` and `getChildObject()` methods are automatically added by the behavior to the parent class. Behind the curtain, the saved `content` row has an additional `descendant_column` field allowing it to use the right model for the job.
|
||||
|
||||
'''Tip''' You can disable the data replication by setting the `copy_data_to_parent` parameter to "false". In that case, the `concrete_inheritance` behavior simply modifies the table at buildtime and does nothing at runtime. Also, with `copy_data_to_parent` disabled, any primary key copied from the parent table is not turned into a foreign key:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="article">
|
||||
<behavior name="concrete_inheritance">
|
||||
<parameter name="extends" value="content" />
|
||||
<parameter name="copy_data_to_parent" value="false" />
|
||||
</behavior>
|
||||
<column name="body" type="VARCHAR" size="100"/>
|
||||
</table>
|
||||
// results in
|
||||
<table name="article">
|
||||
<column name="body" type="VARCHAR" size="100"/>
|
||||
<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>
|
||||
}}}
|
312
library/propel/docs/reference/Buildtime-Configuration.txt
Normal file
312
library/propel/docs/reference/Buildtime-Configuration.txt
Normal file
|
@ -0,0 +1,312 @@
|
|||
= Build Properties Reference =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
Here is a list of properties that can be set to affect how Propel builds database files. For a complete list, see the {{{default.properties}}} file that is bundled with your version of Propel generator (this will be in PEAR's data directory if you are using a PEAR-installed version of Propel).
|
||||
|
||||
First, some conventions:
|
||||
|
||||
* Text surrounded by a '''/''' is text that you would provide and is not defined in the language. (i.e. a table name is a good example of this.)
|
||||
* Items where you have an alternative choice have a '''|''' character between them (i.e. true|false)
|
||||
* Alternative choices may be delimited by '''{''' and '''}''' to indicate that this is the default option, if not overridden elsewhere.
|
||||
|
||||
== Where to Specify Properties ==
|
||||
|
||||
=== In the Project build.properties File ===
|
||||
|
||||
The most natural place to specify properties for a file are in the project's {{{build.properties}}} file. This file is expected to be found in the project directory.
|
||||
|
||||
=== In a global build.properties file ===
|
||||
|
||||
You can also create a {{{global build.properties}}} file in the same directory as Propel's {{{default.properties}}} file. For users who have installed Propel using PEAR, this will be in PEAR data directory structure.
|
||||
|
||||
=== On the Command Line ===
|
||||
|
||||
You can also specify properties on the commandline when you invoke Propel:
|
||||
|
||||
{{{
|
||||
$ propel-gen /path/to/project -Dpropel.someOtherProperty=value
|
||||
}}}
|
||||
|
||||
''Note that there is '''no space''' between the -D and the property name.''
|
||||
|
||||
== The Properties ==
|
||||
|
||||
=== General Build Settings ===
|
||||
|
||||
{{{
|
||||
propel.project = Your-Project-Name
|
||||
}}}
|
||||
|
||||
The name of your project. This affects names of generated files, etc.
|
||||
|
||||
{{{
|
||||
propel.targetPackage = {propel.project}
|
||||
}}}
|
||||
|
||||
The package to use for the generated classes. This affects the value of the @package phpdoc tag, and it also affects the directory that the classes are placed in. By default this will be the same as the project. Note that the target package (and thus the target directory for generated classes) can be overridden in each `<database>` and `<table>` element in the XML schema.
|
||||
|
||||
{{{
|
||||
propel.packageObjectModel = true|{false}
|
||||
}}}
|
||||
|
||||
Whether to join schemas using the same database name into a single schema. This allows splitting schemas in packages, and referencing tables in another schema (but in the same database) in a foreign key. Beware that database behaviors will also be joined when this parameter is set to true.
|
||||
|
||||
{{{
|
||||
propel.schema.validate = {true}|false
|
||||
}}}
|
||||
|
||||
Whether to validate the schema using the XSD file. The default XSD file is located under `generator/resources/xsd/database.xsd`, and you can use a custom XSD file by changing the `propel.schema.xsd.file` property.
|
||||
|
||||
{{{
|
||||
propel.schema.transform = true|{false}
|
||||
}}}
|
||||
|
||||
Whether to transform the schema using the XSL file. This was used in previous Propel versions to clean up the schema, but tended to hide problems in the schema. It is disabled by default since Propel 1.5. The default XSL file is located under `generator/resources/xsd/database.xsl`, and you can use a custom XSL file by changing the `propel.schema.xsl.file` property.
|
||||
|
||||
=== Database Settings ===
|
||||
|
||||
{{{
|
||||
propel.database = pgsql|mysql|sqlite|mssql|oracle
|
||||
}}}
|
||||
|
||||
The Propel platform that will be used to determine how to build the SQL DDL, etc.
|
||||
|
||||
|
||||
{{{
|
||||
propel.database.url = /PDO database connection string/
|
||||
propel.database.user =
|
||||
propel.database.password =
|
||||
}}}
|
||||
|
||||
Propel will use this information as the default to connect to your database. Note that for PDO some drivers (e.g. mysql, oracle) require that you specify the username and password separately from the DSN, which is why they are available as options.
|
||||
|
||||
|
||||
{{{
|
||||
propel.database.buildUrl = /PDO database connection string, defaults to use ${propel.database.url}/
|
||||
}}}
|
||||
|
||||
This property is used by Propel to connect to a database to reverse engineer or data dump. The default is to use the database connection defined by the ''propel.database.url'' property.
|
||||
|
||||
|
||||
{{{
|
||||
propel.database.createUrl = /PDO database connection string, defaults to use ${propel.database.url}/
|
||||
}}}
|
||||
|
||||
This property is used by Propel for creating a database. Of course, Propel is unable to create many databases because they do not provide a SQL method for creation; therefore, it is usually recommended that you actually create your database by hand.
|
||||
|
||||
{{{
|
||||
propel.database.schema = /schema-name/
|
||||
}}}
|
||||
|
||||
Where supported by the RDBMS, you can specify a schema name for Propel to use.
|
||||
|
||||
{{{
|
||||
propel.database.encoding =
|
||||
}}}
|
||||
The encoding to use for the database. This can affect things such as transforming charsets when exporting to XML, etc.
|
||||
|
||||
{{{
|
||||
propel.tablePrefix = {empty}|string
|
||||
}}}
|
||||
|
||||
Add a prefix to all the table names in the database. This does not affect the tables phpName. This setting can be overridden on a per-database basis in the schema.
|
||||
|
||||
=== Reverse-Engineering Settings ===
|
||||
|
||||
{{{
|
||||
propel.samePhpName = true|{false}
|
||||
}}}
|
||||
Whether to specify PHP names that are the same as the column names.
|
||||
|
||||
{{{
|
||||
propel.addVendorInfo = true|{false}
|
||||
}}}
|
||||
Whether to add the vendor info. This is currently only used for MySQL, but does provide additional information (such as full-text indexes) which can affect the generation of the DDL from the schema.
|
||||
|
||||
{{{
|
||||
propel.addValidators = {none}|maxvalue|type|required|unique|all
|
||||
}}}
|
||||
Which Propel validators to add to the generated schema (based on the db constraints).
|
||||
|
||||
=== Customizing Generated Object Model ===
|
||||
|
||||
{{{
|
||||
propel.addGenericAccessors = true|{false}
|
||||
propel.addGenericMutators = true|{false}
|
||||
}}}
|
||||
Whether to add generic getter/setter methods -- e.g. '''getByName()''', '''setByName()'''.
|
||||
|
||||
{{{
|
||||
propel.addTimeStamp = true|{false}
|
||||
}}}
|
||||
Whether to add a timestamp to the phpdoc header of generated OM classes.
|
||||
|
||||
{{{
|
||||
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 classe 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.
|
||||
|
||||
{{{
|
||||
propel.basePrefix = {Base}|/YourPrefix/
|
||||
}}}
|
||||
The prefix to use for the base (super) classes that are generated.
|
||||
|
||||
{{{
|
||||
propel.classPrefix = {empty}|string;
|
||||
}}}
|
||||
Some sort of "namespacing": All Propel classes with get the Prefix "My_ORM_Prefix_" just like "My_ORM_Prefix_BookPeer".
|
||||
|
||||
{{{
|
||||
propel.disableIdentifierQuoting = true|{false}
|
||||
}}}
|
||||
Identifier quoting is only implemented at the DDL layer at this point. Since this may result in undesired behavior (especially in Postgres), it can be disabled by setting this property to true.
|
||||
|
||||
{{{
|
||||
propel.useLeftJoinsInDoJoinMethods = {true}|false
|
||||
}}}
|
||||
Set whether the '''doSelectJoin*()''' methods use LEFT JOIN or INNER JOIN (see ticket:491 and ticket:588 to understand more about why this might be important).
|
||||
|
||||
=== MySQL-specific Settings ===
|
||||
|
||||
{{{
|
||||
propel.mysqlTableType = /DefaultTableEngine/
|
||||
}}}
|
||||
Default table engine - defaults to MyISAM. You can override this setting if you wish to default to another engine for all tables (for instance InnoDB, or HEAP). This setting can also be overridden on a per-table basis using the `<vendor>` element in the schema (see [wiki:Documentation/1.5/Schema#AddingVendorInfo]).
|
||||
|
||||
{{{
|
||||
propel.mysqlTableEngineKeyword = /EngineKeyword/
|
||||
}}}
|
||||
Keyword used to specify the table engine in the CREATE SQL statement. Defaults to 'ENGINE', users of MYSQL < 5 should use 'TYPE' instead.
|
||||
|
||||
=== Date/Time Settings ===
|
||||
|
||||
{{{
|
||||
propel.useDateTimeClass = true|{false}
|
||||
}}}
|
||||
This is how you enable full use of the new DateTime class in Propel. Setting this to true means that getter methods for date/time/timestamp columns will return a DateTime object ''when the default format is empty''. Note that the current default of ''false'' is only for backwards compatibility; in the future ''true'' will be the only option here.
|
||||
|
||||
{{{
|
||||
propel.dateTimeClass = {DateTime}|string
|
||||
}}}
|
||||
Specify a custom DateTime subclass that you wish to have Propel use for temporal values.
|
||||
|
||||
{{{
|
||||
propel.defaultTimeStampFormat = {Y-m-d H:i:s}|string
|
||||
propel.defaultTimeFormat = {%X}|string
|
||||
propel.defaultDateFormat = {%x}|string
|
||||
}}}
|
||||
These are the default formats that will be used when fetching values from temporal columns in Propel. You can always specify these when calling the methods directly, but for methods like getByName() it is nice to change the defaults.
|
||||
|
||||
To have these methods return DateTime objects instead, you should set these to empty values, for example:
|
||||
{{{
|
||||
propel.defaultTimeStampFormat =
|
||||
}}}
|
||||
|
||||
=== Directories ===
|
||||
|
||||
{{{
|
||||
propel.project.dir = default-depends-on-installation-type
|
||||
}}}
|
||||
|
||||
''This is not necessarily a property you can change.'' The project directory is the directory where you project files (build.properties, schema.xml, runtime-conf.xml, etc.) are located. For example, if you use the {{{propel-gen}}} script, this value will get overridden to the path you pass to {{{propel-gen}}}.
|
||||
|
||||
{{{
|
||||
propel.output.dir = ${propel.project.dir}/build
|
||||
}}}
|
||||
The default top-level directory for output of classes, sql, config, etc.
|
||||
|
||||
{{{
|
||||
propel.schema.dir = ${propel.project.dir}
|
||||
}}}
|
||||
The directory where Propel expects to find your schema.xml file.
|
||||
|
||||
{{{
|
||||
propel.conf.dir = ${propel.project.dir}
|
||||
}}}
|
||||
The directory where Propel expects to find your {{{runtime-conf.xml}}} file.
|
||||
|
||||
{{{
|
||||
propel.php.dir = ${propel.output.dir}/classes
|
||||
}}}
|
||||
The directory where Propel will create generated object model classes.
|
||||
|
||||
{{{
|
||||
propel.phpconf.dir = ${propel.output.dir}/conf
|
||||
}}}
|
||||
The directory where Propel will place the php-ified version of your {{{runtime-conf.xml}}}.
|
||||
|
||||
{{{
|
||||
propel.sql.dir = ${propel.output.dir}/sql
|
||||
}}}
|
||||
The directory where Propel will place generated DDL (or data insert statements, etc.)
|
||||
|
||||
|
||||
=== Overriding Builder Classes ===
|
||||
|
||||
{{{
|
||||
# Object Model builders
|
||||
propel.builder.peer.class = propel.engine.builder.om.php5.PHP5ComplexPeerBuilder
|
||||
propel.builder.object.class = propel.engine.builder.om.php5.PHP5ComplexObjectBuilder
|
||||
propel.builder.objectstub.class = propel.engine.builder.om.php5.PHP5ExtensionObjectBuilder
|
||||
propel.builder.peerstub.class = propel.engine.builder.om.php5.PHP5ExtensionPeerBuilder
|
||||
|
||||
propel.builder.objectmultiextend.class = propel.engine.builder.om.php5.PHP5MultiExtendObjectBuilder
|
||||
|
||||
propel.builder.tablemap.class = propel.engine.builder.om.php5.PHP5TableMapBuilder
|
||||
|
||||
propel.builder.interface.class = propel.engine.builder.om.php5.PHP5InterfaceBuilder
|
||||
|
||||
propel.builder.node.class = propel.engine.builder.om.php5.PHP5NodeBuilder
|
||||
propel.builder.nodepeer.class = propel.engine.builder.om.php5.PHP5NodePeerBuilder
|
||||
propel.builder.nodestub.class = propel.engine.builder.om.php5.PHP5ExtensionNodeBuilder
|
||||
propel.builder.nodepeerstub.class = propel.engine.builder.om.php5.PHP5ExtensionNodePeerBuilder
|
||||
|
||||
propel.builder.nestedset.class = propel.engine.builder.om.php5.PHP5NestedSetBuilder
|
||||
propel.builder.nestedsetpeer.class = propel.engine.builder.om.php5.PHP5NestedSetPeerBuilder
|
||||
|
||||
# SQL builders
|
||||
|
||||
propel.builder.ddl.class = propel.engine.builder.sql.${propel.database}.${propel.database}DDLBuilder
|
||||
propel.builder.datasql.class = propel.engine.builder.sql.${propel.database}.${propel.database}DataSQLBuilder
|
||||
|
||||
# Platform classes
|
||||
|
||||
propel.platform.class = propel.engine.platform.${propel.database}Platform
|
||||
|
||||
# Pluralizer class (used to generate plural forms)
|
||||
|
||||
propel.builder.pluralizer.class = propel.engine.builder.util.DefaultEnglishPluralizer
|
||||
}}}
|
||||
|
||||
As you can see, you can specify your own builder and platform classes if you want to extend & override behavior in the default classes
|
||||
|
||||
=== Adding Behaviors ===
|
||||
|
||||
{{{
|
||||
propel.behavior.timestampable.class = propel.engine.behavior.TimestampableBehavior
|
||||
}}}
|
||||
|
||||
Define the path to the class to be used for the `timestampable` behavior. This behavior is bundled wit hPropel, but if you want to override it, you can specify a different path.
|
||||
|
||||
If you want to add more behaviors, write their path following the same model:
|
||||
|
||||
{{{
|
||||
propel.behavior.my_behavior.class = my.custom.path.to.MyBehaviorClass
|
||||
}}}
|
||||
|
||||
Behaviors are enabled on a per-table basis in the `schema.xml`. However, you can add behaviors for all your schemas, provided that you define them in the `propel.behavior.default` setting:
|
||||
|
||||
{{{
|
||||
propel.behavior.default = soft_delete,my_behavior
|
||||
}}}
|
1025
library/propel/docs/reference/ModelCriteria.txt
Normal file
1025
library/propel/docs/reference/ModelCriteria.txt
Normal file
File diff suppressed because it is too large
Load diff
318
library/propel/docs/reference/Runtime-Configuration.txt
Normal file
318
library/propel/docs/reference/Runtime-Configuration.txt
Normal file
|
@ -0,0 +1,318 @@
|
|||
= Runtime Configuration File =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
== Example {{{runtime-conf.xml}}} File ==
|
||||
|
||||
Here is a the sample runtime configuration file.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<log>
|
||||
<ident>propel-bookstore</ident>
|
||||
<name>console</name>
|
||||
<level>7</level>
|
||||
</log>
|
||||
<propel>
|
||||
<datasources default="bookstore">
|
||||
<datasource id="bookstore">
|
||||
<adapter>sqlite</adapter>
|
||||
<connection>
|
||||
<classname>DebugPDO</classname>
|
||||
<dsn>mysql:host=localhost;dbname=bookstore</dsn>
|
||||
<user>testuser</user>
|
||||
<password>password</password>
|
||||
<options>
|
||||
<option id="ATTR_PERSISTENT">false</option>
|
||||
</options>
|
||||
<attributes>
|
||||
<option id="ATTR_EMULATE_PREPARES">true</option>
|
||||
</attributes>
|
||||
<settings>
|
||||
<setting id="charset">utf8</setting>
|
||||
<setting id="queries">
|
||||
<query>set search_path myschema, public</query><!-- automatically set postgresql's search_path -->
|
||||
<query>INSERT INTO BAR ('hey', 'there')</query><!-- execute some other query -->
|
||||
</setting>
|
||||
</settings>
|
||||
</connection>
|
||||
<slaves>
|
||||
<connection>
|
||||
<dsn>mysql:host=slave-server1; dbname=bookstore</dsn>
|
||||
</connection>
|
||||
<connection>
|
||||
<dsn>mysql:host=slave-server2; dbname=bookstore</dsn>
|
||||
</connection>
|
||||
</slaves>
|
||||
</datasource>
|
||||
</datasources>
|
||||
<debugpdo>
|
||||
<logging>
|
||||
<details>
|
||||
<method>
|
||||
<enabled>true</enabled>
|
||||
</method>
|
||||
<time>
|
||||
<enabled>true</enabled>
|
||||
<precision>3</precision>
|
||||
</time>
|
||||
<mem>
|
||||
<enabled>true</enabled>
|
||||
<precision>1</precision>
|
||||
</mem>
|
||||
</details>
|
||||
</logging>
|
||||
</debugpdo>
|
||||
</propel>
|
||||
</config>
|
||||
}}}
|
||||
|
||||
== Explanation of Configuration Sections ==
|
||||
|
||||
Below you will find an explanation of the primary elements in the configuration.
|
||||
|
||||
=== <log> ===
|
||||
|
||||
If the '''<log>''' element is present, Propel will use the specified information to instantiate a [http://pear.php.net/Log PEAR Log] logger.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<log>
|
||||
<type>file<type>
|
||||
<name>/path/to/logger.log</name>
|
||||
<ident>my-app</ident>
|
||||
<level>7</level>
|
||||
</log>
|
||||
}}}
|
||||
|
||||
The nested elements correspond to the configuration options for the logger (options that would otherwise be passed to '''Log::factory()''' method).
|
||||
|
||||
||'''Element'''||'''Default'''||'''Description'''||
|
||||
||'''<type>'''||file||The logger type.||
|
||||
||'''<name>'''||./propel.log||Name of log, meaning is dependent on type specified. (For ''file'' type this is the filename).||
|
||||
||'''<ident>'''||propel||The identifier tag for the log.||
|
||||
||'''<level>'''||7 (PEAR_LOG_DEBUG)||The logging level.||
|
||||
|
||||
This log configuring API is designed to provide a simple way to get log output from Propel; however, if your application already has a logging mechanism, we recommend instead that you use your existing logger (writing a simple log adapter, if you are using an unsupported logger). See the [wiki:Documentation/1.5/07-Logging Logging documentation] for more info.
|
||||
|
||||
=== <datasources> ===
|
||||
|
||||
This is the top-level tag for Propel datasources configuration.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
}}}
|
||||
|
||||
=== <datasource> ===
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource id="bookstore">
|
||||
}}}
|
||||
A specific datasource being configured.
|
||||
* The @id must match the <database> @name attribute from your {{{schema.xml}}}.
|
||||
|
||||
=== <adapter> ===
|
||||
|
||||
The adapter to use for Propel. Currently supported adapters: sqlite, pgsql, mysql, oracle, mssql. Note that it is possible that your adapter could be different from your connection driver (e.g. if using ODBC to connect to MSSQL database, you would use an ODBC PDO driver, but MSSQL Propel adapter).
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<adapter>sqlite</adapter>
|
||||
}}}
|
||||
|
||||
=== <connection> ===
|
||||
|
||||
The PDO database connection for the specified datasource.
|
||||
|
||||
Nested elements define the DSN, connection options, other PDO attributes, and finally some Propel-specific initialization settings.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<connection>
|
||||
}}}
|
||||
|
||||
==== <classname> ====
|
||||
|
||||
A custom PDO class (must be a PropelPDO subclass) that you would like to use for the PDO connection.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<connection>
|
||||
<classname>DebugPDO</classname>
|
||||
}}}
|
||||
|
||||
This can be used to specify the alternative '''DebugPDO''' class bundled with Propel, or your own subclass. ''Your class must extend PropelPDO, because Propel requires the ability to nest transactions (without having exceptions being thrown by PDO).''
|
||||
|
||||
==== <dsn> ====
|
||||
|
||||
The PDO DSN that Propel will use to connect to the database for this datasource.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<connection>
|
||||
<dsn>mysql:host=localhost;dbname=bookstore</dsn>
|
||||
}}}
|
||||
|
||||
See the PHP documentation for specific format:
|
||||
* [http://www.php.net/manual/en/ref.pdo-mysql.connection.php MySQL DSN]
|
||||
* [http://www.php.net/manual/en/ref.pdo-pgsql.connection.php PostgreSQL DSN]
|
||||
* [http://www.php.net/manual/en/ref.pdo-sqlite.connection.php SQLite DSN]
|
||||
* [http://www.php.net/manual/en/ref.pdo-oci.connection.php Oracle DSN]
|
||||
* [http://www.php.net/manual/en/ref.pdo-dblib.connection.php MSSQL DSN]
|
||||
|
||||
Note that some database (e.g. PostgreSQL) specify username and password as part of the DSN while the others specify user and password separately.
|
||||
|
||||
==== <user> and <password> ====
|
||||
|
||||
Specifies credentials for databases that specify username and password separately (e.g. MySQL, Oracle).
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<connection>
|
||||
<dsn>mysql:host=localhost;dbname=bookstore</dsn>
|
||||
<user>test</user>
|
||||
<password>testpass</password>
|
||||
}}}
|
||||
|
||||
==== <options> ====
|
||||
|
||||
Specify any options which ''must'' be specified when the PDO connection is created. For example, the ATTR_PERSISTENT option must be specified at object creation time.
|
||||
|
||||
See the [http://www.php.net/pdo PDO documentation] for more details.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<connection>
|
||||
<!-- ... -->
|
||||
<options>
|
||||
<option id="ATTR_PERSISTENT">false</option>
|
||||
</options>
|
||||
}}}
|
||||
|
||||
==== <attributes> ====
|
||||
|
||||
`<attributes>` are similar to `<options>`; the difference is that options specified in `<attributes>` are set after the PDO object has been created. These are set using the [http://us.php.net/PDO-setAttribute PDO->setAttribute()] method.
|
||||
|
||||
In addition to the standard attributes that can be set on the PDO object, there are also the following Propel-specific attributes that change the behavior of the PropelPDO connection:
|
||||
|
||||
|| '''Attribute constant''' || '''Valid Values (Default)''' || '''Description''' ||
|
||||
|| PropelPDO::PROPEL_ATTR_CACHE_PREPARES || true/false (false) || Whether to have the PropelPDO connection cache the PDOStatement prepared statements. This will improve performance if you are executing the same query multiple times by your script (within a single request / script run). ||
|
||||
|
||||
''Note that attributes in the XML can be specified with or without the PDO:: (or PropelPDO::) constant prefix.''
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<connection>
|
||||
<!-- ... -->
|
||||
<attributes>
|
||||
<option id="ATTR_ERRMODE">PDO::ERRMODE_WARNING</option>
|
||||
<option id="ATTR_STATEMENT_CLASS">myPDOStatement</option>
|
||||
<option id="PROPEL_ATTR_CACHE_PREPARES">true</option>
|
||||
</attributes>
|
||||
}}}
|
||||
|
||||
'''Tip''': If you are using MySQL and get the following error : "SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active", you can try adding the following attribute:
|
||||
|
||||
{{{
|
||||
<option id="MYSQL_ATTR_USE_BUFFERED_QUERY">true</option>
|
||||
}}}
|
||||
|
||||
==== <settings> ====
|
||||
|
||||
Settings are Propel-specific options used to further configure the connection -- or perform other user-defined initialization tasks.
|
||||
|
||||
Currently supported settings are:
|
||||
* charset
|
||||
* queries
|
||||
|
||||
===== charset =====
|
||||
|
||||
Specifies the character set to use. Currently you must specify the charset in the way that is understood by your RDBMS. Also note that not all database systems support specifying charset (e.g. SQLite must be compiled with specific charset support). Specifying this option will likely result in an exception if your database doesn't support the specified charset.
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<connection>
|
||||
<!-- ... -->
|
||||
<settings>
|
||||
<setting id="charset">utf8</setting>
|
||||
</settings>
|
||||
}}}
|
||||
|
||||
===== queries =====
|
||||
|
||||
Specifies any SQL statements to run when the database connection is initialized. This can be used for any environment setup or db initialization you would like to perform. These statements will be executed with every Propel initialization (e.g. every PHP script load).
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<connection>
|
||||
<!-- ... -->
|
||||
<settings>
|
||||
<setting id="queries">
|
||||
<query>set search_path myschema, public</query><!-- automatically set postgresql's search_path -->
|
||||
<query>INSERT INTO BAR ('hey', 'there')</query>
|
||||
</setting>
|
||||
</settings>
|
||||
}}}
|
||||
|
||||
=== <slaves> ===
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<config>
|
||||
<propel>
|
||||
<datasources>
|
||||
<datasource>
|
||||
<slaves>
|
||||
}}}
|
||||
|
||||
The `<slaves>` tag groups slave `<connection>` elements which provide support for configuring slave db servers -- when using Propel in a master-slave replication environment. See the [wiki:Documentation/1.5/Master-Slave Master-Slave documentation] for more information. The nested `<connection>` elements are configured the same way as the top-level `<connection>` element is configured.
|
||||
|
||||
=== <debugpdo> ===
|
||||
|
||||
The optional `<debugpdo>` element may be provided to pass additional logging configuration options to DebugPDO. Note that these settings have no effect unless DebugPDO has been selected in [1.5/RuntimeConfiguration#debugpdo `runtime-conf.xml`] as the PDO connection class. See the [wiki:Documentation/1.5/07-Logging Logging documentation] for more information on configuring DebugPDO.
|
398
library/propel/docs/reference/Schema.txt
Normal file
398
library/propel/docs/reference/Schema.txt
Normal file
|
@ -0,0 +1,398 @@
|
|||
= Database Schema =
|
||||
|
||||
[[PageOutline]]
|
||||
|
||||
The schema for `schema.xml` contains a small number of elements with required and optional attributes. The Propel generator contains a [source:branches/1.5/generator/resources/dtd/database.dtd DTD] that can be used to validate your `schema.xml` document. Also, when you build your SQL and OM, the Propel generator will automatically validate your `schema.xml` file using a highly-detailed [source:branches/1.5/generator/resources/xsd/database.xsd XSD].
|
||||
|
||||
== At-a-Glance ==
|
||||
|
||||
The hierarchical tree relationship for the elements is:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database>
|
||||
<external-schema />
|
||||
<table>
|
||||
<column>
|
||||
<inheritance />
|
||||
</column>
|
||||
<foreign-key>
|
||||
<reference />
|
||||
</foreign-key>
|
||||
<index>
|
||||
<index-column />
|
||||
</index>
|
||||
<unique>
|
||||
<unique-column />
|
||||
</unique>
|
||||
<id-method-parameter/>
|
||||
</table>
|
||||
</database>
|
||||
}}}
|
||||
|
||||
**Tip**: If you use an IDE supporting autocompletion in XML documents, you can take advantage of the XSD describing the `schema.xml` syntax to suggest elements and attributes as you type. To enable it, add a `xmlns:xsi` and a `xsi:noNamespaceSchemaLocation` attribute to the leading `<database>` tag:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database name="my_connection_name" defaultIdMethod="native"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://svn.propelorm.org/branches/1.5/generator/resources/xsd/database.xsd" >
|
||||
}}}
|
||||
|
||||
== Detailed Reference ==
|
||||
|
||||
This page provides an alternate rendering of the Appendix B - Schema Reference from the user's guide.
|
||||
It spells out in specific detail, just where each attribute or element belongs.
|
||||
|
||||
First, some conventions:
|
||||
|
||||
* Text surrounded by a '''/''' is text that you would provide and is not defined in the language. (i.e. a table name is a good example of this.)
|
||||
* Optional items are surrounded by '''[''' and ''']''' characters.
|
||||
* Items where you have an alternative choice have a '''|''' character between them (i.e. true|false)
|
||||
* Alternative choices may be delimited by '''{''' and '''}''' to indicate that this is the default option, if not overridden elsewhere.
|
||||
* '''...''' means repeat the previous item.
|
||||
|
||||
=== <database> element ===
|
||||
|
||||
Starting with the {{{<database>}}} element. The ''attributes'' and ''elements'' available are:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<database
|
||||
name="/DatabaseName/"
|
||||
defaultIdMethod="native|none"
|
||||
[package="/ProjectName/"]
|
||||
[namespace="/ClassNamespace/"]
|
||||
[baseClass="/baseClassName/"]
|
||||
[basePeer="/baseClassPeerName/"]
|
||||
[defaultPhpNamingMethod="nochange|{underscore}|phpname|clean"
|
||||
[heavyIndexing="true|false"]
|
||||
[tablePrefix="/tablePrefix/"]
|
||||
>
|
||||
<external-schema>
|
||||
<table>
|
||||
...
|
||||
</database>
|
||||
}}}
|
||||
|
||||
The `package`, `baseClass`, `basePeer`, `defaultPhpNamingMethod`, and `heavyIndexing` attributes are generally optional.
|
||||
A Database element may include an `<external-schema>` element, or multiple `<table>` elements.
|
||||
|
||||
* `defaultIdMethod` sets the default id method to use for auto-increment columns.
|
||||
* `package` specifies the "package" for the generated classes. Classes are created in subdirectories according to the `package` value.
|
||||
* `namespace` specifies the default namespace that generated model classes will use (PHP 5.3 only). This attribute can be completed or overridden at the table level.
|
||||
* `baseClass` allows you to specify a default base class that all generated Propel objects should extend (in place of `propel.om.BaseObject`).
|
||||
* `basePeer` instructs Propel to use a different SQL-generating `BasePeer` class (or sub-class of `BasePeer`) for all generated objects.
|
||||
* `defaultPhpNamingMethod` the default naming method to use for tables of this database. Defaults to `underscore`, which transforms table names into CamelCase phpNames.
|
||||
* `heavyIndexing` adds indexes for each component of the primary key (when using composite primary keys).
|
||||
* `tablePrefix` adds a prefix to all the SQL table names.
|
||||
|
||||
=== <external-schema> element ===
|
||||
|
||||
The `<external-schema>` element is pretty simple. It just includes a schema file from somewhere on the file systems. The format is:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<external-schema filename="/a path to a file/" />
|
||||
}}}
|
||||
|
||||
=== <table> element ===
|
||||
|
||||
The `<table>` element is the most complicated of the usable elements. Its definition looks like this:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table
|
||||
name = "/TableName/"
|
||||
[idMethod = "native|{none}"]
|
||||
[phpName = "/PhpObjectName/"]
|
||||
[package="/PhpObjectPackage/"]
|
||||
[namespace = "/PhpObjectNamespace/"]
|
||||
[skipSql = "true|false"]
|
||||
[abstract = "true|false"]
|
||||
[phpNamingMethod = "nochange|{underscore}|phpname|clean"]
|
||||
[baseClass = "/baseClassName/"]
|
||||
[basePeer = "/baseClassPeerName/"]
|
||||
[description="/A text description of the table/"]
|
||||
[heavyIndexing = "true|false"]
|
||||
[readOnly = "true|false"]
|
||||
[treeMode = "NestedSet|MaterializedPath"]
|
||||
[reloadOnInsert = "true|false"]
|
||||
[reloadOnUpdate = "true|false"]
|
||||
[allowPkInsert = "true|false"]
|
||||
>
|
||||
|
||||
<column>
|
||||
...
|
||||
<foreign-key>
|
||||
...
|
||||
<index>
|
||||
...
|
||||
<unique>
|
||||
...
|
||||
<id-method-parameter>
|
||||
...
|
||||
</table>
|
||||
}}}
|
||||
|
||||
According to the schema, `name` is the only required attribute. Also, the `idMethod`, `package`, `namespace`, `phpNamingMethod`, `baseClass`, `basePeer`, and `heavyIndexing` attributes all default to what is specified by the `<database>` element.
|
||||
|
||||
==== Description of Attributes ====
|
||||
|
||||
* `idMethod` sets the id method to use for auto-increment columns.
|
||||
* `phpName` specifies object model class name. By default, Propel uses a CamelCase version of the table name as phpName.
|
||||
* `package` specifies the "package" (or subdirectory) in which model classes get generated.
|
||||
* `namespace` specifies the namespace that the generated model classes will use (PHP 5.3 only). If the table namespace starts with a `\`, it overrides the namespace defined in the `<database>` tag; otherwise, the actual table namespace is the concatenation of the database namespace and the table namespace.
|
||||
* `skipSql` instructs Propel not to generate DDL SQL for the specified table. This can be used together with `readOnly` for supperting VIEWS in Propel.
|
||||
* `abstract` Whether the generated ''stub'' class will be abstract (e.g. if you're using inheritance)
|
||||
* `phpNamingMethod` the naming method to use. Defaults to `underscore`, which transforms the table name into a CamelCase phpName.
|
||||
* `baseClass` allows you to specify a class that the generated Propel objects should extend ({{{in place of propel.om.BaseObject}}}).
|
||||
* `basePeer` instructs Propel to use a different SQL-generating `BasePeer` class (or sub-class of `BasePeer`).
|
||||
* `heavyIndexing` adds indexes for each component of the primary key (when using composite primary keys).
|
||||
* `readOnly` suppresses the mutator/setter methods, save() and delete() methods.
|
||||
* `treeMode` is used to indicate that this table is part of a node tree. Currently the only supported values are "!NestedSet" (see [wiki:Documentation/1.5/Behaviors/nested_set]) and "!MaterializedPath" (deprecated).
|
||||
* `reloadOnInsert` is used to indicate that the object should be reloaded from the database when an INSERT is performed. This is useful if you have triggers (or other server-side functionality like column default expressions) that alters the database row on INSERT.
|
||||
* `reloadOnUpdate` is used to indicate that the object should be reloaded from the database when an UPDATE is performed. This is useful if you have triggers (or other server-side functionality like column default expressions) that alters the database row on UPDATE.
|
||||
* `allowPkInsert` can be used if you want to define the primary key of a new object being inserted. By default if idMethod is "native", Propel would throw an exception. However, in some cases this feature is useful, for example if you do some replication of data in an master-master environment. It defaults to false.
|
||||
|
||||
=== <column> element ===
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<column
|
||||
name = "/ColumnName/"
|
||||
[phpName = "/PHPColumnName/"]
|
||||
[peerName = "/PEERNAME/"]
|
||||
[primaryKey = "true|{false}"]
|
||||
[required = "true|{false}"]
|
||||
[type = "BOOLEAN|TINYINT|SMALLINT|INTEGER|BIGINT|DOUBLE|FLOAT|REAL|DECIMAL|CHAR|{VARCHAR}|LONGVARCHAR|DATE|TIME|TIMESTAMP|BLOB|CLOB"]
|
||||
[phpType = "boolean|int|integer|double|float|string|/BuiltInClassName/|/UserDefinedClassName/"]
|
||||
[sqlType = "/NativeDatabaseColumnType/"
|
||||
[size = "/NumericLengthOfColumn/"]
|
||||
[scale = "/DigitsAfterDecimalPlace/"]
|
||||
[defaultValue = "/AnyDefaultValueMatchingType/"]
|
||||
[defaultExpr = "/AnyDefaultExpressionMatchingType/"]
|
||||
[autoIncrement = "true|{false}"]
|
||||
[lazyLoad = "true|{false}"]
|
||||
[description = "/Column Description/"]
|
||||
[primaryString = "true|{false}"]
|
||||
[phpNamingMethod = "nochange|underscore|phpname"]
|
||||
[inheritance = "single|{false}"]
|
||||
[inputValidator = "NameOfInputValidatorClass"]
|
||||
>
|
||||
[<inheritance key="/KeyName/" class="/ClassName/" [extends="/BaseClassName/"] />]
|
||||
</column>
|
||||
}}}
|
||||
|
||||
==== Description of Attributes ====
|
||||
|
||||
* {{{defaultValue}}} The default value that the object will have for this column in the PHP instance after creating a "new Object". This value is always interpreted as a string.
|
||||
* {{{defaultExpr}}} The default value for this column as expressed in SQL. This value is used solely for the "sql" target which builds your database from the schema.xml file. The defaultExpr is the SQL expression used as the "default" for the column.
|
||||
* {{{primaryString}}} A column defined as primary string serves as default value for a `__toString()` method in the generated Propel object.
|
||||
|
||||
=== <foreign-key> element ===
|
||||
|
||||
To link a column to another table use the following syntax:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<foreign-key
|
||||
foreignTable = "/TheOtherTableName/"
|
||||
[name = "/Name for this foreign key/"]
|
||||
[phpName = "/Name for the foreign object in methods generated in this class/"]
|
||||
[refPhpName = "/Name for this object in methods generated in the foreign class/"]
|
||||
[onDelete = "cascade|setnull|restrict|none"]
|
||||
[onUpdate = "cascade|setnull|restrict|none"]
|
||||
[defaultJoin= "Criteria::INNER_JOIN|Criteria::LEFT_JOIN"]
|
||||
>
|
||||
<reference local="/LocalColumnName/" foreign="/ForeignColumnName/" />
|
||||
</foreign-key>
|
||||
}}}
|
||||
|
||||
==== Description of Attributes ====
|
||||
|
||||
* {{{defaultJoin}}} This affects the default join type used in the generated `joinXXX()` methods in the model query class. Propel uses an INNER JOIN for foreign keys attached to a required column, and a LEFT JOIN for foreign keys attached to a non-required column, but you can override this in the foreign key element.
|
||||
|
||||
=== <index> element ===
|
||||
|
||||
To create an index on one or more columns, use the following syntax:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<index>
|
||||
<index-column name="/ColumnName/" [size="/LengthOfIndexColumn/"] />
|
||||
...
|
||||
</index>
|
||||
}}}
|
||||
|
||||
In some cases your RDBMS may require you to specify an index size.
|
||||
|
||||
=== <unique> element ===
|
||||
|
||||
To create a unique index on one or more columns, use the following syntax:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<unique>
|
||||
<unique-column name="/ColumnName/" [size="/LengthOfIndexColumn/"] />
|
||||
...
|
||||
</unique>
|
||||
}}}
|
||||
|
||||
In some cases your RDBMS may require you to specify an index size for unique indexes.
|
||||
|
||||
=== <id-method-parameter> element ===
|
||||
|
||||
If you are using a database that uses sequences for auto-increment columns (e.g. PostgreSQL or Oracle), you can customize the name of the sequence using the <id-method-paramter/> tag:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<id-method-parameter value="my_custom_sequence_name"/>
|
||||
}}}
|
||||
|
||||
== Column Types ==
|
||||
|
||||
Here are the Propel column types with some example mappings to native database and PHP types. There are also several ways to customize the mapping between these types.
|
||||
|
||||
=== Text Types ===
|
||||
|
||||
||'''Propel Type'''||'''Desc'''||'''Example Default DB Type (MySQL)'''||'''Default PHP Native Type'''||
|
||||
||CHAR||Fixed-lenght character data||CHAR||string||
|
||||
||VARCHAR||Variable-lenght character data||VARCHAR||string||
|
||||
||LONGVARCHAR||Long variable-length character data||TEXT||string||
|
||||
||CLOB||Character LOB (locator object)||LONGTEXT||string||
|
||||
|
||||
=== Numeric Types ===
|
||||
|
||||
||'''Propel Type'''||'''Desc'''||'''Example Default DB Type (MySQL)'''||'''Default PHP Native Type'''||
|
||||
||NUMERIC||Numeric data||DECIMAL||string (PHP int is limited)||
|
||||
||DECIMAL||Decimal data||DECIMAL||string (PHP int is limited)||
|
||||
||TINYINT||Tiny integer ||TINYINT||int||
|
||||
||SMALLINT||Small integer ||SMALLINT||int||
|
||||
||INTEGER||Integer||INTEGER||int||
|
||||
||BIGINT||Large integer||BIGINT||string (PHP int is limited)||
|
||||
||REAL||Real number||REAL||double||
|
||||
||FLOAT||Floating point number||FLOAT||double||
|
||||
||DOUBLE||Floating point number||DOUBLE||double||
|
||||
|
||||
=== Binary Types ===
|
||||
|
||||
||'''Propel Type'''||'''Desc'''||'''Example Default DB Type (MySQL)'''||'''Default PHP Native Type'''||
|
||||
||BINARY||Fixed-length binary data||BLOB||double||
|
||||
||VARBINARY||Variable-length binary data||MEDIUMBLOB||double||
|
||||
||LONGVARBINARY||Long variable-length binary data||LONGBLOB||double||
|
||||
||BLOB||Binary LOB (locator object)||LONGBLOB||string||
|
||||
|
||||
=== Temporal (Date/Time) Types ===
|
||||
|
||||
|
||||
||'''Propel Type'''||'''Desc'''||'''Example Default DB Type (MySQL)'''||'''Default PHP Native Type'''||
|
||||
||DATE||Date (e.g. YYYY-MM-DD)||DATE||DateTime object||
|
||||
||TIME||Time (e.g. HH:MM:SS)||TIME||DateTime object||
|
||||
||TIMESTAMP||Date + time (e.g. YYYY-MM-DD HH:MM:SS)||TIMESTAMP||DateTime object||
|
||||
|
||||
==== Legacy Temporal Types ====
|
||||
|
||||
The following Propel 1.2 types are still supported, but are no longer needed with Propel 1.3.
|
||||
|
||||
||'''Propel Type'''||'''Desc'''||'''Example Default DB Type (MySQL)'''||'''Default PHP Native Type'''||
|
||||
||BU_DATE||Pre-/post-epoch date (e.g. 1201-03-02)||DATE||DateTime object||
|
||||
||BU_TIMESTAMP||Pre-/post-epoch Date + time (e.g. 1201-03-02 12:33:00)||TIMESTAMP||DateTime object||
|
||||
|
||||
== Customizing Mappings ==
|
||||
|
||||
=== Specify Column Attributes ===
|
||||
|
||||
You can change the way that Propel maps its own types to native SQL types or to PHP types by overriding the values for a specific column.
|
||||
|
||||
For example:
|
||||
|
||||
(Overriding PHP type)
|
||||
{{{
|
||||
#!xml
|
||||
<column name="population_served" type="INTEGER" phpType="string"/>
|
||||
}}}
|
||||
|
||||
(Overriding SQL type)
|
||||
{{{
|
||||
#!xml
|
||||
<column name="ip_address" type="VARCHAR" sqlType="inet"/>
|
||||
}}}
|
||||
|
||||
=== Adding Vendor Info ===
|
||||
|
||||
Propel supports database-specific elements in the schema (currently only for MySQL). This "vendor" parameters affect the generated SQL. To add vendor data, add a `<vendor>` tag with a `type` attribute specifying the target database vendor. In the `<vendor>` tag, add `<parameter>` tags with a `name` and a `value` attribue. For instance:
|
||||
|
||||
{{{
|
||||
#!xml
|
||||
<table name="book">
|
||||
<vendor type="mysql">
|
||||
<parameter name="Engine" value="InnoDB"/>
|
||||
<parameter name="Charset" value="utf8"/>
|
||||
</vendor>
|
||||
</table>
|
||||
}}}
|
||||
|
||||
This will change the generated SQL table creation to look like:
|
||||
|
||||
{{{
|
||||
#!sql
|
||||
CREATE TABLE book
|
||||
()
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET utf8;
|
||||
}}}
|
||||
|
||||
Propel supports the following vendor parameters for MySQL:
|
||||
|
||||
{{{
|
||||
Name | Example values
|
||||
-----------------|---------------
|
||||
// in <table> element
|
||||
Engine | MYISAM (default), BDB, HEAP, ISAM, InnoDB, MERGE, MRG_MYISAM
|
||||
Charset | utf8, latin1, etc.
|
||||
Collate | utf8_unicode_ci, latin1_german1_ci, etc.
|
||||
Checksum | 0, 1
|
||||
Pack_Keys | 0, 1, DEFAULT
|
||||
Delay_key_write | 0, 1
|
||||
// in <column> element
|
||||
Charset | utf8, latin1, etc.
|
||||
Collate | utf8_unicode_ci, latin1_german1_ci, etc.
|
||||
// in <index> element
|
||||
Index_type | FULLTEXT
|
||||
}}}
|
||||
|
||||
=== Using Custom Platform ===
|
||||
|
||||
For overriding the mapping between Propel types and native SQL types, you can create your own Platform class and override the mapping.
|
||||
|
||||
For example:
|
||||
|
||||
{{{
|
||||
#!php
|
||||
<?php
|
||||
require_once 'propel/engine/platform/MysqlPlatform .php';
|
||||
class CustomMysqlPlatform extends MysqlPlatform {
|
||||
|
||||
/**
|
||||
* Initializes custom domain mapping.
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
$this->setSchemaDomainMapping(new Domain(PropelTypes::NUMERIC, "DECIMAL"));
|
||||
$this->setSchemaDomainMapping(new Domain(PropelTypes::LONGVARCHAR, "TEXT"));
|
||||
$this->setSchemaDomainMapping(new Domain(PropelTypes::BINARY, "BLOB"));
|
||||
$this->setSchemaDomainMapping(new Domain(PropelTypes::VARBINARY, "MEDIUMBLOB"));
|
||||
$this->setSchemaDomainMapping(new Domain(PropelTypes::LONGVARBINARY, "LONGBLOB"));
|
||||
$this->setSchemaDomainMapping(new Domain(PropelTypes::BLOB, "LONGBLOB"));
|
||||
$this->setSchemaDomainMapping(new Domain(PropelTypes::CLOB, "LONGTEXT"));
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
You must then specify that mapping in the {{{build.properties}}} for your project:
|
||||
|
||||
{{{
|
||||
propel.platform.class = propel.engine.platform.${propel.database}Platform
|
||||
}}}
|
Loading…
Add table
Add a link
Reference in a new issue