Sophie

Sophie

distrib > Mageia > 4 > x86_64 > by-pkgid > 7685ee38863f0bdb96d1410dc8b3f169 > files > 5

ezcomponents-PersistentObject-1.7.1-3.mga4.noarch.rpm

eZ Components - PersistentObject
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. contents:: Table of Contents

Introduction
============
The PersistentObject component provides object persistence for PHP 5 using a
database. PersistentObject uses the Database component to provide database
abstraction. It does not rely on code generation and does not force a specific
inheritance structure to work.

PersistentObject is built to be fast and flexible, allowing you to build
persistent classes in the same way as any other class in your application. It
also utilizes the query abstraction layer of the Database component to make it
easy to build queries. Please refer to the API documentation of ezcQuerySelect
for detailed examples on how to use the query abstraction layer.

The PersistentObjectDatabaseSchemaTiein component allows you to automatically
generate the definition files needed by PersistentObject from a database schema
or the structure of your database. For more information, please refer to
the API documentation on ezcPersistentObjectSchemaGenerator.

Class overview
==============

ezcPersistentSession is the main API for interacting with the persistent
objects. Loading, saving and deleting persistent objects is done through this
class. ezcPersistentSessionIdentityDecorator can be used to add identity
mapping support to it (see `Identity mapping`_).

Basic usage
===========

This chapter describes the typical usage of PersistentObject with a
single persistent class, using MySQL as the persistence storage.

The persistent class
--------------------

We want to make a simple class, representing a person, persistent using
a persistent object. This is a simple class with only a few members::

    <?php
    class Person
    {
        private $id = null;
        public $name = null;
        public $age = null;

        public function getState()
        {
            $result = array();
            $result['id'] = $this->id;
            $result['name'] = $this->name;
            $result['age'] = $this->age;
            return $result;
        }

        public function setState( array $properties )
        {
            foreach( $properties as $key => $value )
            {
                $this->$key = $value;
            }
        }
    }
    ?>

The id member will map to the required persistent identifier. It has to default
to null. This is not required for any of the other mapped members. The id field
is a required unique identifier for this persistent object. It is generated by
the identifier generator and usually maps to an auto increment column in the
database.

For simplicity, we have made the name and age members of the Person class
public. However, this is not required and in a real application you can use 
any access method you like. You can even make the data completely private.

All persistent objects must implement the getState() and setState()
methods. They are used to retrieve the state of the object when saving it and
to set it when loading it. The getState() method should always return the
complete state of the object while the setState() method should set
only one member at the time.

The state arrays used by getState() and setState() must be indexed by the
property names, as defined in the persistence mapping below. They do not act on
the database columns names.

The persistence mapping
-----------------------

We are going to map the Person class onto the following SQL table::

  CREATE TABLE persons
  (
    id integer unsigned not null auto_increment,
    full_name varchar(255),
    age integer,
    PRIMARY KEY (id)
  ) TYPE=InnoDB;

The fields map one to one to the members of the Person class. Using the
InnoDB type is not required. We strongly recommend it however, since it
supports transactions. The id column is of the type auto_increment. This is
required for the id generator that we will use. Other id generators may have
other requirements to work as expected.

In order for PersistentObject to be able to store objects of the Person class
into the persons table, we need to tell it how the columns are mapped to class 
members. We will use ezcPersistentCodeManager to fetch the definitions when
required.

ezcPersistentCodeManager requires us to define the mapping using the
ezcPersistentObjectDefinition, ezcPersistentObjectIdProperty and
ezcPersistentObjectProperty classes::

    <?php
    $def = new ezcPersistentObjectDefinition();
    $def->table = "persons";
    $def->class = "Person";

    $def->idProperty = new ezcPersistentObjectIdProperty;
    $def->idProperty->columnName = 'id';
    $def->idProperty->propertyName = 'id';
    $def->idProperty->generator = new ezcPersistentGeneratorDefinition( 'ezcPersistentNativeGenerator' );

    $def->properties['name'] = new ezcPersistentObjectProperty;
    $def->properties['name']->columnName = 'full_name';
    $def->properties['name']->propertyName = 'name';
    $def->properties['name']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_STRING;

    $def->properties['age'] = new ezcPersistentObjectProperty;
    $def->properties['age']->columnName = 'age';
    $def->properties['age']->propertyName = 'age';
    $def->properties['age']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_INT;

    return $def;
    ?>

The first block of code creates the definition object and sets the database
table and the name of the class to map. The second block defines the mapping of
the identifier member and the algorithm that should be used to create
identifiers for new objects. We will use ezcPersistentNativeGenerator, which
simply retrieves the new id generated by auto_increment. If you rely on a
database backend that does not support auto_increment (e.g. Oracle),
ezcPersistentSequenceGenerator is the class to choose here.

The next two code blocks define the mapping between the database columns and
the class members. It is possible to use the same name in the class and the
database for a field.

The members must be inserted into the properties member, which is an
associative array, using the name of the member as the key name. Note that this
must not necessarily be the same name as the database column the property
corresponds to. It is required that you use the property name here!

If you look at the API for ezcPersistentObjectDefinition, it also has a
property named "columns" that is the same array as the "properties", except
that it is mapped to the column names instead of the property names. This
reverse mapping is set up by ezcPersistentCodeManager automatically.

Finally, we return the complete definition. Your definition will not work unless
you return it to the manager.

To make the definition work with the ezcPersistentCodeManager, it must be put in
a separate PHP file and given the name of the class in lowercase letters. In
our example, the filename should be person.php.

For classes in namespaces (PHP 5.3 and newer), sub-directories are used for the
namespaces. That means, the persistence definition for a class
``\My\Namespace\Person`` must reside in the file ``my/namespace/person.php``.

The session object
------------------

