281 lines
11 KiB
Plaintext
281 lines
11 KiB
Plaintext
= 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.
|