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
|
||||
}}}
|
Loading…
Add table
Add a link
Reference in a new issue