The session object is in charge of the actual loading and saving of persistent
objects. A session can be created by simply instantiating it::

    <?php
    $db = ezcDbFactory::create( 'mysql://user:password@host/database' );
    $session = new ezcPersistentSession(
        $db,
        new ezcPersistentCacheManager( new ezcPersistentCodeManager( "path/to/definitions" ) )
    );
    ?>

The session takes two arguments: a pointer to the database instance to use and
the manager from which to retrieve persistent object definitions. You can also
use a Database instance by using the ezcDbInstance class. You can then simply
use ezcDbInstance::get(); instead of passing $db as argument to the
ezcPersistentSession's constructor.

We are using ezcPersistentCodeManager to load the definitions directly from
file. You should point it to the location where you saved the person.php file.
If you have several directories containing definitions, you can use the
ezcPersistentMultiManager class to add as many as you like. In addition to the
code manager we use a cache manager. The cache manager makes sure the
definition is loaded from disk only once.

While it is possible to create a new session each time you want to manipulate a
persistent object, you will probably want to use the same session each
time. This functionality can be achieved by using the
ezcPersistentSessionInstance class::

   <?php
   $session = new ezcPersistentSession( ezcDbInstance::get(),
                                        new ezcPersistentCacheManager( new ezcPersistentCodeManager( ... ) ) );
   ezcPersistentSessionInstance::set( $session ); // set default session

   // retrieve the session
   $session = ezcPersistentSessionInstance::get();
   ?>

Lazy initialization
-------------------

Lazy initialization is a mechanism to load and configure a component, only 
when it is really used in your application. This mechanism saves time for 
parsing the classes and configuration, when the component is not used at all
during one request. You can find a description how you can use it for your 
own components and how it works in the `ezcBase tutorial`__. The keyword for
the database component is ezcInitPersistentSessionInstance.

__ introduction_Base.html#lazy-initialization

.. include:: tutorial_lazy_initialization.php
	:literal:

ezcBaseInit::setCallback accepts as a first parameter a component specific key,
which lets the component later request the right configuration callback. The
second parameter is the name of the class to perform the static callback on.
This class must implement the ezcBaseConfigurationInitializer class.
Each component's lazy initialization calls the static method configureObject()
on the referenced class.

This example shows a way to configure multiple database handlers, only when
they are really requested in your application. The example does basically the
same like the first example in this tutorial, but creates the connection not
before it is really required.

In line 32 the default persistent session is first requested in this example,
which does not exist yet, so that the configuration class earlier referenced
through the setCallback() call will be asked for a new instance for the
current instance name, which is 'null' for the default instance.

In the configureObject() method in line 8 we switch on the instance name and
create and return the right newly created database handler. Line 35 shows,
that this will also work with multiple database instances, creating an
additional persistent session that reads the definition files from another
directory (additionalPersistent).

Creating and updating an object
-------------------------------

Creating a new Person object and making it persistent is straightforward::

    <?php
    $object = new Person();
    $object->name = "Guybrush Threepwood";
    $object->age = 31;

    $session->save( $object );
    ?>

This code saves our newly created object to the database and generates an id
for it. The id is set to the id property of the object. Since Guybrush is our
first person, he is assigned the id of 1.

Of course, the age of Guybrush Threepwood is the source of much debate, and he
is probably younger than 31. To change his age, simply edit the object and
tell the session to update it. ::

    <?php
    $object->age = 25;
    $session->update( $object );
    ?>

Note that we used update() to store the object this time. This is because we
want to trigger an UPDATE query instead of an INSERT query.

Finding objects
----------------

There are several ways to retrieve persistent objects from the database. The
simplest is to fetch one object by its identifier. ::

    <?php
    $object = $session->load( 'Person', 1 );
    ?>

This code retrieves the Guybrush object created above.

If you have stored a lot of persistent objects to the database and want to retrieve a
list, you can use the find() method. The find() method requires a query parameter
that can first be retrieved from the session. ::

    <?php
    $q = $session->createFindQuery( 'Person' );
    $q->where( $q->expr->gt( 'age', $q->bindValue( 15 ) ) )
       ->orderBy( 'full_name' )
       ->limit( 10 );
    $objects = $session->find( $q, 'Person' );
    ?>

This code will fetch a maximum of 10 Person objects where the age is
higher than 15, sorted by name.

This is achieved by manipulating the query object returned by
ezcPersistentSession->createFindQuery(). To learn more about query abstraction
and how to use it, please refer to the `specific subsection of the Database
components tutorial`_.

.. _`specific subsection of the Database components tutorial`: introduction_Database.html#query-abstraction-usage

The find() method will fetch the complete result set and instantiate it for
you. This is not desirable if you are fetching large numbers of objects and you
want it to be fast and efficient. For this you can use the findIterator()
method::

    <?php
    $q = $session->createFindQuery( 'Person' );
    $q->where( $q->expr->gt( 'age', $q->bindValue( 15 ) ) )
      ->orderBy( 'name' )
      ->limit( 10 );
    $objects = $session->findIterator( $q, 'Person' );

    foreach( $objects as $object )
    {
        // ...
    }
    ?>

This code will produce the same result as the first find() example. However,
only one object will be instantiated and the data will be transferred from the
database only when it is needed.

The final example uses a find query with a logical and to find objects::

    <?php
    $q = $session->createFindQuery( 'Person' );
    $q->where( 
        $q->expr->lAnd( 
           $q->expr->eq( 'name', $q->bindValue( 'Guybrush Threepwood' ) ), 
           $q->expr->eq( 'age', $q->bindValue( 25 ) ) 
        )
    );
    $objects = $session->findIterator( $q, 'Person' );
    foreach( $objects as $object )
    {
        // ...
    }
    ?>

Deleting objects
-----------------

