Commit 618815dc authored by zYne's avatar zYne

--no commit message

--no commit message
parent ae4b7157
+++ Introduction
Many web applications have different kinds of lists. The lists may contain data from multiple components (= database tables) and they may have actions such as paging, sorting and setting conditions. {{Doctrine_Hook}} helps building these lists. It has a simple API for building search criteria forms as well as building a DQL query from the 'hooked' parameters.
+++ Building queries
+++ List of parsers
+++ Introduction
[**Note**: The term 'Transaction' doesnt refer to database transactions here but to the general meaning of this term]
[**Note**: This component is in **Alpha State**]
Locking is a mechanism to control concurrency. The two most well known locking strategies are optimistic and pessimistic locking. The following is a short description of these two strategies from which only pessimistic locking is currently supported by Doctrine.
**Optimistic Locking:**
The state/version of the object(s) is noted when the transaction begins. When the transaction finishes the noted state/version of the participating objects is compared to the current state/version. When the states/versions differ the objects have been modified by another transaction and the current transaction should fail. This approach is called 'optimistic' because it is assumed that it is unlikely that several users will participate in transactions on the same objects at the same time.
**Pessimistic Locking:**
The objects that need to participate in the transaction are locked at the moment the user starts the transaction. No other user can start a transaction that operates on these objects while the locks are active. This ensures that the user who starts the transaction can be sure that noone else modifies the same objects until he has finished his work.
Doctrine's pessimistic offline locking capabilities can be used to control concurrency during actions or procedures that take several HTTP request and response cycles and/or a lot of time to complete.
+++ Examples
The following code snippet demonstrates the use of Doctrine's pessimistic offline locking capabilities.
At the page where the lock is requested...
<code type="php">
// Get a locking manager instance
$lockingMngr = new Doctrine_Locking_Manager_Pessimistic();
try
{
// Ensure that old locks which timed out are released
// before we try to acquire our lock
// 300 seconds = 5 minutes timeout
$lockingMngr->releaseAgedLocks(300);
// Try to get the lock on a record
$gotLock = $lockingMngr->getLock(
// The record to lock. This can be any Doctrine_Record
$myRecordToLock,
// The unique identifier of the user who is trying to get the lock
'Bart Simpson'
);
if($gotLock)
{
echo "Got lock!";
// ... proceed
}
else
{
echo "Sorry, someone else is currently working on this record";
}
}
catch(Doctrine_Locking_Exception $dle)
{
echo $dle->getMessage();
// handle the error
}
</code>
At the page where the transaction finishes...
<code type="php">
// Get a locking manager instance
$lockingMngr = new Doctrine_Locking_Manager_Pessimistic();
try
{
if($lockingMngr->releaseLock($myRecordToUnlock, 'Bart Simpson'))
{
echo "Lock released";
}
else
{
echo "Record was not locked. No locks released.";
}
}
catch(Doctrine_Locking_Exception $dle)
{
echo $dle->getMessage();
// handle the error
}
</code>
+++ Planned
* Possibility to release locks of a specific Record type (i.e. releasing all locks on 'User' objects).
+++ Technical Details
The pessimistic offline locking manager stores the locks in the database (therefore 'offline'). The required locking table is automatically created when you try to instantiate an instance of the manager and the ATTR_CREATE_TABLES is set to TRUE. This behaviour may change in the future to provide a centralised and consistent table creation procedure for installation purposes.
+++ Maintainer
Roman Borschel - romanb at #doctrine (freenode)
Don't hesitate to contact me if you have questions, ideas, ect.
+++ Introduction
{{Doctrine_Connection_Profiler}} is an eventlistener for {{Doctrine_Connection}}. It provides flexible query profiling. Besides the SQL strings the query profiles include elapsed time to run the queries. This allows inspection of the queries that have been performed without the need for adding extra debugging code to model classes.
{{Doctrine_Connection_Profiler}} can be enabled by adding it as an eventlistener for {{Doctrine_Connection}}.
<code type="php">
$conn = Doctrine_Manager::connection($dsn);
$profiler = new Doctrine_Connection_Profiler();
$conn->setListener($profiler);
</code>
+++ Basic usage
Perhaps some of your pages is loading slowly. The following shows how to build a complete profiler report from the connection:
<code type="php">
$time = 0;
foreach ($profiler as $event) {
$time += $event->getElapsedSecs();
echo $event->getName() . " " . sprintf("%f", $event->getElapsedSecs()) . "<br>\n";
echo $event->getQuery() . "<br>\n";
$params = $event->getParams();
if( ! empty($params)) {
var_dump($params);
}
}
echo "Total time: " . $time . "<br>\n";
</code>
+++ Advanced usage
+++ Introduction
Validation in Doctrine is a way to enforce your business rules in the model part of the MVC architecture. You can think of this validation as a gateway that needs to be passed right before data gets into the persistent data store. The definition of these business rules takes place at the record level, that means in your active record model classes (classes derived from {{Doctrine_Record}}). The first thing you need to do to be able to use this kind of validation is to enable it globally. This is done through the {{Doctrine_Manager}} (see the code below).
Once you enabled validation, you'll get a bunch of validations automatically:
* **Data type validations**: All values assigned to columns are checked for the right type. That means if you specified a column of your record as type 'integer', Doctrine will validate that any values assigned to that column are of this type. This kind of type validation tries to be as smart as possible since PHP is a loosely typed language. For example 2 as well as "7" are both valid integers whilst "3f" is not. Type validations occur on every column (since every column definition needs a type).
* **Length validation**: As the name implies, all values assigned to columns are validated to make sure that the value does not exceed the maximum length.
<code type="php">
// turning on validation
Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
</code>
You can combine the following constants by using bitwise operations: VALIDATE_ALL, VALIDATE_TYPES, VALIDATE_LENGTHS,
VALIDATE_CONSTRAINTS, VALIDATE_NONE. For example to enable all validations except length validations you would use:
<code>
VALIDATE_ALL & ~VALIDATE_LENGTHS
</code>
+++ More Validation
The type and length validations are handy but most of the time they're not enough. Therefore Doctrine provides some mechanisms that can be used to validate your data in more detail.
Validators are an easy way to specify further validations. Doctrine has a lot of predefined validators that are frequently needed such as email, country, ip, range and regexp validators. You find a full list of available validators at the bottom of this page. You can specify which validators apply to which column through the 4th argument of the {{hasColumn()}} method. If that is still not enough and you need some specialized validation that is not yet available as a predefined validator you have three options:
* You can write the validator on your own.
* You can propose your need for a new validator to a Doctrine developer.
* You can use validation hooks.
The first two options are advisable if it is likely that the validation is of general use and is potentially applicable in many situations. In that case it is a good idea to implement a new validator. However if the validation is special it is better to use hooks provided by Doctrine:
* {{validate()}} (Executed every time the record gets validated)
* {{validateOnInsert()}} (Executed when the record is new and gets validated)
* {{validateOnUpdate()}} (Executed when the record is not new and gets validated)
If you need a special validation in your active record you can simply override one of these methods in your active record class (a descendant of {{Doctrine_Record}}). Within thess methods you can use all the power of PHP to validate your fields. When a field doesnt pass your validation you can then add errors to the record's error stack. The following code snippet shows an example of how to define validators together with custom validation:
<code type="php">
class User extends Doctrine_Record
{
public function setUp()
{
$this->ownsOne('Email', array('local' => 'email_id'));
}
public function setTableDefinition()
{
// no special validators used only types
// and lengths will be validated
$this->hasColumn('name', 'string', 15);
$this->hasColumn('email_id', 'integer');
$this->hasColumn('created', 'integer', 11);
}
// Our own validation
protected function validate()
{
if ($this->name == 'God') {
// Blasphemy! Stop that! ;-)
// syntax: add(<fieldName>, <error code/identifier>)
$this->getErrorStack()->add('name', 'forbiddenName');
}
}
}
class Email extends Doctrine_Record {
public function setTableDefinition() {
// validators 'email' and 'unique' used
$this->hasColumn("address","string",150, array("email", "unique"));
}
}
</code>
+++ Valid or Not Valid
Now that you know how to specify your business rules in your models, it is time to look at how to deal with these rules in the rest of your application.
++++ Implicit validation
Whenever a record is going to be saved to the persistent data store (i.e. through calling {{$record->save()}}) the full validation procedure is executed. If errors occur during that process an exception of the type {{Doctrine_Validator_Exception}} will be thrown. You can catch that exception and analyze the errors by using the instance method {{Doctine_Validator_Exception::getInvalidRecords()}}. This method returns an ordinary array with references to all records that did not pass validation. You can then further explore the errors of each record by analyzing the error stack of each record. The error stack of a record can be obtained with the instance method {{Doctrine_Record::getErrorStack()}}. Each error stack is an instance of the class {{Doctrine_Validator_ErrorStack}}. The error stack provides an easy to use interface to inspect the errors.
++++ Explicit validation
You can explicitly trigger the validation for any record at any time. For this purpose Doctrine_Record provides the instance method {{Doctrine_Record::isValid()}}. This method returns a boolean value indicating the result of the validation. If the method returns false, you can inspect the error stack in the same way as seen above except that no exception is thrown, so you simply obtain the error stack of the record that didnt pass validation through {{Doctrine_Record::getErrorStack()}}.
The following code snippet shows an example of handling implicit validation which caused a {{Doctrine_Validator_Exception}}.
<code type="php">
try {
$user->name = "this is an example of too long name";
$user->Email->address = "drink@@notvalid..";
$user->save();
} catch(Doctrine_Validator_Exception $e) {
// Note: you could also use $e->getInvalidRecords(). The direct way
// used here is just more simple when you know the records you're dealing with.
$userErrors = $user->getErrorStack();
$emailErrors = $user->Email->getErrorStack();
/* Inspect user errors */
foreach($userErrors as $fieldName => $errorCodes) {
switch ($fieldName) {
case 'name':
// $user->name is invalid. inspect the error codes if needed.
break;
}
}
/* Inspect email errors */
foreach($emailErrors as $fieldName => $errorCodes) {
switch ($fieldName) {
case 'address':
// $user->Email->address is invalid. inspect the error codes if needed.
break;
}
}
}
</code>
+++ Introduction
Database views can greatly increase the performance of complex queries. You can think of them as cached queries. {{Doctrine_View}} provides integration between database views and DQL queries.
+++ Managing views
<code type="php">
$conn = Doctrine_Manager::getInstance()
->openConnection(new PDO("dsn","username","password"));
$query = new Doctrine_Query($conn);
$query->from('User.Phonenumber')->limit(20);
$view = new Doctrine_View($query, 'MyView');
// creating a database view
$view->create();
// dropping the view from the database
$view->drop();
</code>
+++ Using views
<code type="php">
$conn = Doctrine_Manager::getInstance()
->openConnection(new PDO("dsn","username","password"));
$query = new Doctrine_Query($conn);
$query->from('User.Phonenumber')->limit(20);
// hook the query into appropriate view
$view = new Doctrine_View($query, 'MyView');
// now fetch the data from the view
$coll = $view->execute();
</code>
This chapter describes various plugins availible for Doctrine. When refering to plugins we refer to classes that extend Doctrine_Plugin. All the components in this chapter can be considered 'core' plugins, that means they reside at the Doctrine main repository. There are other official plugins too which can be found at the homesite of the Sensei project (www.sensei-project.org).
++ Validators ++ Validators
++ View ++ View
++ Profiler ++ Profiler
......
+++ Introduction
Caching is one of the most influental things when it comes to performance tuning. {{Doctrine_Cache}} provides means for caching queries and for managing the cached queries.
+++ Query cache
+++ Introduction
+++ Creating new listener
Creating a new listener is very easy. You can set the listener in global, connection or factory level.
<code type="php">
class MyListener extends Doctrine_EventListener {
public function onLoad(Doctrine_Record $record) {
print $record->getTable()->getComponentName()." just got loaded!";
}
public function onSave(Doctrine_Record $record) {
print "saved data access object!";
}
}
class MyListener2 extends Doctrine_EventListener {
public function onPreUpdate() {
try {
$record->set("updated",time());
} catch(InvalidKeyException $e) {
}
}
}
// setting global listener
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_LISTENER,new MyListener());
// setting connection level listener
$conn = $manager->openConnection($dbh);
$conn->setAttribute(Doctrine::ATTR_LISTENER,new MyListener2());
// setting factory level listener
$table = $conn->getTable("User");
$table->setAttribute(Doctrine::ATTR_LISTENER,new MyListener());
</code>
+++ List of events
Here is a list of availible events and their parameters:
<code type="php">
interface Doctrine_EventListener_Interface {
public function onLoad(Doctrine_Record $record);
public function onPreLoad(Doctrine_Record $record);
public function onUpdate(Doctrine_Record $record);
public function onPreUpdate(Doctrine_Record $record);
public function onCreate(Doctrine_Record $record);
public function onPreCreate(Doctrine_Record $record);
public function onSave(Doctrine_Record $record);
public function onPreSave(Doctrine_Record $record);
public function onInsert(Doctrine_Record $record);
public function onPreInsert(Doctrine_Record $record);
public function onDelete(Doctrine_Record $record);
public function onPreDelete(Doctrine_Record $record);
public function onEvict(Doctrine_Record $record);
public function onPreEvict(Doctrine_Record $record);
public function onSleep(Doctrine_Record $record);
public function onWakeUp(Doctrine_Record $record);
public function onClose(Doctrine_Connection $connection);
public function onPreClose(Doctrine_Connection $connection);
public function onOpen(Doctrine_Connection $connection);
public function onTransactionCommit(Doctrine_Connection $connection);
public function onPreTransactionCommit(Doctrine_Connection $connection);
public function onTransactionRollback(Doctrine_Connection $connection);
public function onPreTransactionRollback(Doctrine_Connection $connection);
public function onTransactionBegin(Doctrine_Connection $connection);
public function onPreTransactionBegin(Doctrine_Connection $connection);
public function onCollectionDelete(Doctrine_Collection $collection);
public function onPreCollectionDelete(Doctrine_Collection $collection);
}
</code>
+++ Listening events
<code type="php">
$table = $conn->getTable("User");
$table->setEventListener(new MyListener2());
// retrieve user whose primary key is 2
$user = $table->find(2);
$user->name = "John Locke";
// update event will be listened and current time will be assigned to the field 'updated'
$user->save();
</code>
+++ Chaining
+++ AccessorInvoker
<code type="php">
class User {
public function setTableDefinition() {
$this->hasColumn("name", "string", 200);
$this->hasColumn("password", "string", 32);
}
public function setPassword($password) {
return md5($password);
}
public function getName($name) {
return strtoupper($name);
}
}
$user = new User();
$user->name = 'someone';
print $user->name; // someone
$user->password = '123';
print $user->password; // 123
$user->setAttribute(Doctrine::ATTR_LISTENER, new Doctrine_EventListener_AccessorInvoker());
print $user->name; // SOMEONE
$user->password = '123';
print $user->password; // 202cb962ac59075b964b07152d234b70
</code>
+++ Creating a logger
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment