Commit 95bd6100 authored by doctrine's avatar doctrine

Manual added

parent a33e2b89
<?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 session level listener
$session = $manager->openSession($dbh);
$session->setAttribute(Doctrine::ATTR_LISTENER,new MyListener2());
// setting factory level listener
$table = $session->getTable("User");
$table->setAttribute(Doctrine::ATTR_LISTENER,new MyListener());
?>
<?php
interface iDoctrine_EventListener {
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_Session $session);
public function onPreClose(Doctrine_Session $session);
public function onOpen(Doctrine_Session $session);
public function onTransactionCommit(Doctrine_Session $session);
public function onPreTransactionCommit(Doctrine_Session $session);
public function onTransactionRollback(Doctrine_Session $session);
public function onPreTransactionRollback(Doctrine_Session $session);
public function onTransactionBegin(Doctrine_Session $session);
public function onPreTransactionBegin(Doctrine_Session $session);
public function onCollectionDelete(Doctrine_Collection $collection);
public function onPreCollectionDelete(Doctrine_Collection $collection);
}
?>
<?php
$table = $session->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();
?>
<?php
$hook = new Doctrine_Hook($table, $fields);
?>
<?php
try {
$user->name = "this is an example of too long name";
$user->Email->address = "drink@@notvalid..";
$user->save();
} catch(Doctrine_Validator_Exception $e) {
$stack = $e->getErrorStack();
foreach($stack as $component => $err) {
foreach($err as $field => $type) {
switch($type):
case Doctrine_Validator::ERR_TYPE:
print $field." is not right type";
break;
case Doctrine_Validator::ERR_UNIQUE:
print $field." is not unique";
break;
case Doctrine_Validator::ERR_VALID:
print $field." is not valid";
break;
case Doctrine_Validator::ERR_LENGTH:
print $field." is too long";
break;
endswitch;
}
}
}
?>
<?php
// turning on validation
Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_VLD, true);
?>
<?php
class User extends Doctrine_Record {
public function setUp() {
$this->ownsOne("Email","User.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);
}
}
class Email extends Doctrine_Record {
public function setTableDefinition() {
// specialized validators 'email' and 'unique' used
$this->hasColumn("address","string",150,"email|unique");
}
}
$session = Doctrine_Manager::getInstance()->openSession(new PDO("dsn","username","password"));
$user = new User();
$user->name = "this is an example of too long name";
$user->save(); // throws a Doctrine_Validator_Exception
$user->name = "valid name";
$user->created = "not valid"; // not valid type
$user->save(); // throws a Doctrine_Validator_Exception
$user->created = time();
$user->Email->address = "drink@.."; // not valid email address
$user->save(); // throws a Doctrine_Validator_Exception
$user->Email->address = "drink@drinkmore.info";
$user->save(); // saved
$user = $session->create("User");
$user->Email->address = "drink@drinkmore.info"; // not unique!
$user->save(); // throws a Doctrine_Validator_Exception
?>
<?php
$table = $session->getTable("User");
$users = $table->findAll();
// accessing elements with ArrayAccess interface
$users[0]->name = "Jack Daniels";
$users[1]->name = "John Locke";
// accessing elements with get()
print $users->get(1)->name;
?>
<?php
$users = $table->findAll();
print count($users); // 5
$users[5]->name = "new user 1";
$users[6]->name = "new user 2";
?>
<?php
// delete all users with name 'John'
$users = $table->findBySql("name LIKE '%John%'");
$users->delete();
?>
<?php
$table = $session->getTable("User");
$table->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_IMMEDIATE);
$users = $table->findAll();
// or
$users = $session->query("FROM User-I"); // immediate collection
foreach($users as $user) {
print $user->name;
}
$table->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_LAZY);
$users = $table->findAll();
// or
$users = $session->query("FROM User-L"); // lazy collection
foreach($users as $user) {
print $user->name;
}
$table->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_BATCH);
$users = $table->findAll();
// or
$users = $session->query("FROM User-B"); // batch collection
foreach($users as $user) {
print $user->name;
}
$table->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_OFFSET);
$users = $table->findAll();
// or
$users = $session->query("FROM User-O"); // offset collection
foreach($users as $user) {
print $user->name;
}
?>
<?php
$users = $table->findAll();
$users->count();
// or
count($users); // Doctrine_Collection implements Countable interface
?>
<?php
$users = $table->findAll();
$users[0]->name = "Jack Daniels";
$users[1]->name = "John Locke";
$users->save();
?>
<?php
// Doctrine_Manager controls all the sessions
$manager = Doctrine_Manager::getInstance();
// open first session
$session = $manager->openSession(new PDO("dsn","username","password"), "session 1");
// open second session
$session2 = $manager->openSession(new PDO("dsn2","username2","password2"), "session 2");
$manager->getCurrentSession(); // $session2
$manager->setCurrentSession("session 1");
$manager->getCurrentSession(); // $session
// iterating through sessions
foreach($manager as $session) {
}
?>
<?php
// Doctrine_Manager controls all the sessions
$manager = Doctrine_Manager::getInstance();
// Doctrine_Session
// a script may have multiple open sessions
// (= multiple database connections)
$dbh = new PDO("dsn","username","password");
$session = $manager->openSession();
// or if you want to use Doctrine Doctrine_DB and its
// performance monitoring capabilities
$dsn = "schema://username:password@dsn/dbname";
$dbh = Doctrine_DB::getConnection($dsn);
$session = $manager->openSession();
?>
<?php
$query->from("User")
->where("User.name = ?");
$query->execute(array('Jack Daniels'));
?>
<?php
// find all users
$coll = $session->query("FROM User");
// find all groups
$coll = $session->query("FROM Group");
// find all users and user emails
$coll = $session->query("FROM User, User.Email");
?>
<?php
// select all users and load the data directly (Immediate fetching strategy)
$coll = $session->query("FROM User-I");
// or
$coll = $session->query("FROM User-IMMEDIATE");
// select all users and load the data in batches
$coll = $session->query("FROM User-B");
// or
$coll = $session->query("FROM User-BATCH");
// select all user and use lazy fetching
$coll = $session->query("FROM User-L");
// or
$coll = $session->query("FROM User-LAZY");
?>
<?php
// find the first ten users and their emails
$coll = $session->query("FROM User, User.Email LIMIT 10");
// find the first ten users starting from the user number 5
$coll = $session->query("FROM User LIMIT 10 OFFSET 5");
?>
<?php
// retrieve all users with only their properties id and name loaded
$users = $session->query("FROM User(id, name)");
?>
<?php
$session = Doctrine_Manager::getInstance()->openSession(new PDO("dsn","username","password"));
$query = new Doctrine_Query($session);
$query->from("User-b")
->where("User.name LIKE 'Jack%'")
->orderby("User.created");
->limit(5);
$users = $query->execute();
?>
<?php
// find all users, sort by name descending
$coll = $session->query("FROM User ORDER BY User.name DESC");
// find all users sort by name ascending
$coll = $session->query("FROM User ORDER BY User.name ASC");
// or
$coll = $session->query("FROM User ORDER BY User.name");
// find all users and their emails, sort by email address
$coll = $session->query("FROM User, User.Email ORDER BY User.Email.address");
// find all users and their emails, sort by user name and email address
$coll = $session->query("FROM User, User.Email ORDER BY User.name, User.Email.address");
?>
<?php
$query->from("User:Email");
$query->execute();
// executed SQL query:
// SELECT ... FROM user INNER JOIN email ON ...
$query->from("User.Email");
$query->execute();
// executed SQL query:
// SELECT ... FROM user LEFT JOIN email ON ...
?>
<?php
// find all groups where the group primary key is bigger than 10
$coll = $session->query("FROM Group WHERE Group.id > 10");
// find all users where users where user name matches a regular expression,
// REGEXP keyword must be supported by the underlying database
$coll = $session->query("FROM User WHERE User.name REGEXP '[ad]'");
// find all users and their associated emails where SOME of the users phonenumbers
// (the association between user and phonenumber tables is Many-To-Many) starts with 123
$coll = $session->query("FROM User, User.Email WHERE User.Phonenumber.phonenumber LIKE '123%'");
// multiple conditions
$coll = $session->query("FROM User WHERE User.name LIKE '%Jack%' && User.Email.address LIKE '%@drinkmore.info'");
// nesting conditions
$coll = $session->query("FROM User WHERE (User.name LIKE '%Jack%' || User.name LIKE '%John%') && User.Email.address LIKE '%@drinkmore.info'");
?>
<?php
$query = new Doctrine_RawSql($session);
$query->select('{entity.name}')
->from('entity');
$query->addComponent("entity", "User");
$coll = $query->execute();
?>
<?php
$query = new Doctrine_RawSql($session);
$query->parseQuery("SELECT {entity.name} FROM entity");
$entities = $query->execute();
?>
<?php
$user = $table->find(3);
// access property through overloading
$name = $user->name;
// access property with get()
$name = $user->get("name");
// access property with ArrayAccess interface
$name = $user['name'];
// iterating through properties
foreach($user as $key => $value) {
}
?>
<?php
$user = $session->create("User");
// alternative way:
$table = $session->getTable("User");
$user = $table->create();
// the simpliest way:
$user = new User();
// records support array access
$user["name"] = "John Locke";
// save user into database
$user->save();
?>
<?php
$table = $session->getTable("User");
try {
$user = $table->find(2);
} catch(Doctrine_Find_Exception $e) {
print "Couldn't find user";
}
// deletes user and all related composite objects
$user->delete();
$users = $table->findAll();
// delete all users and their related composite objects
$users->delete();
?>
<?php
$state = $record->getState();
switch($state):
case Doctrine_Record::STATE_PROXY:
// data access object is in proxy state,
// meaning its persistent but not all of its properties are
// loaded from the database
break;
case Doctrine_Record::STATE_TCLEAN:
// data access object is transient clean,
// meaning its transient and
// none of its properties are changed
break;
case Doctrine_Record::STATE_TDIRTY:
// data access object is transient dirty,
// meaning its transient and
// some of its properties are changed
break;
case Doctrine_Record::STATE_DIRTY:
// data access object is dirty,
// meaning its persistent and
// some of its properties are changed
break;
case Doctrine_Record::STATE_CLEAN:
// data access object is clean,
// meaning its persistent and
// none of its properties are changed
break;
endswitch;
?>
<?php
$table = $session->getTable("User");
// find by primary key
try {
$user = $table->find(2);
} catch(Doctrine_Find_Exception $e) {
print "Couldn't find user";
}
// get all users
foreach($table->findAll() as $user) {
print $user->name;
}
// finding by sql
foreach($table->findBySql("name LIKE '%John%'") as $user) {
print $user->created;
}
// finding objects with DQL
$users = $session->query("FROM User WHERE User.name LIKE '%John%'");
?>
<?php
$string = serialize($user);
$user = unserialize($string);
?>
<?php
$table = $session->getTable("User");
try {
$user = $table->find(2);
} catch(Doctrine_Find_Exception $e) {
print "Couldn't find user";
}
$user->name = "Jack Daniels";
$user->save();
?>
<?php
$user = new User();
$user->name = "Jack";
$group = $session->create("Group");
$group->name = "Drinking Club";
// saves all the changed objects into database
$session->flush();
?>
<?php
$manager = Doctrine_Manager::getInstance();
// open new session
$session = $manager->openSession(new PDO("dsn","username","password"));
// getting a table object
$table = $session->getTable("User");
?>
<?php
switch($session->getState())
case Doctrine_Session::STATE_ACTIVE:
// session open and zero open transactions
break;
case Doctrine_Session::STATE_ACTIVE:
// one open transaction
break;
case Doctrine_Session::STATE_BUSY:
// multiple open transactions
break;
case Doctrine_Session::STATE_CLOSED:
// session closed
break;
endswitch;
?>
<?php
// select all users
$session->query("FROM User");
// select all users where user email is jackdaniels@drinkmore.info
$session->query("FROM User WHERE User.Email.address = 'jackdaniels@drinkmore.info'");
?>
<?php
class UserTable extends Doctrine_Table {
/**
* you can add your own finder methods here
*/
public function findByName($name) {
return $this->getSession()->query("FROM User WHERE name LIKE '%$name%'");
}
}
class User extends Doctrine_Record { }
$session = Doctrine_Manager::getInstance()->openSession(new PDO("dsn","username","password"));
// doctrine will now check if a class called UserTable exists and if it inherits Doctrine_Table
$table = $session->getTable("User");
print get_class($table); // UserTable
$users = $table->findByName("Jack");
?>
<?php
// valid table object
class UserTable extends Doctrine_Table {
}
// not valid [doesn't extend Doctrine_Table]
class GroupTable { }
?>
<?php
$table = $session->getTable("User");
// find by primary key
try {
$user = $table->find(2);
} catch(Doctrine_Find_Exception $e) {
print "Couldn't find user";
}
// get all users
foreach($table->findAll() as $user) {
print $user->name;
}
// finding by sql
foreach($table->findBySql("name LIKE '%John%'") as $user) {
print $user->created;
}
?>
<?php
$table = $session->getTable('User');
// getting column names
$names = $table->getColumnNames();
// getting column information
$columns = $table->getColumns();
?>
<?php
class Customer extends Doctrine_Record {
public function setUp() {
// setup code goes here
}
public function setTableDefinition() {
// table definition code goes here
}
public function getAvailibleProducts() {
// some code
}
public function setName($name) {
if($this->isValidName($name))
$this->set("name",$name);
}
public function getName() {
return $this->get("name");
}
}
?>
<?php
// custom primary key column name
class Group extends Doctrine_Record {
public function setUp() {
$this->setPrimaryKeyColumn("group_id");
}
}
?>
<?php
// setting default batch size for batch collections
$manager->setAttribute(Doctrine::ATTR_BATCH_SIZE, 7);
?>
<?php
// setting default event listener
$manager->setAttribute(Doctrine::ATTR_LISTENER, new MyListener());
?>
<?php
// sets the default collection type (fetching strategy)
$manager->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_LAZY);
?>
<?php
// sets the default offset collection limit
$manager->setAttribute(Doctrine::ATTR_COLL_LIMIT, 10);
?>
<?php
// setting default lockmode
$manager->setAttribute(Doctrine::ATTR_LOCKMODE, Doctrine::LOCK_PESSIMISTIC);
?>
<?php
// turns automatic table creation off
$manager->setAttribute(Doctrine::ATTR_CREATE_TABLES, false);
?>
<?php
// turns transactional validation on
$manager->setAttribute(Doctrine::ATTR_VLD, true);
?>
<?php
class Email extends Doctrine_Record {
public function setUp() {
$this->setAttribute(Doctrine::ATTR_LISTENER,new MyListener());
}
public function setTableDefinition() {
$this->hasColumn("address","string",150,"email|unique");
}
}
?>
<?php
// setting default fetchmode
// availible fetchmodes are Doctrine::FETCH_LAZY, Doctrine::FETCH_IMMEDIATE and Doctrine::FETCH_BATCH
// the default fetchmode is Doctrine::FETCH_LAZY
class Address extends Doctrine_Record {
public function setUp() {
$this->setAttribute(Doctrine::ATTR_FETCHMODE,Doctrine::FETCH_IMMEDIATE);
}
}
?>
<?php
// using sequences
class User extends Doctrine_Record {
public function setUp() {
$this->setSequenceName("user_seq");
}
}
?>
<?php
$sess = Doctrine_Manager::getInstance()->openSession(new PDO("dsn","username","password"));
// select first ten rows starting from the row 20
$sess->select("select * from user",10,20);
?>
<?php
try {
$session->beginTransaction();
$user->save();
$session->beginTransaction();
$group->save();
$email->save();
$session->commit();
$session->commit();
} catch(Exception $e) {
$session->rollback();
}
?>
<?php
// works only if you use doctrine database handler
$dbh = $session->getDBH();
$times = $dbh->getExecTimes();
// print all executed queries and their execution times
foreach($dbh->getQueries() as $index => $query) {
print $query." ".$times[$index];
}
?>
<?php
$sess = Doctrine_Manager::getInstance()->openSession(new PDO("dsn","username","password"));
// gets the next ID from a sequence
$sess->getNextID($sequence);
?>
<?php
$sess = Doctrine_Manager::getInstance()->openSession(new PDO("dsn","username","password"));
try {
$sess->beginTransaction();
// some database operations
$sess->commit();
} catch(Exception $e) {
$sess->rollback();
}
?>
<?php
require_once("path-to-doctrine/Doctrine.class.php");
// autoloading objects
function __autoload($class) {
Doctrine::autoload($class);
}
// loading all components
Doctrine::loadAll();
?>
<?php
class Email extends Doctrine_Record {
public function setTableDefinition() {
/**
* email table has one column named 'address' which is
* php type 'string'
* maximum length 200
* database constraints: UNIQUE
* validators: email, unique
*
*/
$this->hasColumn("address","string",200,"email|unique");
}
}
?>
<?php
class User extends Doctrine_Record {
public function setTableDefinition() {
// set 'user' table columns, note that
// id column is always auto-created
$this->hasColumn("name","string",30);
$this->hasColumn("username","string",20);
$this->hasColumn("password","string",16);
$this->hasColumn("created","integer",11);
}
}
?>
<?php
// NOTE: related record have always the first letter in uppercase
$email = $user->Email;
$email->address = "jackdaniels@drinkmore.info";
$user->save();
// alternative:
$user->Email->address = "jackdaniels@drinkmore.info";
$user->save();
?>
<?php
$user->Email->delete();
$user->Phonenumber[3]->delete();
// deleting user and all related objects:
$user->delete();
?>
<?php
print $user->Email["address"];
print $user->Phonenumber[0]->phonenumber;
print $user->Group[0]->get("name");
?>
<?php
$user->Email["address"] = "koskenkorva@drinkmore.info";
$user->Phonenumber[0]->phonenumber = "123123";
$user->save();
// saves the email and phonenumber
?>
<?php
class User extends Doctrine_Record {
public function setUp() {
$this->ownsMany("Phonenumber","Phonenumber.user_id");
}
public function setTableDefition() {
$this->hasColumn("name","string",50);
$this->hasColumn("loginname","string",20);
$this->hasColumn("password","string",16);
}
}
class Phonenumber extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("phonenumber","string",50);
$this->hasColumn("user_id","integer");
}
}
?>
<?php
class User extends Doctrine_Record {
public function setUp() {
$this->hasOne("Address","Address.user_id");
$this->ownsOne("Email","User.email_id");
$this->ownsMany("Phonenumber","Phonenumber.user_id");
}
public function setTableDefition() {
$this->hasColumn("name","string",50);
$this->hasColumn("loginname","string",20);
$this->hasColumn("password","string",16);
// foreign key column for email ID
$this->hasColumn("email_id","integer");
}
}
class Email extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("address","string",150);
}
}
class Address extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("street","string",50);
$this->hasColumn("user_id","integer");
}
}
?>
<?php
class Task extends Doctrine_Record {
public function setUp() {
$this->hasOne("Task as Parent","Task.parent_id");
$this->hasMany("Task as Subtask","Subtask.parent_id");
}
public function setTableDefinition() {
$this->hasColumn("name","string",100);
$this->hasColumn("parent_id","integer");
}
}
?>
<?php
class Entity extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("name","string",30);
$this->hasColumn("username","string",20);
$this->hasColumn("password","string",16);
$this->hasColumn("created","integer",11);
}
}
class User extends Entity {
public function setUp() {
$this->setInheritanceMap(array("type"=>1);
}
}
class Group extends Entity {
public function setUp() {
$this->setInheritanceMap(array("type"=>2);
}
}
?>
<?php
class Entity extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("name","string",30);
$this->hasColumn("username","string",20);
$this->hasColumn("password","string",16);
$this->hasColumn("created","integer",11);
}
}
class User extends Entity { }
class Group extends Entity { }
?>
<?php
class Entity extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("name","string",30);
$this->hasColumn("username","string",20);
$this->hasColumn("password","string",16);
$this->hasColumn("created","integer",11);
}
}
class User extends Entity {
public function setTableDefinition() {
// the following method call is needed in
// one-table-one-class inheritance
parent::setTableDefinition();
}
}
class Group extends Entity {
public function setTableDefinition() {
// the following method call is needed in
// one-table-one-class inheritance
parent::setTableDefinition();
}
}
?>
<?php
class User extends Doctrine_Record {
public function setUp() {
$this->hasMany("Group","Groupuser.group_id");
}
public function setTableDefinition() {
$this->hasColumn("name","string",30);
}
}
class Group extends Doctrine_Record {
public function setUp() {
$this->hasMany("User","Groupuser.user_id");
}
public function setTableDefinition() {
$this->hasColumn("name","string",30);
}
}
class Groupuser extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("user_id","integer");
$this->hasColumn("group_id","integer");
}
}
$user = new User();
// add two groups
$user->Group[0]->name = "First Group";
$user->Group[1]->name = "Second Group";
// save changes into database
$user->save();
$groups = new Doctrine_Collection($session->getTable("Group"));
$groups[0]->name = "Third Group";
$groups[1]->name = "Fourth Group";
$user->Group[2] = $groups[0];
// $user will now have 3 groups
$user->Group = $groups;
// $user will now have two groups 'Third Group' and 'Fourth Group'
?>
<?php
class User extends Doctrine_Record {
public function setUp() {
$this->hasMany("User as Friend","UserReference.user_id-user_id2");
}
public function setTableDefinition() {
$this->hasColumn("name","string",30);
}
}
class UserReference extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("user_id","integer");
$this->hasColumn("user_id2","integer");
}
}
?>
<?php
class Forum_Board extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("name", "string", 100);
$this->hasColumn("description", "string", 5000);
}
public function setUp() {
// notice the 'as' keyword here
$this->ownsMany("Forum_Thread as Threads", "Forum_Thread.board_id");
}
}
class Forum_Thread extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("board_id", "integer", 10);
$this->hasColumn("updated", "integer", 10);
$this->hasColumn("closed", "integer", 1);
}
public function setUp() {
// notice the 'as' keyword here
$this->hasOne("Forum_Board as Board", "Forum_Thread.board_id");
}
}
$board = new Board();
$board->Threads[0]->updated = time();
?>
<?php
/**
* lets presume $users contains a collection of users
* each having 0-1 email and 0-* phonenumbers
*/
$users->delete();
/**
* On session drivers other than mysql doctrine would now perform three queries
* regardless of how many users, emails and phonenumbers there are
*
* the queries would look something like:
* DELETE FROM entity WHERE entity.id IN (1,2,3, ... ,15)
* DELETE FROM phonenumber WHERE phonenumber.id IN (4,6,7,8)
* DELETE FROM email WHERE email.id IN (1,2, ... ,10)
*
* On mysql doctrine is EVEN SMARTER! Now it would perform only one query!
* the query would look like:
* DELETE entity, email, phonenumber FROM entity
* LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id, email
* WHERE (entity.email_id = email.id) && (entity.id IN(4, 5, 6, 7, 8, 9, 10, 11)) && (entity.type = 0)
*/
?>
<?php
// lets presume $users contains a collection of new users
// each having 0-1 email and 0-* phonenumbers
$users->save();
/**
* now doctrine would perform prepared queries in the following order:
*
* first the emails since every user needs to get the primary key of their newly created email
* INSERT INTO email (address) VALUES (:address)
* INSERT INTO email (address) VALUES (:address)
* INSERT INTO email (address) VALUES (:address)
*
* then the users
* INSERT INTO entity (name,email_id) VALUES (:name,:email_id)
* INSERT INTO entity (name,email_id) VALUES (:name,:email_id)
* INSERT INTO entity (name,email_id) VALUES (:name,:email_id)
*
* and at last the phonenumbers since they need the primary keys of the newly created users
* INSERT INTO phonenumber (phonenumber,entity_id) VALUES (:phonenumber,:entity_id)
* INSERT INTO phonenumber (phonenumber,entity_id) VALUES (:phonenumber,:entity_id)
* INSERT INTO phonenumber (phonenumber,entity_id) VALUES (:phonenumber,:entity_id)
* INSERT INTO phonenumber (phonenumber,entity_id) VALUES (:phonenumber,:entity_id)
* INSERT INTO phonenumber (phonenumber,entity_id) VALUES (:phonenumber,:entity_id)
*
* These operations are considerably fast, since many databases perform multiple
* prepared queries very rapidly
*/
?>
<?php
class Forum_Category extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("root_category_id", "integer", 10);
$this->hasColumn("parent_category_id", "integer", 10);
$this->hasColumn("name", "string", 50);
$this->hasColumn("description", "string", 99999);
}
public function setUp() {
$this->hasMany("Forum_Category as Subcategory", "Subcategory.parent_category_id");
$this->hasOne("Forum_Category as Rootcategory", "Forum_Category.root_category_id");
}
}
class Forum_Board extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("category_id", "integer", 10);
$this->hasColumn("name", "string", 100);
$this->hasColumn("description", "string", 5000);
}
public function setUp() {
$this->hasOne("Forum_Category as Category", "Forum_Board.category_id");
$this->ownsMany("Forum_Thread as Threads", "Forum_Thread.board_id");
}
}
class Forum_Entry extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("author", "string", 50);
$this->hasColumn("topic", "string", 100);
$this->hasColumn("message", "string", 99999);
$this->hasColumn("parent_entry_id", "integer", 10);
$this->hasColumn("thread_id", "integer", 10);
$this->hasColumn("date", "integer", 10);
}
public function setUp() {
$this->hasOne("Forum_Entry as Parent", "Forum_Entry.parent_entry_id");
$this->hasOne("Forum_Thread as Thread", "Forum_Entry.thread_id");
}
}
class Forum_Thread extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("board_id", "integer", 10);
$this->hasColumn("updated", "integer", 10);
$this->hasColumn("closed", "integer", 1);
}
public function setUp() {
$this->hasOne("Forum_Board as Board", "Forum_Thread.board_id");
$this->ownsMany("Forum_Entry as Entries", "Forum_Entry.thread_id");
}
}
?>
<?php
class Entity extends Doctrine_Record {
public function setUp() {
$this->ownsOne("Email","Entity.email_id");
$this->ownsMany("Phonenumber","Phonenumber.entity_id");
$this->setAttribute(Doctrine::ATTR_FETCHMODE,Doctrine::FETCH_BATCH);
$this->setAttribute(Doctrine::ATTR_LISTENER,new EntityListener());
}
public function setTableDefinition() {
$this->hasColumn("name","string",50);
$this->hasColumn("loginname","string",20);
$this->hasColumn("password","string",16);
$this->hasColumn("type","integer",1);
$this->hasColumn("created","integer",11);
$this->hasColumn("updated","integer",11);
$this->hasColumn("email_id","integer");
}
}
}
class Group extends Entity {
public function setUp() {
parent::setUp();
$this->hasMany("User","Groupuser.user_id");
$this->setInheritanceMap(array("type"=>1));
}
}
class User extends Entity {
public function setUp() {
parent::setUp();
$this->hasMany("Group","Groupuser.group_id");
$this->setInheritanceMap(array("type"=>0));
}
}
class Groupuser extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("group_id","integer");
$this->hasColumn("user_id","integer");
}
}
class Phonenumber extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("phonenumber","string",20);
$this->hasColumn("entity_id","integer");
}
}
class Email extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("address","string",150,"email|unique");
}
}
class EntityListener extends Doctrine_EventListener {
public function onPreUpdate(Doctrine_Record $record) {
$record->updated = time();
}
public function onPreInsert(Doctrine_Record $record) {
$record->created = time();
}
}
// USER MANAGEMENT SYSTEM IN ACTION:
$manager = Doctrine_Manager::getInstance();
$session = $manager->openSession(new PDO("DSN","username","password"));
$user = new User();
$user->name = "Jack Daniels";
$user->Email->address = "jackdaniels@drinkmore.info";
$user->Phonenumber[0]->phonenumber = "123 123";
$user->Phonenumber[1]->phonenumber = "133 133";
$user->save();
$user->Group[0]->name = "beer lovers";
$user->Group[0]->Email->address = "beerlovers@drinkmore.info";
$user->Group[0]->save();
?>
<?php
$user = $table->find(2);
// get state
$state = $user->getState();
print $user->name;
print $user["name"];
print $user->get("name");
$user->name = "Jack Daniels";
$user->set("name","Jack Daniels");
// serialize record
$serialized = serialize($user);
$user = unserialize($serialized);
// create a copy
$copy = $user->copy();
// get primary key
$id = $user->getID();
// print lots of useful info
print $user;
// save all the properties and composites
$user->save();
// delete this data access object and related objects
$user->delete();
?>
<?php
$sess = $manager->openSession(Doctrine_DB::getConnection("schema://username:password@hostname/database"));
// get session state:
switch($sess):
case Doctrine_Session::STATE_BUSY:
// multiple open transactions
break;
case Doctrine_Session::STATE_ACTIVE:
// one open transaction
break;
case Doctrine_Session::STATE_CLOSED:
// closed state
break;
case Doctrine_Session::STATE_OPEN:
// open state and zero open transactions
break;
endswitch;
// getting database handler
$dbh = $sess->getDBH();
// flushing the session
$sess->flush();
// print lots of useful info about session:
print $sess;
?>
<?php
$session->beginTransaction();
$user = new User();
$user->name = 'New user';
$user->save();
$user = $session->getTable('User')->find(5);
$user->name = 'Modified user';
$user->save();
$session->commit(); // all the queries are executed here
?>
<?php
function saveUserAndGroup(Doctrine_Session $session, User $user, Group $group) {
$session->beginTransaction();
$user->save();
$group->save();
$session->commit();
}
try {
$session->beginTransaction();
saveUserAndGroup($session,$user,$group);
saveUserAndGroup($session,$user2,$group2);
saveUserAndGroup($session,$user3,$group3);
$session->commit();
} catch(Doctrine_Exception $e) {
$session->rollback();
}
?>
<?php
$session->beginTransaction();
$user = new User();
$user->name = 'New user';
$user->save();
$user = $session->getTable('User')->find(5);
$user->name = 'Modified user';
$user->save();
$pending = $session->getInserts(); // an array containing one element
$pending = $session->getUpdates(); // an array containing one element
$session->commit(); // all the queries are executed here
?>
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.
Creating a new listener is very easy. You can set the listener in global, session or factory level.
Here is a list of availible events and their parameters:
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.
With Doctrine validators you can validate a whole transaction and get info of everything
that went wrong. Some Doctrine validators also act as a database level constraints. For example
adding a unique validator to column 'name' also adds a database level unique constraint into that
column.
<br \><br \>
Validators are added as a 4 argument for hasColumn() method. Validators should be separated
by '|' mark. For example email|unique would validate a value using Doctrine_Validator_Email
and Doctrine_Validator_Unique.
<br \><br \>
Doctrine has many predefined validators (see chapter 13.3). If you wish to use
custom validators just write *Validator classes and doctrine will automatically use them.
Here is a list of predefined validators. You cannot use these names for your custom validators.
<table width="500">
<tr>
<td>
<b>name</b>
</td>
<td>
<b>arguments</b>
</td>
<td>
<b>task</b>
</td>
</tr>
<tr>
<td>
email
</td>
<td>
</td>
<td>
Checks if value is valid email.
</td>
</tr>
<tr>
<td>
notblank
</td>
<td>
</td>
<td>
Checks if value is not blank.
</td>
</tr>
<tr>
<td>
notnull
</td>
<td>
</td>
<td>
Checks if value is not null.
</td>
</tr>
<tr>
<td>
country
</td>
<td>
</td>
<td>
Checks if value is valid country code.
</td>
</tr>
<tr>
<td>
ip
</td>
<td>
</td>
<td>
Checks if value is valid internet protocol address.
</td>
</tr>
<tr>
<td>
htmlcolor
</td>
<td>
</td>
<td>
Checks if value is valid html color.
</td>
</tr>
<tr>
<td>
nospace
</td>
<td>
</td>
<td>
Checks if value has no space chars.
</td>
</tr>
<tr>
<td>
range
</td>
<td>
[min, max]
</td>
<td>
Checks if value is in range specified by arguments.
</td>
</tr>
<tr>
<td>
unique
</td>
<td>
</td>
<td>
Checks if value is unique in its database table.
</td>
</tr>
<tr>
<td>
regexp
</td>
<td>
[expression]
</td>
<td>
Checks if value matches a given regexp.
</td>
</tr>
</table>
When the validation attribute is set as true all transactions will be validated, so whenever Doctrine_Record::save(),
Doctrine_Session::flush() or any other saving method is used all the properties of all records in that transaction will have their values
validated.
<br \><br \>
Validation errors are being stacked into Doctrine_Validator_Exception.
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.
You can access the elements of Doctrine_Collection with set() and get() methods or with ArrayAccess interface.
Doctrine Collections can be deleted in very same way is Doctrine Records you just call delete() method.
As for all collections Doctrine knows how to perform single-shot-delete meaning it only performs one
database query for the each collection.
<br \> <br \>
For example if we have collection of users which own [0-*] phonenumbers. When deleting the collection
of users doctrine only performs two queries for this whole transaction. The queries would look something like:
<br \><br \>
DELETE FROM user WHERE id IN (1,2,3, ... ,N)<br \>
DELETE FROM phonenumber WHERE id IN (1,2,3, ... ,M)<br \>
Whenever you fetch records with eg. Doctrine_Table::findAll or Doctrine_Session::query methods an instance of
Doctrine_Collection is returned. There are many types of collections in Doctrine and it is crucial to understand
the differences of these collections. Remember choosing the right fetching strategy (collection type) is one of the most
influental things when it comes to boosting application performance.
<br \><br \>
<li>Immediate Collection<ul>
Fetches all records and all record data immediately into collection memory. Use this collection only if you really need to show all that data
in web page.
<br \><br \>
Example query:<br \>
SELECT id, name, type, created FROM user
<br \><br \></ul>
<li>Batch Collection<ul>
Fetches all record primary keys into colletion memory. When individual collection elements are accessed this collection initializes proxy objects.
When the non-primary-key-property of a proxy object is accessed that object sends request to Batch collection which loads the data
for that specific proxy object as well as other objects close to that proxy object.
<br \><br \>
Example queries:<br \>
SELECT id FROM user<br \>
SELECT id, name, type, created FROM user WHERE id IN (1,2,3,4,5)<br \>
SELECT id, name, type, created FROM user WHERE id IN (6,7,8,9,10)<br \>
[ ... ]<br \>
</ul>
<li>Lazy Collection<ul>
Lazy collection is exactly same as Batch collection with batch size preset to one.
<br \><br \>
Example queries:<br \>
SELECT id FROM user<br \>
SELECT id, name, type, created FROM user WHERE id = 1<br \>
SELECT id, name, type, created FROM user WHERE id = 2<br \>
SELECT id, name, type, created FROM user WHERE id = 3<br \>
[ ... ]<br \>
</ul>
<li>Offset Collection<ul>
Offset collection is the same as immediate collection with the difference that it uses database provided limiting of queries.
<br \><br \>
Example queries:<br \>
SELECT id, name, type, created FROM user LIMIT 5<br \>
SELECT id, name, type, created FROM user LIMIT 5 OFFSET 5<br \>
SELECT id, name, type, created FROM user LIMIT 5 OFFSET 10<br \>
[ ... ]<br \></ul>
Doctrine_Manager is the heart of every Doctrine based application. Doctrine_Manager handles all sessions (database connections).
Switching between sessions in Doctrine is very easy, you just call Doctrine_Manager::setCurrentSession() method.
You can access the session by calling Doctrine_Manager::getSession() or Doctrine_Manager::getCurrentSession() if you only
want to get the current session.
In order to get your first application started you first
need to get an instance of Doctrine_Manager which handles all the sessions (database connections).
The second thing to do is to open a new session.
<?php
$str = "
The following examples should give a hint of how DQL is converted into SQL.
The classes used in here are the same as in chapter 14.1 (Users and groups are both entities etc).
DQL QUERY: FROM Email WHERE Email.address LIKE '%@example%'
SQL QUERY: SELECT email.id AS Email__id FROM email WHERE (email.address LIKE '%@example%')
DQL QUERY: FROM User(id) WHERE User.Phonenumber.phonenumber LIKE '%123%'
SQL QUERY: SELECT entity.id AS entity__id FROM entity LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id WHERE phonenumber.phonenumber LIKE '%123%' AND (entity.type = 0)
DQL QUERY: FROM Forum_Board(id).Threads(id).Entries(id)
SQL QUERY: SELECT forum_board.id AS forum_board__id, forum_thread.id AS forum_thread__id, forum_entry.id AS forum_entry__id FROM forum_board LEFT JOIN forum_thread ON forum_board.id = forum_thread.board_id LEFT JOIN forum_entry ON forum_thread.id = forum_entry.thread_id
DQL QUERY: FROM User(id) WHERE User.Group.name = 'Action Actors'
SQL QUERY: SELECT entity.id AS entity__id FROM entity LEFT JOIN groupuser ON entity.id = groupuser.user_id LEFT JOIN entity AS entity2 ON entity2.id = groupuser.group_id WHERE entity2.name = 'Action Actors' AND (entity.type = 0 AND (entity2.type = 1 OR entity2.type IS NULL))
DQL QUERY: FROM User(id) WHERE User.Group.Phonenumber.phonenumber LIKE '123 123'
SQL QUERY: SELECT entity.id AS entity__id FROM entity LEFT JOIN groupuser ON entity.id = groupuser.user_id LEFT JOIN entity AS entity2 ON entity2.id = groupuser.group_id LEFT JOIN phonenumber ON entity2.id = phonenumber.entity_id WHERE phonenumber.phonenumber LIKE '123 123' AND (entity.type = 0 AND (entity2.type = 1 OR entity2.type IS NULL))
";
$e = explode("\n",$str);
$color = "367FAC";
foreach($e as $line) {
if(strpos($line, "SQL") !== false)
$color = "A50A3D";
elseif(strpos($line, "DQL") !== false)
$color = "367FAC";
$l = str_replace("SELECT","<br \><font color='$color'><b>SELECT</b></font>",$line);
$l = str_replace("FROM","<br \><font color='$color'><b>FROM</b></font>",$l);
$l = str_replace("LEFT JOIN","<br \><font color='$color'><b>LEFT JOIN</b></font>",$l);
$l = str_replace("WHERE","<br \><font color='$color'><b>WHERE</b></font>",$l);
$l = str_replace("AS","<font color='$color'><b>AS</b></font>",$l);
$l = str_replace("ON","<font color='$color'><b>ON</b></font>",$l);
$l = str_replace("ORDER BY","<font color='$color'><b>ORDER BY</b></font>",$l);
$l = str_replace("LIMIT","<font color='$color'><b>LIMIT</b></font>",$l);
$l = str_replace("OFFSET","<font color='$color'><b>OFFSET</b></font>",$l);
$l = str_replace(" ","<dd>",$l);
if(substr($l,0,3) == "DQL") print "<hr valign='left' class='small'>";
print $l."<br>";
}
?>
DQL (Doctrine Query Language) is a object query language which allows
you to find objects. DQL understands things like object relationships, polymorphism and
inheritance (including column aggregation inheritance).
<br \><br \>
So instead of writing lots of SQL inner and outer joins, unions and subselects yourself,
you can write simple DQL queries where relationships are being referenced with dot-notation.
<br \><br \>
You can execute DQL queries with Doctrine_Session::query() method.
You can overload the query object by calling the dql query parts as methods.
ORDER BY - part works in much same way as SQL ORDER BY.
Doctrine provides two relation operators: '.' aka dot and ':' aka colon.
<br \><br \>
The dot-operator is used for SQL LEFT JOINs and the colon-operator is used
for SQL INNER JOINs. Basically you should use dot operator if you want for example
to select all users and their phonenumbers AND it doesn't matter if the users actually have any phonenumbers.
<br \><br \>
On the other hand if you want to select only the users which actually have phonenumbers you should use the colon-operator.
You can retrieve existing objects (database rows) with Doctrine_Table or Doctrine_Session.
Doctrine_Table provides simple methods like findBySql, findAll and find for finding objects whereas
Doctrine_Session provides complete OQL API for retrieving objects (see chapter 9).
There are three possible ways to access the properties of a record (fields of database row).
You can use overloading, ArrayAccess interface or simply Doctrine_Record::get() method.
<b>Doctrine_Record objects have always all properties in lowercase</b>.
There are couple of ways for creating new records. Propably the easiest is using
native php new -operator. The other ways are calling Doctrine_Table::create() or Doctrine_Session::create().
The last two exists only for backward compatibility. The recommended way of creating new objects is the new operator.
Deleting records in Doctrine is handled by Doctrine_Record::delete(), Doctrine_Collection::delete() and
Doctrine_Session::delete() methods.
Sometimes you may want to get a copy of your object (a new object with all properties copied).
Doctrine provides a simple method for this: Doctrine_Record::copy().
Every Doctrine_Record has a state. First of all record can be transient or persistent.
Every record that is retrieved from database is persistent and every newly created record is transient.
If a Doctrine_Record is retrieved from database but the only loaded property is its primary key, then this record
has a state called proxy.
<br /><br />
Every transient and persistent Doctrine_Record is either clean or dirty. Doctrine_Record is clean when none of its properties are changed and
dirty when atleast one of its properties has changed.
Doctrine provides many ways for record retrieval. The fastest ways for retrieving existing records
are the finder methods provided by Doctrine_Table. If you need to use more complex queries take a look at
DQL API and Doctrine_Session::query method.
Sometimes you may want to serialize your record objects (possibly for caching purposes). Records can be serialized,
but remember: Doctrine cleans all relations, before doing this. So remember to persist your objects into database before serializing them.
Updating objects is very easy, you just call the Doctrine_Record::save() method. The other way
(perhaps even easier) is to call Doctrine_Session::flush() which saves all objects. It should be noted though
that flushing is a much heavier operation than just calling save method.
Creating new record (database row) is very easy. You can either use the Doctrine_Session::create() or Doctrine_Table::create()
method to do this or just simple use the new operator.
In order to get table object for specified record just call Doctrine_Record::getTable() or Doctrine_Session::getTable().
Session state gives you information about how active session currently is. You can get the current state
by calling Doctrine_Session::getState().
You can add custom finder methods to your custom table object. These finder methods may use fast
Doctrine_Table finder methods or DQL API (Doctrine_Session::query()).
Adding custom table classes is very easy. Only thing you need to do is name the classes as [componentName]Table and make them
inherit Doctrine_Table.
Doctrine_Table provides basic finder methods. These finder methods are very fast and should be used if you only need to fetch
data from one database table. If you need queries that use several components (database tables) use Doctrine_Session::query().
<pre>
InvalidKeyException
Doctrine_Exception
DQLException
Doctrine_PrimaryKey_Exception thrown when Doctrine_Record is loaded and there is no primary key field
Doctrine_Refresh_Exception thrown when Doctrine_Record is refreshed and the refreshed primary key doens't match the old one
Doctrine_Find_Exception thrown when user tries to find a Doctrine_Record for given primary key and that object is not found
Doctrine_Naming_Exception thrown when user defined Doctrine_Table is badly named
Doctrine_Session_Exception thrown when user tries to get the current
session and there are no open sessions
Doctrine_Table_Exception thrown when user tries to initialize a new instance of Doctrine_Table,
while there already exists an instance of that factory
Doctrine_Mapping_Exception thrown when user tries to get a foreign key object but the mapping is not done right
</pre>
There are couple of availible Cache attributes on Doctrine:
<ul>
<li \>Doctrine::ATTR_CACHE_SIZE
<ul>
<li \> Defines which cache container Doctrine uses
<li \> Possible values: Doctrine::CACHE_* (for example Doctrine::CACHE_FILE)
</ul>
<li \>Doctrine::ATTR_CACHE_DIR
<ul>
<li \> cache directory where .cache files are saved
<li \> the default cache dir is %ROOT%/cachedir, where
%ROOT% is automatically converted to doctrine root dir
</ul>
<li \>Doctrine::ATTR_CACHE_SLAM
<ul>
<li \> On very busy servers whenever you start the server or modify files you can create a race of many processes all trying to cache the same file at the same time. This option sets the percentage of processes that will skip trying to cache an uncached file. Or think of it as the probability of a single process to skip caching. For example, setting apc.slam_defense to 75 would mean that there is a 75% chance that the process will not cache an uncached file. So, the higher the setting the greater the defense against cache slams. Setting this to 0 disables this feature
</ul>
<li \>Doctrine::ATTR_CACHE_SIZE
<ul>
<li \> Cache size attribute
</ul>
<li \>Doctrine::ATTR_CACHE_TTL
<ul>
<li \> How often the cache is cleaned
</ul>
</ul>
Doctrine has very comprehensive and fast caching solution.
Its cache is <b>always up-to-date</b>.
In order to achieve this doctrine does the following things:
<br /><br />
<table border=1 class='dashed' cellpadding=0 cellspacing=0>
<tr><td>
1. Every Doctrine_Table has its own cache directory. The default is cache/componentname/. All the cache files are saved into that directory.
The format of each cache file is [primarykey].cache.
<br /><br />
2. When retrieving records from the database doctrine always tries to hit the cache first.
<br /><br />
3. If a record (Doctrine_Record) is retrieved from database or inserted into database it will be saved into cache.
<br /><br />
4. When a Data Access Object is deleted or updated it will be deleted from the cache
</td></tr>
</table>
<br /><br />
Now one might wonder that this kind of solution won't work since eventually the cache will be a copy of database!
So doctrine does the following things to ensure the cache won't get too big:
<br /><br />
<table border=1 class='dashed' cellpadding=0 cellspacing=0>
<tr><td>
1. Every time a cache file is accessed the id of that record will be added into the $fetched property of Doctrine_Cache
<br /><br />
2. At the end of each script the Doctrine_Cache destructor will write all these primary keys at the end of a stats.cache file
<br /><br />
3. Doctrine does propabalistic cache cleaning. The default interval is 200 page loads (= 200 constructed Doctrine_Managers). Basically this means
that the average number of page loads between cache cleans is 200.
<br /><br />
4. On every cache clean stats.cache files are being read and the least accessed cache files
(cache files that have the smallest id occurance in the stats file) are then deleted.
For example if the cache size is set to 200 and the number of files in cache is 300, then 100 least accessed files are being deleted.
Doctrine also clears every stats.cache file.
</td></tr>
</table>
<br /><br />
So for every 199 fast page loads there is one page load which suffers a little overhead from the cache cleaning operation.
Doctrine has a three-level configuration structure. You can set configuration attributes in global, session and table level.
If the same attribute is set on both lower level and upper level, the uppermost attribute will always be used. So for example
if user first sets default fetchmode in global level to Doctrine::FETCH_BATCH and then sets 'example' table fetchmode to Doctrine::FETCH_LAZY,
the lazy fetching strategy will be used whenever the records of 'example' table are being fetched.
<br \><br \>
<li> Global level
<ul>
The attributes set in global level will affect every session and every table in each session.
</ul>
<li> Session level
<ul>
The attributes set in session level will take effect on each table in that session.
</ul>
<li> Table level
<ul>
The attributes set in table level will take effect only on that table.
</ul>
<li> Doctrine::ATTR_LISTENER
<li> Doctrine::ATTR_FETCHMODE = 2;
<li> Doctrine::ATTR_CACHE_DIR = 3;
<li> Doctrine::ATTR_CACHE_TTL = 4;
<li> Doctrine::ATTR_CACHE_SIZE = 5;
<li> Doctrine::ATTR_CACHE_SLAM = 6;
<li> Doctrine::ATTR_CACHE = 7;
<li> Doctrine::ATTR_BATCH_SIZE = 8;
<li> Doctrine::ATTR_PK_COLUMNS = 9;
/**
* primary key type attribute
*/
<li> Doctrine::ATTR_PK_TYPE = 10;
/**
* locking attribute
*/
<li> Doctrine::ATTR_LOCKMODE = 11;
/**
* validatate attribute
*/
<li> Doctrine::ATTR_VLD = 12;
/**
* name prefix attribute
*/
<li> Doctrine::ATTR_NAME_PREFIX = 13;
/**
* create tables attribute
*/
<li> Doctrine::ATTR_CREATE_TABLES = 14;
/**
* collection key attribute
*/
<li> Doctrine::ATTR_COLL_KEY = 15;
/**
* collection limit attribute
*/
<li> Doctrine::ATTR_COLL_LIMIT = 16;
The installation of doctrine is very easy. Just extract doctrine.*.zip into your www folder.
<br \><br \>
In the beginning of your every script you can put:
Doctrine requires PHP >= 5.1. it doesn't require any external libraries.
For database abstraction Doctrine uses PDO which is bundled with php by default. Doctrine also requires a little
adodb-hack for table creation, which comes with doctrine as of beta4.
Following data types and constraints are availible in doctrine
<ul>
<li /><b> unique</b>
<ul> Acts as database level unique constraint. Also validates that the specified column is unique.
</ul>
<li /><b> nospace</b>
<ul> Nospace validator. This validator validates that specified column doesn't contain any space/newline characters. <br />
</ul>
<li /><b> notblank</b>
<ul> Notblank validator. This validator validates that specified column doesn't contain only space/newline characters. Useful in for example comment posting applications
where users are not allowed to post empty comments. <br />
</ul>
<li /><b> notnull</b>
<dd /> Acts as database level notnull constraint as well as notnull validator for the specified column.<br />
<li /><b> email</b>
<dd /> Email validator. Validates that specified column is a valid email address.
<li /><b> date</b>
<dd /> Date validator.
<li /><b> range:[args]</b>
<dd /> Range validator, eg range:1-32
<li /><b> enum:[args]</b>
<dd /> Enum validator, eg enum:city1-city2-city3
<li /><b> country</b>
<ul> Country code validator validates that specified column has a valid country code.
</ul>
<li /><b> regexp:[args]</b>
<ul> Regular expression validator validates that specified column matches a regular expression, eg regexp:[John]
</ul>
<li /><b> ip</b>
<ul> Ip validator validates that specified column is a valid internet protocol address.
</ul>
<li /><b> usstate</b>
<ul> Usstate validator validates that specified column is a valid usstate.
</ul>
</ul>
Following data types are availible in doctrine:
<ul>
<li /><b> string / s</b>
<dd /> The same as type 'string' in php
<li /><b> float / double / f</b>
<dd /> The same as type 'float' in php<br />
<li /><b> integer / int / i</b>
<dd /> The same as type 'integer' in php<br />
<li /><b> boolean / bool</b>
<dd /> The same as type 'boolean' in php<br />
<li /><b> array / a</b>
<ul> The same as type 'array' in php. Automatically serialized when saved into database and unserialized when retrieved from database.</ul>
<li /><b> object / o</b>
<ul> The same as type 'object' in php. Automatically serialized when saved into database and unserialized when retrieved from database.</ul>
<li /><b> enum / e</b>
<ul> Unified 'enum' type. Automatically converts the string values into index numbers and vice versa. The possible values for the column
can be specified with Doctrine_Record::setEnumValues(columnName, array values).</ul>
<li /><b> timestamp / t</b>
<dd /> Database 'timestamp' type
<li /><b> clob</b>
<dd /> Database 'clob' type
<li /><b> blob</b>
<dd /> Database 'blob' type
<li /><b> date / d</b>
<dd /> Database 'date' type
</ul>
It should be noted that the length of the column affects in database level type
as well as application level validated length (the length that is validated with Doctrine validators).<br \>
<br \>Example 1. Column named 'content' with type 'string' and length 3000 results in database type 'TEXT' of which has database level length of 4000.
However when the record is validated it is only allowed to have 'content' -column with maximum length of 3000.<br \>
<br \>Example 2. Column with type 'integer' and length 1 results in 'TINYINT' on many databases.
<br \><br \>
In general Doctrine is smart enough to know which integer/string type to use depending on the specified length.
<br \>
Setting up a table definition in doctrine is done by using hasColumn method calls inside setTableDefinition method.
Doctrine_Record::hasColumn() takes 4 arguments:<br \>
1. [<b class="title">column name</b>] <br />
2. [<b class="title">column type</b>] <br />
3. [<b class="title">column length</b>] <br />
4. [<b class="title">column constraints and validators</b>]
Doctrine_Record is the basic component of every doctrine-based project.
There should be atleast one Doctrine_Record for each of your database tables.
Doctrine_Record follows <a href="http://www.martinfowler.com/eaaCatalog/activeRecord.html">Active Record pattern</a>.
<br \><br \>
Doctrine auto-creates database tables and always adds a primary key column named 'id' to tables. Only thing you need to for creating database tables
is defining a class which extends Doctrine_Record and setting a setTableDefinition method with hasColumn() method calls.
<br \><br \>
Consider we want to create a database table called 'user' with columns id(primary key), name, username, password and created. You only need couple of lines of code
to create a simple CRUD (Create, Retrieve, Update, Delete) application for this database table.
Doctrine supports aggregates and composites. When binding composites you can use methods Doctrine_Table::ownsOne() and Doctrine_Table::ownsMany(). When binding
aggregates you can use methods Doctrine_Table::hasOne() and Doctrine_Table::hasMany().
<br \><br \>
In Doctrine if you bind an Email to a User using ownsOne or ownsMany methods, everytime User record calls save or delete the associated
Email record is also saved/deleted.
<br \><br \>
Then again if you bind an Email to a User using hasOne or hasMany methods, everytime User record calls save or delete the associated
Email record is NOT saved/deleted.
When accessing related records and if those records do not exists Doctrine automatically creates new records.
You can delete related records individually be calling delete() on each record. If you want to delete a whole record graph just call
delete on the owner record.
You can retrieve related records by the very same Doctrine_Record methods you've already propably used for accessing record properties.
When accessing related record you just simply use the class names.
You can update the related records by calling save for each related object / collection individually or by calling
save on the object that owns the other objects. You can also call Doctrine_Session::flush which saves all pending objects.
Binding One-To-One foreign key associations is done with Doctrine_Record::ownsOne() and Doctrine_Record::hasOne() methods.
In the following example user owns one email and has one address. So the relationship between user and email is one-to-one composite.
The relationship between user and address is one-to-one aggregate.
<br \><br \>
The Email component here is mapped to User component's column email_id hence their relation is called LOCALKEY relation.
On the other hand the Address component is mapped to User by it's user_id column hence the relation between User and Address is called
FOREIGNKEY relation.
In the following example we have one database table called 'entity'.
Users and groups are both entities and they share the same database table.
<br \><br \>
The entity table has a column called 'type' which tells whether an entity is a group or a user.
Then we decide that users are type 1 and groups type 2.
The only thing we have to do is to create 3 records (the same as before) and add
call the Doctrine_Table::setInheritanceMap() method inside the setUp() method.
When it comes to handling inheritance Doctrine is very smart.
In the following example we have one database table called 'entity'.
Users and groups are both entities and they share the same database table.
The only thing we have to make is 3 records (Entity, Group and User).
<br /><br />
Doctrine is smart enough to know that the inheritance type here is one-table-many-classes.
In the following example we have three database tables called 'entity', 'user' and 'group'.
Users and groups are both entities.
The only thing we have to do is write 3 classes (Entity, Group and User) and make iterative
setTableDefinition method calls.
<br /><br />
If you are coming from relational database background it may be familiar to you
how many-to-many associations are handled: an additional association table is needed.
<br \><br \>
In the following example we have Groups and Users of which relation is defined as
many-to-many. In this case we also need to define an additional class called Groupuser.
Doctrine supports relation aliases through 'as' keyword.
In the following example we make a user management system where
<br /><br />
1. Each user and group are entities
<br /><br />
2. User is an entity of type 0
<br /><br />
3. Group is an entity of type 1
<br /><br />
4. Each entity (user/group) has 0-1 email
<br /><br />
5. Each entity has 0-* phonenumbers
<br /><br />
6. If an entity is saved all its emails and phonenumbers are also saved
<br /><br />
7. If an entity is deleted all its emails and phonenumbers are also deleted
<br /><br />
8. When an entity is created and saved a current timestamp will be assigned to 'created' field
<br /><br />
9. When an entity is updated a current timestamp will be assigned to 'updated' field
<br /><br />
10. Entities will always be fetched in batches
Doctrine_Association represents a many-to-many association between database tables.
Doctrine_Collection is a collection of Data Access Objects. Doctrine_Collection represents a record set.
Doctrine_Collection_Batch is a Doctrine_Collection with batch fetching strategy.
Doctrine_Collection_Immediate is a Doctrine_Collection with immediate fetching strategy.
Doctrine_Collection_Lazy is a Doctrine_Collection with lazy fetching strategy.
Doctrine_ForeignKey represents a one-to-many association or one-to-one association between two database tables.
Doctrine_Manager is the base component of Doctrine ORM framework. Doctrine_Manager is a colletion of Doctrine_Sessions. All the new sessions are being
opened by Doctrine_Manager::openSession().
Doctrine_Record is a wrapper for database row.
Doctrine_Session is a wrapper for database connection. It creates Doctrine_Tables and keeps track of all the created tables.
Doctrine_Session provides things that are missing from PDO like sequence support and limit/offset emulation.
Doctrine_Table creates records and holds info of all foreign keys and associations. Doctrine_Table
represents a database table.
GoF [Gang Of Four] design patterns used:
<br \>
<li \><a href="http://www.dofactory.com/Patterns/PatternSingleton.aspx">Singleton</a><br \>
<dd>For forcing only one instance of Doctrine_Manager
<li \><a href="http://www.dofactory.com/Patterns/PatternComposite.aspx">Composite</a><br \>
<dd>For leveled configuration
<li \><a href="http://www.dofactory.com/Patterns/PatternFactory.aspx">Factory</a><br \>
<dd>For session driver loading and many other things
<li \><a href="http://www.dofactory.com/Patterns/PatternObserver.aspx">Observer</a><br \>
<dd>For event listening
<li \><a href="http://www.dofactory.com/Patterns/PatternFlyweight.aspx">Flyweight</a><br \>
<dd>For efficient usage of validators
<li \><a href="http://www.dofactory.com/Patterns/PatternFlyweight.aspx">Iterator</a><br \>
<dd>For iterating through components [Tables, Sessions, Records etc.]
<li \><a href="http://www.dofactory.com/Patterns/PatternState.aspx">State</a><br \>
<dd>For state-wise sessions
<li \><a href="http://www.dofactory.com/Patterns/PatternStrategy.aspx">Strategy</a><br \>
<dd>For algorithm strategies
<br \><br \>
Enterprise application design patterns used:
<br \>
<li \><a href="http://www.martinfowler.com/eaaCatalog/activeRecord.html">Active Record</a><br \>
<dd>Doctrine is an implementation of this pattern
<li \><a href="http://www.martinfowler.com/eaaCatalog/unitOfWork.html">UnitOfWork</a><br \>
<dd>For maintaining a list of objects affected in a transaction
<li \><a href="http://www.martinfowler.com/eaaCatalog/identityField.html">Identity Field</a><br \>
<dd>For maintaining the identity between record and database row
<li \><a href="http://www.martinfowler.com/eaaCatalog/metadataMapping.html">Metadata Mapping</a><br \>
<dd>For Doctrine DataDict
<li \><a href="http://www.martinfowler.com/eaaCatalog/dependentMapping.html">Dependent Mapping</a><br \>
<dd>For mapping in general, since all records extend Doctrine_Record which performs all mappings
<li \><a href="http://www.martinfowler.com/eaaCatalog/foreignKeyMapping.html">Foreign Key Mapping</a><br \>
<dd>For one-to-one, one-to-many and many-to-one relationships
<li \><a href="http://www.martinfowler.com/eaaCatalog/associationTableMapping.html">Association Table Mapping</a><br \>
<dd>For association table mapping (most commonly many-to-many relationships)
<li \><a href="http://www.martinfowler.com/eaaCatalog/lazyLoad.html">Lazy Load</a><br \>
<dd>For lazy loading of objects and object properties
<li \><a href="http://www.martinfowler.com/eaaCatalog/queryObject.html">Query Object</a><br \>
<dd>DQL API is actually an extension to the basic idea of Query Object pattern
<li \><b class="title">Lazy initialization</b><br \>
For collection elements
<br \><br \>
<li \><b class="title">Subselect fetching</b><br \>
Doctrine knows how to fetch collections efficiently using a subselect.
<br \><br \>
<li \><b class="title">Executing SQL statements later, when needed</b><br \>
The session never issues an INSERT or UPDATE until it is actually needed. So if an exception occurs and you need to abort the transaction, some statements will never actually be issued. Furthermore, this keeps lock times in the database as short as possible (from the late UPDATE to the transaction end).
<br \><br \>
<li \><b class="title">Join fetching</b><br \>
Doctrine knows how to fetch complex object graphs using joins and subselects
<br \><br \>
<li \><b class="title">Multiple collection fetching strategies</b><br \>
Doctrine has multiple collection fetching strategies for performance tuning.
<br \><br \>
<li \><b class="title">Dynamic mixing of fetching strategies</b><br \>
Fetching strategies can be mixed and for example users can be fetched in a batch collection while
users' phonenumbers are loaded in offset collection using only one query.
<br \><br \>
<li \><b class="title">Driver specific optimizations</b><br \>
Doctrine knows things like bulk-insert on mysql
<br \><br \>
<li \><b class="title">Transactional single-shot delete</b><br \>
Doctrine knows how to gather all the primary keys of the pending objects in delete list and performs only one sql delete statement per table.
<br \><br \>
<li \><b class="title">Updating only the modified columns.</b><br \>
Doctrine always knows which columns have been changed.
<br \><br \>
<li \><b class="title">Never inserting/updating unmodified objects.</b><br \>
Doctrine knows if the the state of the record has changed.
<br \><br \>
<li \><b class="title">PDO for database abstraction</b><br \>
PDO is by far the fastest availible database abstraction layer for php.
<br \><br \>
<?php
require_once("highlight.php");
error_reporting(E_ALL);
include("top.php");
$h = new PHP_Highlight();
function render($title,$t,$e) {
global $h;
print $e." <a name=\"$e\"><u>".$t."</u></a><br><br>\n";
$c = "";
if(file_exists("docs/$e.php")) {
rename("docs/$e.php","docs/$title - $t.php");
}
if(file_exists("docs/$t.php")) {
rename("docs/$t.php","docs/$title - $t.php");
}
if(file_exists("docs/$title - $t.php")) {
$c = file_get_contents("docs/$title - $t.php");
if(substr($c,0,5) == "<?php") {
include("docs/$title - $t.php");
} else
print $c."<br><br>";
}
$c = "";
if(file_exists("codes/$e.php")) {
rename("codes/$e.php","codes/$title - $t.php");
}
if(file_exists("codes/$t.php")) {
rename("codes/$t.php","codes/$title - $t.php");
}
if(file_exists("codes/$title - $t.php")) {
print "<table border=1 class='dashed' cellpadding=0 cellspacing=0>";
print "<tr><td>";
$c = file_get_contents("codes/$title - $t.php");
$h->loadString($c);
print $h->toHtml();
print "</td></tr>";
print "</table>";
}
print "<br>";
}
function render_block($name) {
$h = new PHP_Highlight;
if(file_exists("docs/$name.php")) {
$c = file_get_contents("docs/$name.php");
if(substr($c,0,5) == "<?php") {
include("docs/$name.php");
} else {
print $c."<br><br>";
}
}
if(file_exists("codes/$name.php")) {
print "<table width=500 border=1 class='dashed' cellpadding=0 cellspacing=0>";
print "<tr><td>";
$c = file_get_contents("codes/$name.php");
$h->loadString($c);
print $h->toHtml();
print "</td></tr>";
print "</table>";
}
}
function array2path($array, $path = '') {
$arrayValues = array();
$index = 1;
foreach ($array as $k => $value) {
$p = ($path !== '')?$path.".".$index:$index;
if (is_scalar($value) || is_resource($value)) {
$arrayValues[$p] = $value;
} elseif (is_array($value)) {
$arrayValues[$p] = $k;
$arrayValues = $arrayValues + array2path($value, $p);
}
$index++;
}
return $arrayValues;
}
$menu = array("Getting started" =>
array(
"Requirements",
"Installation",
"Starting new project",
"Setting table definition" => array(
"Introduction",
"Data types and lengths",
"Constraints and validators",
),
),
"Basic Components" =>
array(
"Manager"
=> array("Introduction",
"Opening a new session",
"Managing sessions"),
"Record"
=> array("Introduction",
"Creating new records",
"Retrieving existing records",
"Accessing properties",
"Updating records",
"Deleting records",
"Getting record state",
"Getting object copy",
"Serializing",
"Getting identifiers",
"Callbacks"),
"Session"
=> array("Introduction",
"Availible drivers",
"Getting a table object",
"Flushing the session",
"Limiting queries",
"Querying the database",
"Getting session state"),
"Collection"
=> array("Introduction",
"Accessing elements",
"Adding new elements",
"Getting collection count",
"Saving the collection",
"Deleting collection",
"Fetching strategies",
"Collection expanding",
),
"Table" => array("Introduction",
"Getting table information",
"Finder methods",
"Custom table classes",
"Custom finders",
"Getting relation objects"),
"Query" => array("Introduction",
"FROM - selecting tables",
"LIMIT and OFFSET - limiting the query results",
"WHERE - setting query conditions",
"ORDER BY - sorting query results",
"Fetching strategies",
"Lazy property fetching",
"Method overloading",
"Relation operators",
"Bound parameters",
"Aggregate functions",
"DQL - SQL conversion"),
"RawSql" => array(
"Introduction",
"Using SQL",
"Adding components",
"Method overloading"),
"Statement - <font color='red'>UNDER CONSTRUCTION</font>" => array("Introduction",
"Setting parameters",
"Getting parameters",
"Getting row count",
"Executing the statement"),
"Exceptions" => array(
"Overview",
"List of exceptions"
)
),
"Mapping object relations" =>
array(
"Introduction",
"Composites and aggregates",
"Relation aliases",
"Foreign key associations" => array(
"One-to-One",
"One-to-Many, Many-to-One",
"Tree structure"),
"Join table associations" => array(
"One-to-One",
"One-to-Many, Many-to-One",
"Many-to-Many",
"Self-referencing"),
"Dealing with relations" => array(
"Creating related records",
"Retrieving related records",
"Updating related records",
"Deleting related records"),
"Inheritance" =>
array("One table many classes",
"One table one class",
"Column aggregation"
),
),
"Configuration" =>
array(
"Introduction",
"Levels of configuration",
"Setting attributes" => array(
"Table creation",
"Fetching strategy",
"Batch size",
"Session lockmode",
"Event listener",
"Validation",
"Offset collection limit"
)
),
"Advanced components" => array(
"Eventlisteners" =>
array(
"Introduction",
"Creating new listener",
"List of events",
"Listening events",
"Chaining",
),
"Validators" => array(
"Intruduction",
"Validating transactions",
"Analyzing the ErrorStack",
"List of predefined validators"
),
"View" => array(
"Intoduction",
"Managing views",
"Using views"
),
/**
"Hook" => array(
"Introduction",
"Parameter hooking",
"Paging",
"Setting conditions",
"Sorting"
),
*/
"Cache" => array(
"Introduction",
"Query cache"),
"Locking Manager" => array(
"Introduction",
"Pessimistic locking",
"Examples"),
/**
"Debugger" => array(
"Introduction",
"Debugging actions"),
"Library" => array(
"Introduction",
"Using library functions"),
"Iterator" => array(
"Introduction",
"BatchIterator",
"ExpandableIterator",
"OffsetIterator")
*/
),
"Transactions" => array(
"Introduction",
"Unit of work",
"Locking strategies" =>
array("Pessimistic locking",
"Optimistic locking"),
"Nesting"
),
/**
"Developer components" => array(
"DataDict" => array(
"Introduction",
"Usage"
),
"IndexGenerator" =>
array(
"Introduction",
"Usage"),
"Relation" => array(
"Introduction",
"Types of relations",
),
"Null" => array(
"Introduction",
"Extremely fast null value checking"
),
"Access" => array(
"Introduction",
"Usage"
),
"Configurable" => array(
"Introduction",
"Usage"
),
),
*/
"Technology" => array(
"Architecture",
"Design patterns used",
"Speed",
"Internal optimizations" =>
array("DELETE",
"INSERT",
"UPDATE"),
),
"Real world examples" => array("User management system","Forum application","Album lister")
);
?>
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td width=50>
<td>
<td width=800 align="left" valign="top">
<table width="100%" cellspacing=1 cellpadding=1>
<tr>
<td colspan=2 bgcolor="white">
<img src="images/logo.jpg" align="left"><b class="title">Doctrine - PHP Data Persistence and ORM Tool</b>
<hr>
</td>
</tr>
<tr>
<td bgcolor="white" valign="top" width="570">
<?php
if( ! isset($_REQUEST["index"])) {
$i = 1;
$missing = array();
$missing[0] = 0;
$missing[1] = 0;
foreach($menu as $title => $titles) {
print $i.". <a href=\"".$_SERVER['PHP_SELF']."?index=$i#$i\">".$title."</a><br>\n";
$i2 = 1;
foreach($titles as $k => $t) {
$e = "$i.".$i2."";
if(is_array($t)) {
print "<dd>".$e." <a href=\"".$_SERVER['PHP_SELF']."?index=$i.$i2#$e\">".$k."</a><br>\n";
$i3 = 1;
foreach($t as $k2 => $v2) {
$str = "";
if( ! file_exists("docs/$title - $k - $v2.php")) {
$missing[0]++;
$str .= " [ <font color='red'>doc</font> ] ";
}
if( ! file_exists("codes/$title - $k - $v2.php")) {
$missing[1]++;
$str .= " [ <font color='red'>code</font> ] ";
}
$e = implode(".",array($i,$i2,$i3));
print "<dd><dd>".$e." <a href=\"".$_SERVER['PHP_SELF']."?index=$i.$i2#$e\">".$v2."</a><br>\n";
$i3++;
}
} else {
$str = "";
if( ! file_exists("docs/$title - $t.php")) {
$missing[0]++;
$str .= " [ <font color='red'>doc</font> ] ";
}
if( ! file_exists("codes/$title - $t.php")) {
$missing[1]++;
$str .= " [ <font color='red'>code</font> ] ";
}
print "<dd>".$e." <a href=\"".$_SERVER['PHP_SELF']."?index=$i#$e\">".$t."</a><br>\n";
}
$i2++;
}
$i++;
}
} else {
$i = 1;
$ex = explode(".",$_REQUEST["index"]);
$paths = array2path($menu);
if( ! isset($paths[$ex[0]]))
exit;
$break = false;
$tmp = $paths;
foreach($tmp as $path => $title) {
$e = explode(".", $path);
if(count($e) > 2) {
unset($tmp[$path]);
}
}
$prev = 1;
foreach($tmp as $path => $title) {
if($break)
break;
if($path == $_REQUEST["index"]) {
$break = true;
} else {
$prev = $path;
}
}
$index = $_REQUEST['index'];
print "<table width='100%'>";
print "<tr><td colspan=3 align='center'><b></td></tr>";
print "<tr><td colspan=3 align='center'><b class='title'>".$paths[$ex[0]]."</b></td></tr>";
print "<tr><td align='left'><b><a href=documentation.php?index=$prev>Prev</a></b></td>";
print "<td align='right'><b><a href=documentation.php?index=$path>Next</a></b></td></tr>";
print "<tr><td>&nbsp;</td></tr>";
print "</table>";
$tmp = $ex;
if(count($tmp) > 2) {
//array_pop($tmp);
}
foreach($tmp as $k => $v) {
$numbers[] = $v;
$curr = implode(".",$numbers);
$stack[] = $paths[$curr];
}
if(isset($ex[1])) {
$name = implode(" - ", $stack);
print "<b class='title'>".$paths[$curr]."</b><hr>";
$n = $numbers;
$o = $paths[$n[0]];
$s = implode(".", array($n[0], $n[1]));
$o2 = $paths[$s];
$value = $menu[$o];
if( ! is_array($value))
exit;
if(in_array($o2, $value)) {
render_block($name);
} else {
$value = $menu[$o][$o2];
if(is_array($value)) {
foreach($value as $k => $title) {
print "<br \><b class='title'>".$title."</b><hr style='height: 1px' \>";
$s = $name." - ".$title;
render_block($s);
}
}
}
} else {
//if( ! isset($menu[$ex[0]]))
// exit;
$tmp = $paths[$ex[0]];
$i = 1;
foreach($menu[$tmp] as $title => $value) {
$n = $ex[0].".".$i;
if(is_scalar($value)) {
print "<dd>".$n.". <a href=\"documentation.php?index=".$n."\">".$value."</a><br \>\n";
} else {
print "<dd>".$n.". <a href=\"documentation.php?index=".$n."\">".$title."</a><br \>\n";
}
$i++;
}
}
}
?>
<td bgcolor="white" align="left" valign="top">
<?php
$i = 1;
print "<dd>-- <b><a href=documentation.php>index</a></b><br>\n";
foreach($menu as $title => $titles) {
print "<dd>".$i.". <a href=\"".$_SERVER['PHP_SELF']."?index=$i\">".$title."</a><br>\n";
$i++;
}
?>
</td>
<td>
<tr>
<td bgcolor="white" valign="top" colspan="2">
<?php
/**
foreach($menu as $title => $titles) {
if($i == $ex[0]) {
print $i.". <b><a class=\"big\" name=\"$i\">".$title."</a></b><hr><br>\n";
$i2 = 1;
foreach($titles as $k => $t) {
$e = "$i.".$i2;
if(is_array($t)) {
$tmp = "$title - $k";
if( ! isset($ex[1]) || $i2 != $ex[1]) {
$i2++;
continue;
}
print $e." <b><a class=\"big\" name=\"$e\">".$k."</a></b><hr><br><br>\n";
foreach($t as $k2 => $t2) {
if( ! isset($ex[1]) || $i2 != $ex[1])
break;
$e = "$i.".$i2.".".($k2+1);
render($tmp,$t2,$e);
}
} else {
if( ! isset($ex[1])) {
render($title,$t,$e);
}
}
$i2++;
}
}
$i++;
}
*/
?>
</td>
</tr>
</td>
<td>
</td>
</tr>
</table>
<?php
/**
* Define constants for PHP 4 / PHP 5 compatability
*
* The source of this file can be found at:
* http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Constant/T.php?r=HEAD
*
* It is part of the PEAR PHP_Compat package:
* http://pear.php.net/package/PHP_Compat
*/
//require_once 'PHP/Compat/Constant/T.php';
/**
* Improved PHP syntax highlighting.
*
* Generates valid XHTML output with function referencing
* and line numbering.
*
* Four output methods provide maximum flexibility.
* * toHTML => Formatted HTML
* * toHtmlComment => Formatted HTML, specifically for comments on messageboards
* * toList => Ordered lists
* * toArray => Associative array
*
* Highlighting can be inline (with styles), or the same as
* highlight_file() where colors are taken from php.ini.
*
* @author Aidan Lister <aidan@php.net>
* @author Based on an idea by Matthew Harris <shugotenshi@gmail.com>
* @version 1.3.0
* @link http://aidanlister.com/repos/v/PHP_Highlight.php
*/
if (!defined('T_ML_COMMENT')) {
define('T_ML_COMMENT', T_COMMENT);
} else {
define('T_DOC_COMMENT', T_ML_COMMENT);
}
class PHP_Highlight
{
/**
* Hold highlight colors
*
* Contains an associative array of token types and colours.
* By default, it contains the colours as specified by php.ini
*
* For example, to change the colour of strings, use something
* simular to $h->highlight['string'] = 'blue';
*
* @var array
* @access public
*/
var $highlight;
/**
* Things to be replaced for formatting or otherwise reasons
*
* The first element contains the match array, the second the replace
* array.
*
* @var array
* @access public
*/
var $replace = array(
0 => array("\t", ' '),
1 => array('&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;'));
/**
* Format of the link to the PHP manual page
*
* @var string
* @access public
*/
var $manual = '<a href="http://www.php.net/function.%s">%s</a>';
/**
* Format of the span tag to be wrapped around each token
*
* @var string
* @access public
*/
var $span;
/**
* Hold the source
*
* @var string
* @access private
*/
var $_source = false;
/**
* Hold plaintext keys
*
* An array of lines which are plaintext
*
* @var array
* @access private
*/
var $_plaintextkeys = array();
/**
* Constructor
*
* Populates highlight array
*
* @param bool $inline If inline styles rather than colors are to be used
* @param bool $plaintext Do not format code outside PHP tags
*/
function PHP_Highlight($inline = false)
{
// Inline
if ($inline === false) {
// Default colours from php.ini
$this->highlight = array(
'string' => ini_get('highlight.string'),
'comment' => ini_get('highlight.comment'),
'keyword' => ini_get('highlight.keyword'),
'bg' => ini_get('highlight.bg'),
'default' => ini_get('highlight.default'),
'html' => ini_get('highlight.html')
);
$this->span = '<span style="color: %s;">%s</span>';
} else {
// Basic styles
$this->highlight = array(
'string' => 'string',
'comment' => 'comment',
'keyword' => 'keyword',
'bg' => 'bg',
'default' => 'default',
'html' => 'html'
);
$this->span = '<span class="%s">%s</span>';
}
}
/**
* Load a file
*
* @access public
* @param string $file The file to load
* @return bool Returns TRUE
*/
function loadFile($file)
{
$this->_source = file_get_contents($file);
return true;
}
/**
* Load a string
*
* @access public
* @param string $string The string to load
* @return bool Returns TRUE
*/
function loadString($string)
{
$this->_source = $string;
return true;
}
/**
* Parse the loaded string into an array
* Source is returned with the element key corresponding to the line number
*
* @access public
* @param bool $funcref Reference functions to the PHP manual
* @param bool $blocks Whether to ignore processing plaintext
* @return array An array of highlighted source code
*/
function toArray($funcref = true, $blocks = false)
{
// Ensure source has been loaded
if ($this->_source == false) {
return false;
}
// Init
$tokens = token_get_all($this->_source);
$manual = $this->manual;
$span = $this->span;
$i = 0;
$out = array();
$out[$i] = '';
// Loop through each token
foreach ($tokens as $j => $token) {
// Single char
if (is_string($token)) {
// Skip token2color check for speed
$out[$i] .= sprintf($span, $this->highlight['keyword'], htmlspecialchars($token));
// Heredocs behave strangely
list($tb) = isset($tokens[$j - 1]) ? $tokens[$j - 1] : false;
if ($tb === T_END_HEREDOC) {
$out[++$i] = '';
}
continue;
}
// Proper token
list ($token, $value) = $token;
// Make the value safe
$value = htmlspecialchars($value);
$value = str_replace($this->replace[0], $this->replace[1], $value);
// Process
if ($value === "\n") {
// End this line and start the next
$out[++$i] = '';
} else {
// Function linking
if ($funcref === true && $token === T_STRING) {
// Look ahead 1, look ahead 2, and look behind 3
if ((isset($tokens[$j + 1]) && $tokens[$j + 1] === '(' ||
isset($tokens[$j + 2]) && $tokens[$j + 2] === '(') &&
isset($tokens[$j - 3][0]) && $tokens[$j - 3][0] !== T_FUNCTION
&& function_exists($value)) {
// Insert the manual link
$value = sprintf($manual, $value, $value);
}
}
// Explode token block
$lines = explode("\n", $value);
foreach ($lines as $jj => $line) {
$line = trim($line);
if ($line !== '') {
// This next line is helpful for debugging
//$out[$i] .= token_name($token);
// Check for plaintext
if ($blocks === true && $token === T_INLINE_HTML) {
$this->_plaintextkeys[] = $i;
$out[$i] .= $line;
} else {
$out[$i] .= sprintf($span, $this->_token2color($token), $line);
}
}
// Start a new line
if (isset($lines[$jj + 1])) {
$out[++$i] = '';
}
}
}
}
return $out;
}
/**
* Convert the source to an ordered list.
* Each line is wrapped in <li> tags.
*
* @access public
* @param bool $return Return rather than print the results
* @param bool $funcref Reference functions to the PHP manual
* @param bool $blocks Whether to use code blocks around plaintext
* @return string A HTML ordered list
*/
function toList($return = false, $funcref = true, $blocks = true)
{
// Ensure source has been loaded
if ($this->_source == false) {
return false;
}
// Format list
$source = $this->toArray($funcref, $blocks);
$out = "<ol>\n";
foreach ($source as $i => $line) {
$out .= " <li>";
// Some extra juggling for lines which are not code
if (empty($line)) {
$out .= '&nbsp;';
} elseif ($blocks === true && in_array($i, $this->_plaintextkeys)) {
$out .= $line;
} else {
$out .= "<code>$line</code>";
}
$out .= "</li>\n";
}
$out .= "</ol>\n";
if ($return === true) {
return $out;
} else {
echo $out;
}
}
/**
* Convert the source to formatted HTML.
* Each line ends with <br />.
*
* @access public
* @param bool $return Return rather than print the results
* @param bool $linenum Display line numbers
* @param string $format Specify format of line numbers displayed
* @param bool $funcref Reference functions to the PHP manual
* @return string A HTML block of code
*/
function toHtml($return = false, $linenum = false, $format = null, $funcref = true)
{
// Ensure source has been loaded
if ($this->_source == false) {
return false;
}
// Line numbering
if ($linenum === true && $format === null) {
$format = '<span>%02d</span> ';
}
// Format code
$source = $this->toArray($funcref);
$out = "<code>\n";
foreach ($source as $i => $line) {
$out .= ' ';
if ($linenum === true) {
$out .= sprintf($format, $i);
}
$out .= empty($line) ? '&nbsp;' : $line;
$out .= "<br />\n";
}
$out .= "</code>\n";
if ($return === true) {
return $out;
} else {
echo $out;
}
}
/**
* Convert the source to formatted HTML blocks.
* Each line ends with <br />.
*
* This method ensures only PHP is between <code> blocks.
*
* @access public
* @param bool $return Return rather than print the results
* @param bool $linenum Display line numbers
* @param string $format Specify format of line numbers displayed
* @param bool $reset Reset the line numbering each block
* @param bool $funcref Reference functions to the PHP manual
* @return string A HTML block of code
*/
function toHtmlBlocks($return = false, $linenum = false, $format = null, $reset = true, $funcref = true)
{
// Ensure source has been loaded
if ($this->_source == false) {
return false;
}
// Default line numbering
if ($linenum === true && $format === null) {
$format = '<span>%03d</span> ';
}
// Init
$source = $this->toArray($funcref, true);
$out = '';
$wasplain = true;
$k = 0;
// Loop through each line and decide which block to use
foreach ($source as $i => $line) {
// Empty line
if (empty($line)) {
if ($wasplain === true) {
$out .= '&nbsp;';
} else {
if (in_array($i+1, $this->_plaintextkeys)) {
$out .= "</code>\n";
// Reset line numbers
if ($reset === true) {
$k = 0;
}
} else {
$out .= ' ';
// Add line number
if ($linenum === true) {
$out .= sprintf($format, ++$k);
}
}
}
// Plain text
} elseif (in_array($i, $this->_plaintextkeys)) {
if ($wasplain === false) {
$out .= "</code>\n";
// Reset line numbers
if ($reset === true) {
$k = 0;
}
}
$wasplain = true;
$out .= str_replace('&nbsp;', ' ', $line);
// Code
} else {
if ($wasplain === true) {
$out .= "<code>\n";
}
$wasplain = false;
$out .= ' ';
// Add line number
if ($linenum === true) {
$out .= sprintf($format, ++$k);
}
$out .= $line;
}
$out .= "<br />\n";
}
// Add final code tag
if ($wasplain === false) {
$out .= "</code>\n";
}
// Output method
if ($return === true) {
return $out;
} else {
echo $out;
}
}
/**
* Assign a color based on the name of a token
*
* @access private
* @param int $token The token
* @return string The color of the token
*/
function _token2color($token)
{
switch ($token):
case T_CONSTANT_ENCAPSED_STRING:
return $this->highlight['string'];
break;
case T_INLINE_HTML:
return $this->highlight['html'];
break;
case T_COMMENT:
case T_DOC_COMMENT:
case T_ML_COMMENT:
return $this->highlight['comment'];
break;
case T_ABSTRACT:
case T_ARRAY:
case T_ARRAY_CAST:
case T_AS:
case T_BOOLEAN_AND:
case T_BOOLEAN_OR:
case T_BOOL_CAST:
case T_BREAK:
case T_CASE:
case T_CATCH:
case T_CLASS:
case T_CLONE:
case T_CONCAT_EQUAL:
case T_CONTINUE:
case T_DEFAULT:
case T_DOUBLE_ARROW:
case T_DOUBLE_CAST:
case T_ECHO:
case T_ELSE:
case T_ELSEIF:
case T_EMPTY:
case T_ENDDECLARE:
case T_ENDFOR:
case T_ENDFOREACH:
case T_ENDIF:
case T_ENDSWITCH:
case T_ENDWHILE:
case T_END_HEREDOC:
case T_EXIT:
case T_EXTENDS:
case T_FINAL:
case T_FOREACH:
case T_FUNCTION:
case T_GLOBAL:
case T_IF:
case T_INC:
case T_INCLUDE:
case T_INCLUDE_ONCE:
case T_INSTANCEOF:
case T_INT_CAST:
case T_ISSET:
case T_IS_EQUAL:
case T_IS_IDENTICAL:
case T_IS_NOT_IDENTICAL:
case T_IS_SMALLER_OR_EQUAL:
case T_NEW:
case T_OBJECT_CAST:
case T_OBJECT_OPERATOR:
case T_PAAMAYIM_NEKUDOTAYIM:
case T_PRIVATE:
case T_PROTECTED:
case T_PUBLIC:
case T_REQUIRE:
case T_REQUIRE_ONCE:
case T_RETURN:
case T_SL:
case T_SL_EQUAL:
case T_SR:
case T_SR_EQUAL:
case T_START_HEREDOC:
case T_STATIC:
case T_STRING_CAST:
case T_THROW:
case T_TRY:
case T_UNSET_CAST:
case T_VAR:
case T_WHILE:
return $this->highlight['keyword'];
break;
case T_CLOSE_TAG:
case T_OPEN_TAG:
case T_OPEN_TAG_WITH_ECHO:
default:
return $this->highlight['default'];
endswitch;
}
}
?>
body {
margin: 10,0,0,0;
font-family: Verdana, Arial, Helvetica;
text-decoration: none;
}
hr {
color: A50A3D;
}
hr.small {
color: A50A3D;
width: 100%;
align: left;
height: 1 px;
}
td.small {
font-family: Verdana, Arial, Helvetica;
font-size: 12 px;
}
dd {
line-height: 1.5em;
}
ul {
line-height: 1.5em;
}
td {
font-size: 12 px;
font-family: Verdana, Arial, Helvetica;
}
joo {
color: A50A3D;
}
table.dashed {
border-style: solid;
border-color: 116699;
border-width: 1 px;
}
b.title {
color: #A50A3D;
}
u.title {
color: #367FAC;
}
a.big {
font-size: 13 px;
}
a {
color: #367FAC;
}
body {
margin: 10,0,0,0;
font-family: Verdana, Arial, Helvetica;
text-decoration: none;
background: #393939;
color: #dfdfdf;
}
hr {
color: a3cb14;
}
hr.small {
color: a3cb14;
width: 100%;
align: left;
height: 1 px;
}
td.small {
font-family: Verdana, Arial, Helvetica;
font-size: 12 px;
}
td {
font-size: 12 px;
font-family: Verdana, Arial, Helvetica;
background: #393939;
}
joo {
color: A50A3D;
}
table.dashed {
border-style: solid;
border-color: 116699;
border-width: 1 px;
}
b.title {
color: #0090d9;
}
u.title {
color: #367FAC;
}
a.big {
font-size: 13 px;
}
a {
color: #a3cb14;
}
<link rel="stylesheet" type="text/css" href="styles/basic.css">
<style type="text/css">
</style>
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td width=2000 background="images/docflashbg.jpg">
<img src="images/docflash.jpg">
</td>
</tr>
</table>
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td align="right">
<a href="index.php">main</a> | <a href="documentation.php">documentation</a> | <a href="https://sourceforge.net/project/showfiles.php?group_id=160015">download</a> | <a href="faq.php">FAQ</a> | <a href="about.php">about</a> | <a href="http://www.phpbbserver.com/phpdoctrine/">forums</a>
</td>
<td width=30>
</td>
</tr>
</table>
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