The easiest way to delete persistent objects is to use the delete() method on
the session::

    <?php
    $object = $session->load( 'Person', 1 );
    $session->delete( $object );
    ?>

Of course, you can only delete instantiated objects this way. If you want to
delete an object or a whole series of objects that are not instantiated, you can
use the deleteFromQuery() method::

    <?php
    $q = $session->createDeleteQuery( 'Person' );
    $q->where( $q->expr->gt( 'age', $q->bindValue( 15 ) ) );
    $session->deleteFromQuery( $q );
    ?>

The above code will remove all persons from the database who are more than 15
years old.


Identifier generation
=====================

All persistent objects must have an identifier field. The identifier
generation algorithm defines how the system will generate ids for new
objects. This chapter describes the available generators.

ezcPersistentSequenceGenerator
------------------------------

The sequence generator relies on the PDO::lastInsertId() method to retrieve the
ids for newly created persistent objects.

For databases supporting auto_increment (like MySQL and SQLite), use
ezcPersistentNativeGenerator. Other databases must use a sequence. For example,
the PostgreSQL person table definition should be as follows::

  CREATE TABLE persons
  (
    id integer unsigned not null,
    full_name varchar(255),
    age integer,
    PRIMARY KEY (id)
  );
  CREATE SEQUENCE person_seq START 1;

If your database requires you to use a sequence, this parameter should be
provided to ezcPersistentSequenceGenerator in the mapping definition. ::

    <?php
    $def->idProperty->generator = new ezcPersistentGeneratorDefinition(
        'ezcPersistentSequenceGenerator',
        array( 'sequence' => 'person_sequence' )
    );
    ?>

ezcPersistentNativeGenerator
----------------------------

The native generator relies on auto_increment, which is supported by, among
others, MySQL and SQLite. An example table definition looks like this::

    CREATE TABLE persons
    (
        id integer unsigned not null auto_increment,
        full_name varchar(255),
        age integer,
        PRIMARY KEY (id)
    );

The corresponding generator definition is below::

    <?php
    $def->idProperty->generator = new ezcPersistentGeneratorDefinition(
        'ezcPersistentNativeGenerator'
    );
    ?>

ezcPersistentManualGenerator
----------------------------

If you do not rely on a database mechanism to generate values for a primary
key column, you have to use the ezcPersistentManualGenerator class. You can
then set the value of the id property by hand and save the object afterwards.

For example::
    
    CREATE TABLE persons
    (
        login varchar(25),
        full_name varchar(255),
        age integer,
        PRIMARY KEY (login)
    );

In this table, the string value is used as the primary key. Therefore, we have
to generate id values manually. Use the following definition::

    <?php
    $def->idProperty->generator = new ezcPersistentGeneratorDefinition(
        'ezcPersistentManualGenerator'
    );
    ?>

For saving a new instance, use the following code::
    
    <?php
    $object = new Person();

    // Manually set the id
    $object->login = "guybrush";

    $object->name = "Guybrush Threepwood";
    $object->age = 31;

    $session->save( $object );
    ?>

Definition loaders
===================

The session object needs to be able to fetch the persistent object
definitions in order to function properly. The task of fetching the definitions
is performed by a definition loader, which is provided to the session when it is
instantiated. 

ezcPersistentCodeManager
-------------------------

This is currently the only manager available. It simply reads the definition
from a file with the same name as the class from the specified directory. It does
not perform any error checking on the definition and simply assumes that it is
correct.


Extending the definition loader
-------------------------------

It is very easy to create your own definition loader. Simply extend the
ezcPersistentDefinitionManager abstract class and implement the
fetchDefinition() method::

    <?php
    class ezcPersistentCodeManager extends ezcPersistentDefinitionManager
    {
        public function fetchDefinition( $class )
        {
        }
    }
    ?>

The fetchDefinition() method should create the definition structure for the
requested class or throw an exception.

Relations
=========

Relations are defined within the persistence mapping.

Relation class overview
-----------------------

The following definition classes are available to realize object relations:

ezcPersistentOneToManyRelation
  This class is used to define 1:n relations. For example, one person might be
  related to multiple addresses, but one address might only be related to
  one person.

ezcPersistentManyToManyRelation
  Using this class, you can define n:m relations. For example, a person can be
  related to multiple addresses, while an address can be related to multiple
  persons.

ezcPersistentOneToOneRelation
  With this class you can define 1:1 relations, which might be useful for
  slight de-normalization. For example, you can split your user data
  from the user credentials.

ezcPersistentManyToOneRelation
  This relation (n:1) does not make sense on its own, but as the reverse
  connection for a 1:n relation.

ezcPersistentRelationCollection
  This class allows you to define multiple relations to the same PHP class.

All of these classes extend the abstract class ezcPersistentRelation.

Relations basics
----------------

For the examples in this section, we will reuse the Person class, defined in
`The persistent class`_. In addition, we will use an Address class, which looks
as follows::

    <?php
    class Address
    {
        public $id = null;
        public $street = null;
        public $zip = null;
        public $city = null;

        public function setState( array $state )
        {
            foreach ( $state as $key => $value )
            {
                $this->$key = $value;
            }
        }

        public function getState()
        {
            return array(
                "id" => $this->id,
                "street" => $this->street,
                "zip" => $this->zip,
                "city" => $this->city,
            );
        }
    }
    ?>

The Address class will be extended later on to include relations. The
following basic persistence mapping is used and extended for each example::

    <?php
    $def = new ezcPersistentObjectDefinition();
    $def->table = "addresses";
    $def->class = "Address";

    $def->idProperty                = new ezcPersistentObjectIdProperty;
    $def->idProperty->columnName    = 'id';
    $def->idProperty->propertyName  = 'id';
    $def->idProperty->generator     = new ezcPersistentGeneratorDefinition( 'ezcPersistentSequenceGenerator' );

    $def->properties['street']                 = new ezcPersistentObjectProperty;
    $def->properties['street']->columnName     = 'street';
    $def->properties['street']->propertyName   = 'street';
    $def->properties['street']->propertyType   = ezcPersistentObjectProperty::PHP_TYPE_STRING;

    $def->properties['zip']                 = new ezcPersistentObjectProperty;
    $def->properties['zip']->columnName     = 'zip';
    $def->properties['zip']->propertyName   = 'zip';
    $def->properties['zip']->propertyType   = ezcPersistentObjectProperty::PHP_TYPE_STRING;

    $def->properties['city']                 = new ezcPersistentObjectProperty;
    $def->properties['city']->columnName     = 'city';
    $def->properties['city']->propertyName   = 'city';
    $def->properties['city']->propertyType   = ezcPersistentObjectProperty::PHP_TYPE_STRING;

    return $def;
    ?>

Defining a simple relation
--------------------------

The following extensions are necessary for the given class and persistence
mapping, to realize a simple 1:n relation. Each person will be able to have
multiple addresses, but one address may only refer to one person.

The Address class needs to be enhanced as follows, to store the id of the
Person it is related to. ::

    <?php
    class Address
    {
        // ...

        private $person;

        // ...

        public function getState()
        {
            return array(
                // ...
                "person"    => $this->person,
            );
        }
    }
    ?>

Additionally, we need to define the new property $person in the persistence
mapping of the Address class::

    <?php
    // ...

    $def->properties['person'] = new ezcPersistentObjectProperty;
    $def->properties['person']->columnName   = 'person_id';
    $def->properties['person']->propertyName = 'person';
    $def->properties['person']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_INT;

    ?>

The relation definition takes place in the persistence mapping of the Person
class in `The persistent class`_. It needs to be extended as follows::

    <?php
    // ...

    $def->relations["Address"] = new ezcPersistentOneToManyRelation(
        "persons",
        "addresses"
    );
    $def->relations["Address"]->columnMap = array(
        new ezcPersistentSingleTableMap(
            "id",
            "person_id"
        )
    );

    // ..
    ?>

A relation to another persistent object is defined in the property
ezcPersistentObjectDefinition $relations, which is an array. Each relation must
have the name of the persistent object class it refers to as the key in this
array. An instance of one of the classes shown in `Class overview`_ must be the
value. In this case, it is ezcPersistentOneToManyRelation. The parameter to its
constructor are the names of the tables that the relation refers to. The first
table is the table of the current object, and the second one refers to the related
object.

To define which properties are used to realize the relation mapping, the
property ezcPersistentOneToManyRelation->columnMap is used. It contains an
array of (in this case) ezcPersistentSingleTableMap, which maps one column of
each of the tables to one column of another. In the above case, the
database column "id" from the table "persons" would be mapped to the column
"person_id" in the table "addresses". In general, this means, that "id" is the
primary key from the "persons" table and "person_id" is the foreign key in the
"addresses" table, that refers to the "persons" table. Please note that the 
relation mappings are done on the table name/column name and not on the
class name/property name.

If you want to map using several columns, you can add more
ezcPersistentSingleTableMap instances to the columnMap array. For example, if
you are using a person's first and last name as the primary key for the
"persons" table, you could define the relation like this::

    <?php
    // ...

    $def->relations["Address"] = new ezcPersistentOneToManyRelation(
        "persons",
        "addresses"
    );
    $def->relations["Address"]->columnMap = array(
        new ezcPersistentSingleTableMap(
            "firstname",
            "person_firstname"
        ),
        new ezcPersistentSingleTableMap(
            "lastname",
            "person_lastname"
        )
    );

    return $def;
    ?>

Using a relation
----------------

To use the previously defined 1:n relation, ezcPersistentSession offers several
new methods:

ezcPersistentSession->getRelatedObject()
  This method can be used to retrieve a single related object to a given source
  object. If no related object can be found, it will throw an exception.

ezcPersistentSession->getRelatedObjects()
  In contrast to ezcPersistentSession->getRelatedObject(), this method always
  returns an array of all related objects. It will not throw an exception if no
  related object can be found, but will instead return an empty array.

ezcPersistentSession->addRelatedObject()
  Using this method, you can build a relation between two persistent objects. It
  will set the defined properties on the objects, but does not store them to
  the database automatically. (Exceptions are
  ezcPersistentManyToManyRelation objects. Further details are below.)

ezcPersistentSession->removeRelatedObject()
  As the counterpart to ezcPersistentSession->addRelatedObject(), this method
  is used to remove the relation between two objects. It will not store the given
  objects for you, but only remove the necessary properties. (Again, exceptions
  are ezcPersistentManyToManyRelation objects. Further details are
  below.)

Using these methods, we can now retrieve all addresses that are related to one
person::

    <?php
    $person = $session->load( "Person", 1 );
    $addresses = $session->getRelatedObjects( $person, "Address" );
    ?>

The variable $addresses will then contain an array of all Address objects found
for the Person object with an id of 1. To relate these addresses to another
Person object, we can do the following::


    <?php
    $personOld = $session->load( "Person", 1 );
    $personNew = $session->load( "Person", 23 );
    $addresses = $session->getRelatedObjects( $personOld, "Address" );

    foreach ( $addresses as $address )
    {
        $session->removeRelatedObject( $personOld, $address );
        $session->addRelatedObject( $personNew, $address );
        $session->update( $address );
    }
    ?>

Defining n:m relations
----------------------

The ezcPersistentManyToManyRelation class works slightly different than
the other ezcPersistentRelation classes. For this kind of relation, you need an
extra table in your database to store the relation records. The next example
shows the definition of an ezcPersistentManyToManyRelation relation, based on
the Person and Address example classes. Each person can have several
addresses and each address can be used by several persons. You need to use the 
original example classes for this, thus we do not need to extend them here. 
The definition of the relational mapping for the Person class must be extended 
as follows::

    <?php
    // ...

    $def->relations["Address"] = new ezcPersistentManyToManyRelation(
        "persons",
        "addresses",
        "persons_addresses"
    );
    $def->relations["Address"]->columnMap = array(
        new ezcPersistentDoubleTableMap( "id", "person_id", "address_id", "id" )
    );

    return $def;
    ?>
    
In contrast to all other implementations of ezcPersistentRelation, the
ezcPersistentManyToManyRelation constructor expects three table names:

- the name of the current objects table
- the name of the related objects table
- the table name to store the relation records

A similar exception applies to columnMap of this relation definition. It
consists of ezcPersistentDoubleTableMap instances, which carry four column names
each. The first column is the column to choose from the source table (usually
its primary key). The second column defines the column in your relation table
that maps to the first column. In our example, the column "id" from the
"persons" table maps to the column "person_id" from the relation table
"persons_addresses". The same applies to the third and fourth columns. The third
column defines the column of the relation table that maps to the fourth column
given. The fourth column specifies the column of your destination table to use for
mapping. In our example, the relation table "persons_addresses" has a column
"address_id", which is a foreign key referring to the column "id" in the table
"addresses".

As with ezcPersistentSingleTableMap instances, you can use multiple
mappings in one ezcPersistentManyToManyRelation->columnMap array. Here, we use
a person's first and last name for the mapping::

    <?php
    // ...

    $def->relations["Address"] = new ezcPersistentManyToManyRelation(
        "persons",
        "addresses",
        "persons_addresses"
    );
    $def->relations["Address"]->columnMap = array(
        new ezcPersistentDoubleTableMap( "firstname", "person_firstname", "address_id", "id" ),
        new ezcPersistentDoubleTableMap( "lastname", "person_lastname", "address_id", "id" )
    );

    return $def;
    ?>

Using n:m relations
-------------------

As stated earlier, the usage methods behave slightly differently when dealing
with n:m relations. If you use ezcPersistentSession->addRelatedObject(), the 
desired relation record is inserted into the relation table. The same applies to the
removeRelatedObject() method of ezcPersistentSession, which deletes the specific
record. This also means that you do not need to store the affected objects
explicitly after altering the relations between them. If you have made other
changes to the objects, they must be stored to save the changes.

Aside from that, the ezcPersistentSession->delete() method keeps track of the
relation records. If you delete a record, all of its relation records are
automatically deleted.

Special 1:1 relations
---------------------

If you want to use 1:1 relations, where two tables share a common primary key,
you need to define a table to generate the key and the other table to use
ezcPersistentManualGenerator. For our example, if one person may only have one
address, the definition would be as follows::

    <?php
    $def = new ezcPersistentObjectDefinition();
    $def->table = "addresses";
    $def->class = "Address";

    $def->idProperty                = new ezcPersistentObjectIdProperty;
    $def->idProperty->columnName    = 'id';
    $def->idProperty->propertyName  = 'id';
    $def->idProperty->generator     = new ezcPersistentGeneratorDefinition( 'ezcPersistentManualGenerator' );

    return $def;
    ?>

This is the relation (defined in the definition file of the Person)::

    <?php
    // ...

    $def->relations["Address"] = new ezcPersistentOneToOneRelation(
        "persons",
        "addresses"
    );
    $def->relations["Address"]->columnMap = array(
        new ezcPersistentSingleTableMap(
            "id",
            "person_id"
        )
    );

    return $def;
    ?>

If you let both tables use ezcPersistentSequenceGenerator for the same key,
ezcPersistentSession will fail to save a related object, since the id will
already be set by the ezcPersistentSession::addRelatedObject() method.

Another way to make this work is to not use the same primary key for both
tables, but to make the Address object have its own id and only use the Person
id as a foreign key.

Reverse relations
-----------------

Since you can always look at a relation from two sides, ezcPersistentRelation
implementations can be configured to be "reverse". A reverse relation
indicates that the relation is already defined in the opposite direction and
that the original direction is the main used one. The one
marked as "reverse" is a secondary one, for consistency reasons. For a relation
that is marked as reverse, it is not possible to use
ezcPersistentSession->addRelatedObject() and
ezcPersistentSession->removeRelatedObject(). You can still use
ezcPersistentSession->getRelatedObjects() for relations that are flagged
"reverse".

For most relation types, the reverse attribute of the relation definition object
is set to false by default. You can manually set it. Exceptions are
ezcPersistentManyToOneRelation relations. This relation type only makes sense
as a reverse relation for ezcPersistentOneToManyRelation. Therefore, the
reverse attribute is set to true for ezcPersistentManyToOneRelation and is not
publicly accessible for writing.

The following example shows the reverse relation definition for the n:m relations
example::

    <?php
    // ...

    $def->relations["Person"] = new ezcPersistentManyToManyRelation(
        "addresses",
        "persons",
        "persons_addresses"
    );
    $def->relations["Address"]->columnMap = array(
        new ezcPersistentDoubleTableMap( "id", "address_id", "person_id", "id" ),
    );
    $def->relations["Address"]->reverse = true;

    return $def;
    ?>

With the relation definition shown above, you would still be able to relate the
Persons object to an Address object, but not to add or remove related
Person objects to/from an Address. In other words, the following code still
works::

    <?php
    $address = $session->load( "Address", 23 );
    $persons = $session->getRelatedObjects( $address, "Person" );
    // ...
    ?>

While the following would not work::

    <?php
    // ...
    foreach ( $persons as $person )
    {
        $session->removeRelatedObject( $address, $person );
    }
    ?>

Instead, only the other direction works, because this one is the main
direction. ::

    <?php
    // ...
    foreach ( $persons as $person )
    {
        $session->removeRelatedObject( $person, $address );
    }
    ?>

Cascading deletes
-----------------

Cascading relations are done through the flag "cascade" of a
ezcPersistentRelation implementation. All implementations except the
ezcPersistentManyToManyRelation class support this flag. It allows you to
automatically delete all related objects for a source object when the source
object is deleted.

The following example shows how to add a cascading relation to a relation
definition, based on the example from `Defining a simple relation`_::

    <?php
    // ...

    $def->relations["Address"] = new ezcPersistentOneToManyRelation(
        "persons",
        "addresses"
    );
    $def->relations["Address"]->columnMap = array(
        new ezcPersistentSingleTableMap(
            "id",
            "person_id"
        ),
    );
    $def->relations["Address"]->cascade = true;

    return $def;
    ?>

If you now use the following, the Person object and all related Address objects
are deleted::

    <?php
    $person = $session->load( "Person", 1 );
    $session->delete( $person );
    ?>

Beware that this does not work with ezcPersistentManyToManyRelation instances,
because it could cause serious inconsistencies in your data.

Defining multiple relations to the same PHP class
-------------------------------------------------

In some cases it might be necessary to define multiple relations to the same
PHP class. An example for this can be seen when enhancing the Person example
from `The persistence mapping`_  as follows::

  CREATE TABLE persons
  (
    id integer unsigned not null auto_increment,
    full_name varchar(255),
    age integer,
    mother integer,
    father integer,
    PRIMARY KEY (id)
  )

Here each person is connected to 2 objects of the same table: The mother and
the father. Since only 1 PHP class per table is desired, the class needs to be
referenced by the Person class twice.

Assuming that the 2 new properties of the Person class have been defined
correctly in the persistence mapping, the following code can be used to achieve
the desired relations::

    <?php
    // ... $def is the persistence defintion

    $relations = new ezcPersistentRelationCollection();

    // Mother relation

    $relations['mothers_children'] = new ezcPersistentOneToManyRelation(
        'PO_person',
        'PO_person'
    );
    $relations['mothers_children']->columnMap = array(
        new ezcPersistentSingleTableMap( 'id', 'mother' )
    );
    $relations['mothers_children']->cascade = true;

    $relations['mother'] = new ezcPersistentManyToOneRelation(
        'PO_person',
        'PO_person'
    );
    $relations['mother']->columnMap = array(
        new ezcPersistentSingleTableMap( 'mother', 'id' )
    );

    // ..

    $def->relations['Person'] = $relations;
    ?>

2 relations need to be defined to reflect the relation between a mother an her
children. "mother" defines the relation from a child to its mother and
"mothers_children" defines the opposite direction, from a child to its mother.
Both relations operate on the Person class itself, therefore an
ezcPersistentRelationCollection is used to carry the relation definitions.

Relations are defined as shown earlier in this section. The only difference
here is, that the relation definitions themselves are not added directly to the
$relations property of the ezcPersistentObjectDefinition instance. Instead they
are assigned to unique names on a relation collection, which is then added to
the $relations property of the object definition.

The comment indicating further code (...) in the example above indicates that
further relations are missing in the collection: The relations "father" and
"fathers_children" are excluded here, since they work exactly like the
corresponding mother relations, above. A fifth relation is shown below::

    <?php
    $relations = new ezcPersistentRelationCollection();

    // ... mother / father relations

    // Sibling relation

    $relations['siblings'] = new ezcPersistentManyToManyRelation(
        "PO_person",
        "PO_person",
        "PO_sibling"
    );
    $relations['siblings']->columnMap = array(
        new ezcPersistentDoubleTableMap( "id", "person", "sibling", "id" ),
    );

    $def->relations['MultiRelationTestPerson'] = $relations;

    // assigning the relation collection

    $def->relations['Person'] = $relations;
    ?>

As can be seen above, a relation collection can carry an arbitrary number of
relations, which are of arbitrary type.

Using multiple relations to the same PHP class
----------------------------------------------

To make use of the relations to the Person class defined in the last section,
the name of the desired relation has to be submitted to all relation
operations::

    <?php
    $mother   = $this->session->load( 'Person', 1 );
    $children = $this->session->getRelatedObjects(
        $mother,
        'Person',
        'mothers_children'
    );
    ?>

The code above fetches all children of a mother, as defined by the relation
"mothers_children". The third parameter to
ezcPersistentSession->getRelatedObject() is mandatory in this case. If you
leave it out, a ezcPersistentUndeterministicRelationException will be thrown.
The parameter is ignored if you submit it when not working with a relation
collection.

In the same manor a new related object can only be added if the affected
relation is submitted to ezcPersistentSession->addRelatedObject() as shown
below::

    <?php
    $newChild = new Person();
    $newChild->name = "New child";

    $this->session->save( $newChild );

    $this->session->addRelatedObject(
        $mother,
        $newChild,
        'mothers_children'
    );

    // Make relation changes take effect
    $this->session->save( $newChild );
    ?>

Identity mapping
================

The `identity map pattern`__, as described by Martin Fowler, ensures that only
1 PHP object with the same identity exists. That means, if you load an object
from the database a second time, the originally created instance is re-used
instead of creating a new instance. In addition to that, identity mapping can
potentially save SQL queries and therefore reduce database load.

Note that with identity mapping, the methods *updateFromQuery()* and
*deleteFromQuery()* will result in a complete reset of the identity map.
Further details on that can be found under `Effects of identity mapping`_.

.. __: http://martinfowler.com/eaaCatalog/identityMap.html

Activate identity mapping
-------------------------

Identity mapping is activated by wrapping an instance of
ezcPersistentSessionIdentityDecorator around your existing
ezcPersistentSession::

    <?php
    // $originalSession contains a valid ezcPersistentSession

    $identityMap = new ezcPersistentBasicIdentityMap(
        $originalSession->definitionManager
    );
    $session = new ezcPersistentSessionIdentityDecorator(
        $originalSession,
        $identityMap
    );
    ?>

You can transparently exchange ezcPersistentSession and
ezcPersistentSessionIdentityDecorator inside your application, since their APIs
do not differ.

One point where you might experience problems are *instanceof* checks for
ezcPersistentSession. Just replace these with checks for
ezcPersistentSessionFoundation.

Effects of identity mapping
---------------------------

Identity mapping avoids that you have 2 different PHP objects with the same
database identity in your application. For example::

    <?php
    // $identitySession is an ezcPersistentSessionIdentityDecorator

    $person = $identitySession->load( 'Person', 23 );

    // ... somewhere else in your app ...

    $samePerson = $identitySession->load( 'Person', 23 );
    ?>

The variables *$person* and *$samePerson* are both references to the very same
object. In fact, the second call to *$identitySession->load()* does not issue a
database query at all, but just returns the existing instance of the desired
*Person* object, since this has already been loaded before.

Identity mapping also affects finding of persistent objects::

    <?php
    // $identitySession is an ezcPersistentSessionIdentityDecorator

    $person = $identitySession->load( 'Person', 23 );

    $person->name = 'New Name';

    // ... somewhere else in your app ...

    $query = $identitySession->createFindQuery( 'Person' );
    $persons = $identitySession->find( $query );
    ?>

The call to *$identitySession->find()* fetches all *Person* objects from the
database. This includes the *Person* with ID 23, which has already been loaded
before. Due to that, there is not a second instance of this object in
*$persons*, but the existing instance is re-used. This does not save you an SQL
query, but avoids inconsistencies. The change of the *$name* property of the
*Person* object with ID 23 is reflected in the found objects, although the
object has not been saved, yet.

**Note:** You should not use the methods *updateFromQuery()* and
*deleteFromQuery()* with ezcPersistentSessionIdentityDecorator. If you use any
of these methods, all cached objects will automatically be removed from the
identity map, because the effects of these methods can not be traced by the
mechanism. The reset of the identity map might result in unexpected
inconsistencies, which should originally be avoided by the mechanism.

Related objects
---------------

Identity mapping also affects the loading of related objects::

    <?php
    // $identitySession is an ezcPersistentSessionIdentityDecorator

    $person = $identitySession->load( 'Person', 23 );
    $addresses = $identitySession->getRelatedObjects(
        $person,
        'Address'
    );

    // ... somewhere else in your app ...

    $sameAddresses = $identitySession->getRelatedObjects(
        $person,
        'Address'
    );

    ?>

The variable *$sameAddresses* contains the very same array of *Address* objects
as *$addresses*. In fact, no second database call is issued, to fetch the
related objects, since they have already been loaded before.

This also works, if you add or delete related objects::

    <?php
    // $identitySession is an ezcPersistentSessionIdentityDecorator

    $person = $identitySession->load( 'Person', 23 );
    $addresses = $identitySession->getRelatedObjects(
        $person,
        'Address'
    );

    // Add a new address
    $newAddress = new Address();
    $identitySession->addRelatedObject(
        $person,
        $newAddress
    );

    // Remove an address
    $identitySession->removeRelatedObject(
        $person,
        $addresses[42]
    );

    // ... somewhere else in your app ...

    $sameAddresses = $identitySession->getRelatedObjects(
        $person,
        'Address'
    );

    ?>

There is no additional database query performed on the second call to
*$identitySession->getRelatedObjects()* here, too. Still, the *$sameAddresses*
reflects the operations of adding a new *Address* to and removing the *Address*
with ID *42* from the list of related objects. Even if these operations have
not been reflected in the database, yet. 

Pre-fetching of related objects
-------------------------------

Using the identity map decorator allows you to pre-fetch related objects, using
a JOIN. There are 2 different methods that
ezcPersistentSessionIdentityDecorator has in addition to ezcPersistentSession.
The first one allows you to load a certain object together with related
objects::

    <?php
    // $identitySession is an ezcPersistentSessionIdentityDecorator

    $prefetchRelations = array(
        'addresses' => new ezcPersistentRelationFindDefinition(
            'Address',
            null,
            array(
                'cities' => new ezcPersistentRelationFindDefinition(
                    'City'
                )
            )
        ),
        'purchases' => new ezcPersistentRelationFindDefinition(
            'Purchase'
        )
    );

    $person = $identitySession->loadWithRelatedObjects(
        'Person',
        23,
        $prefetchRelations
    );

    // ... somewhere else in your app ...

    $addresses = $identitySession->getRelatedObjects(
        $person,
        'Address'
    );

    foreach ( $addresses as $address )
    {
        $city = $identitySession->getRelatedObjects(
            $address,
            'City'
        );
    }

    // ... somewhere else in your app ...

    $purchases = $identitySession->getRelatedObjects(
        $person,
        'Purchase'
    );

    ?>

The method *$idSession->loadWithRelatedObjects()* allows you to load a certain
object and its related objects (included nesting).

The array *$prefetchRelations* defines, which related objects are fetched. The
first ezcPersistentRelationFindDefinition in the array instructs to load
related objects of type *Address*. The second parameter to the constructor of
ezcPersistentRelationFindDefinition can be used to specify the relation name,
if you use multiple relations to the same PHP class. The third parameter is
used to define deeper relations. In the example above, it defines that for each
loaded *Address* object, the related object of class *City* is loaded.

The second element of the *$prefetchRelations* array defines that for the
object to load, also all related objects of the *Purchase* class are loaded.
This loading happens in a single SQL statement, using multiple JOIN statements.

After defining the relations to fetch, the *Person* with ID *23* is loaded,
including the defined related objects. Therefore, none of the latter calls to
*$idSession->getRelatedObjects()* results in a database query, since all of
these related object sets have already been loaded in a single SQL query.

In a similar way, you can find a set of objects including their related
objects. Assume that *$prefetchRelations* is defined in the same way as in the
last example::

    <?php
    // $identitySession is an ezcPersistentSessionIdentityDecorator

    // $prefetchRelations = array( ... );

    $query = $identitySession->createFindQueryWithRelations(
        'Person',
        $prefetchRelations
    );
    $persons = $identitySession->find( $query );

    // ... somewhere else in your app ...

    foreach ( $persons as $person )
    {
        $addresses = $identitySession->getRelatedObjects(
            $person,
            'Address'
        );

        foreach ( $addresses as $address )
        {
            $city = $identitySession->getRelatedObjects(
                $address,
                'City'
            );
        }

        $purchases = $identitySession->getRelatedObjects(
            $person,
            'Purchase'
        );
    }

    ?>

In this case, all *Person* objects are loaded from the database, including
their related objects as defined. Therefore, none of the calls to
*$identitySession->getRelatedObjects()* leads to a database query, but just
returns the pre-fetched set of related objects.

Related object sub-sets
-----------------------

In many cases it is not desirable to load all objects of a certain class and
their related objects. Therefore it is possible, to restrict the loaded
objects, using the typical query manipulations. However, this might often mean,
that the set of related objects fetched for an object does not contain all
related objects of a type, but only a sub-set.

To work around this potential inconsistencies, the concept of *named related
object sub-sets* was introduced::

    <?php
    // $identitySession is an ezcPersistentSessionIdentityDecorator

    $prefetchRelations = array(
        'custom_addresses' => new ezcPersistentRelationFindDefinition(
            'Address'
        ),
    );

    $query = $identitySession->createFindQueryWithRelations(
        'Person',
        $prefetchRelations
    );
    $query->where(
        $query->expr->gte(
            'custom_addresses_zip',
            40000
        ),
        $query->expr->lte(
            'custom_addresses_zip',
            50000
        ),
    );

    $persons = $identitySession->find( $query );

    // ... somewhere else in your app ...

    foreach ( $persons as $person )
    {
        $addresses = $identitySession->getRelatedObjectSubset(
            $person,
            'custom_addresses'
        );
    }

    ?>

In this example the query to fetch *Person* objects and their related *Address*
objects has been restricted by a WHERE clause: Only the persons of which the
*Address* lies between ZIP code *40000* and *50000* are fetched and only these
addresses are loaded as related objects. A person that has two addresses, one
with ZIP code *42235* and the other with *12345*, will be loaded with this
query, but only one of its addresses is loaded. This is obviously not the full
set of related objects of type *Address* for this person. Storing this set of
*Address* objects in the identity map as usual would lead to inconsistencies.

Therefore, whenever you use a WHERE condition to restrict the set of loaded
objects, a *named related object sub-set* is created. As can be seen in the
example above, to fetch the pre-loaded *Address* objects for a *Person*, the
method *$identity->getRelatedObjectSubset()* is used instead of
*getRelatedObjects()*. This method receives the alias you have chosen before
for a certain sub-set in the *$prefetchRelations* array. *'custom_addresses'*
is the array key used to define the pre-fetching of *Address* objects.

For this reasons, the array keys used in the *$prefetchRelations* array must be
unique on all levels, if you want to restrict the pre-fetching query using a
WHERE condition. As shown in the example above, the keys used to define related
objects to be loaded, are also used, to access these specific relations in the
query. *'custom_addresses_zip'* is used to restrict on the property *$zip* of
the *Address* objects to be fetched in the set *custom_addresses*.

**Note:** Normal sets of related objects and named sub-sets can exist beside
each other in the identity map. Whenever you use *addRelatedObject()*, all
named related objects sub-sets will be removed from the identity map, since the
mechanism can not detect to which of these the object must be added and to
which not. However, using *removeRelatedObject()* and *delete()* do not have
this side-effect, and will simply be reflected in the named sets, too.

Creating named sub-sets of related objects also works with the
*createRelationFindQuery()* method, which already exists in
ezcPersistentSession. This method optionally receives a 4th parameter
*$setName* on ezcPersistentSessionIdentityDecorator, to define the set to store
related objects found::

    <?php
    // $identitySession is an ezcPersistentSessionIdentityDecorator

    $person = $identitySession->load( 'Person', 23 );

    $query = $identitySession->createRelationFindQuery(
        $person,
        'Address',
        null,
        'custom_addresses'
    );

    $query->where(
        $query->expr->gte(
            'zip',
            40000
        ),
        $query->expr->lte(
            'zip',
            50000
        ),
    );

    $customAddresses = $identitySession->find( $query );

    // ... some where else in your app ...

    $sameCustomAddresses = $identitySession->getRelatedObjectSubset(
        $person,
        'custom_addresses'
    );

    ?>

Retrieving the named sub-set of related objects works the same way before. 

Note that there is no need to prefix the property names in the WHERE condition
in this case. Since only one level of related objects can be loaded here, it is
not necessary. Note also, that in this case, *Address* objects are only loaded
for one specific *Person* and are not pre-fetched for any other *Person*
object. Trying to access the created named related object sub-set for another
*Person* will return *null* since these to do not exist.

You can also access the named related object sub-set just created, by using an
equaling relation find query (which uses the same set name!). The identity
mapping mechanism detects that you want to load this specific named set,
determines that this is already loaded and simply returns it, instead of
issuing another database query.


..
   Local Variables:
   mode: rst
   fill-column: 79
   End: 
   vim: et syn=rst tw=79