This chapter describes typical usage of the Persistent Object package with a
single persistent class using MySQL as the persistence storage.
We want to make a simple class, representing a person, persistent using
persistent object. It is a simple class with only a few members:
1. <?php
2. class Person
3. {
4. private $id = null;
5. public $name = null;
6. public $age = null;
7.
8. public function getState()
9. {
10. $result = array();
11. $result['id'] = $this->id;
12. $result['name'] = $this->name;
13. $result['age'] = $this->age;
14. return $result;
15. }
16.
17. public function setState( array $properties )
18. {
19. foreach( $properties as $key => $value )
20. {
21. $this->$key = $value;
22. }
23. }
24. }
25. ?>
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 e.g. access methods or properties or even having 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 be prepared to
only set one member at the time.
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 Persistent Object 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 the ezcPersistentCodeManager to fetch the definitions when
required.
ezcPersistentCodeManager requires us to define the mapping using the
ezcPersistentObjectDefinition, ezcPersistentObjectIdProperty and
ezcPersistentObjectProperty classes:
1. <?php
2. $def = new ezcPersistentObjectDefinition();
3. $def->table = "persons";
4. $def->class = "Person";
5.
6. $def->idProperty = new ezcPersistentObjectIdProperty;
7. $def->idProperty->columnName = 'id';
8. $def->idProperty->propertyName = 'id';
9. $def->idProperty->generator = new ezcPersistentGeneratorDefinition( 'ezcPersistentSequenceGenerator' );
10.
11. $def->properties['name'] = new ezcPersistentObjectProperty;
12. $def->properties['name']->columnName = 'full_name';
13. $def->properties['name']->propertyName = 'name';
14. $def->properties['name']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_STRING;
15.
16. $def->properties['age'] = new ezcPersistentObjectProperty;
17. $def->properties['age']->columnName = 'age';
18. $def->properties['age']->propertyName = 'age';
19. $def->properties['age']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_INT;
20.
21. return $def;
22. ?>
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 the ezcPersistentSequenceGenerator
which simply retrieves the new identifier generated by auto_increment.
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.
If you look at the API of ezcPersistentObjectDefinition
it also has a property named 'columns' which is the same array as the
'properties' except it is mapped on the column names instead of the property
names. This reverse mapping is set up by the ezcPersistentCodeManager.
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 it should be put in a file named person.php.
Creating a new Person object and making it persistent is straight forward:
1. <?php
2. $object = new Person();
3. $object->name = "Guybrush Threepwood";
4. $object->age = 31;
5.
6. $session->save( $object );
7. ?>
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 will get the identifier 1.
Of course, the age of Guybrush Threepwood is the source of much debate and most
probably he is younger than 31. To make the change simply edit the object and
tell the session to update it.
1. <?php
2. $object->age = 25;
3. $session->update( $object );
4. ?>
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.
There are several ways to retrieve persistent objects from the database. The
simplest is to fetch one object by its identifier.
1. <?php
2. $object = $session->load( 'Person', 1 );
3. ?>
This code retrieves the Guybrush object created above.
If you have stored a lot of persistent objects to the database and you want to retrieve a
list you can use the find method. The find method requires a query parameter which you
can retrieve from the session first.
1. <?php
2. $q = $session->createFindQuery( 'Person' );
3. $q->where( $q->expr->gt( 'age', $q->bindValue( 15 ) ) )
4. ->orderBy( 'full_name' )
5. ->limit( 10 );
6. $objects = $session->find( $q, 'Person' );
7. ?>
This code will fill fetch a maximum of 10 Person objects older than 15 years
sorted by their names.
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 fetchIterator()
method:
1. <?php
2. $q = $session->createFindQuery( 'Person' );
3. $q->where( $q->expr->gt( 'age', $q->bindValue( 15 ) ) )
4. ->orderBy( 'name' )
5. ->limit( 10 );
6. $objects = $session->findIterator( $q, 'Person' );
7.
8. foreach( $objects as $object )
9. {
10. // ...
11. }
12. ?>
This code will produce the same result as the first find() example. However,
only one object will be instantiated and the data will be transfered from the
database only when it is needed.
The easiest way to delete persistent objects is to use the delete method() on
the session:
1. <?php
2. $object = $session->load( 'Person', 1 );
3. $session->delete( $object );
4. ?>
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:
1. <?php
2. $q = $session->createDeleteQuery( 'Person' );
3. $q->where( $q->expr->gt( 'age', $q->bindValue( 15 ) ) );
4. $session->deleteFromQuery( $q );
5. ?>
When executed the above code will remove all persons older than 15 years old
from the database.
All persistent objects are required to have an identifier field. The identifier
generation algorithm defines how the system will generate IDs for new
objects. This chapter describes the available generators.
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) the usage of
ezcPersistentNativeGenerator. Other databases need to use a sequence. E.g
for PostgreSQL the person table definition should look like:
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 the ezcPersistentSequenceGenerator in the mapping definition.
1. <?php
2. $def->idProperty->generator = new ezcPersistentGeneratorDefinition(
3. 'ezcPersistentSequenceGenerator',
4. array( 'sequence' => 'person_sequence' )
5. );
6. ?>
The native generator relies on auto_increment, which is supported by e.g. MySQL
and SQLite. A table definition for this looks like that:
CREATE TABLE persons
(
id integer unsigned not null auto_increment,
full_name varchar(255),
age integer,
PRIMARY KEY (id)
);
The according generator definition:
1. <?php
2. $def->idProperty->generator = new ezcPersistentGeneratorDefinition(
3. 'ezcPersistentNativeGenerator'
4. );
5. ?>
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 you use a string value as the primary key and therefore have to
generate ID values manually. Use the following definition:
1. <?php
2. $def->idProperty->generator = new ezcPersistentGeneratorDefinition(
3. 'ezcPersistentManualGenerator'
4. );
5. ?>
And for saving a new instance the following code:
1. <?php
2. $object = new Person();
3.
4. // Manually set the ID
5. $object->login = "guybrush";
6.
7. $object->name = "Guybrush Threepwood";
8. $object->age = 31;
9.
10. $session->save( $object );
11. ?>
Relations are defined within the persistence mapping.
The following definition classes are available to realize object relations:
- ezcPersistentOneToManyRelation
- This class is used to define 1:n relations. For example 1 person can be
related to multiple addresses, while only 1 person can live at an address.
- ezcPersistentManyToManyRelation
- Using this class you can define n:m relations, for example a person can be
related to multiple addresses, while multiple persons can live at one
address.
- ezcPersistentOneToOneRelation
- With this class you can define 1:1 relations, which might be useful for
slight de-normalization. For example, if you want to split your users 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.
All of these classes extend the abstract class ezcPersistentRelation.
For the examples in this section, we will reuse the Person class, define in
The persistent class. In addition, we will use a class Address, which looks
as follows:
1. <?php
2. class Address
3. {
4. public $id = null;
5. public $street = null;
6. public $zip = null;
7. public $city = null;
8.
9. public function setState( array $state )
10. {
11. foreach ( $state as $key => $value )
12. {
13. $this->$key = $value;
14. }
15. }
16.
17. public function getState()
18. {
19. return array(
20. "id" => $this->id,
21. "street" => $this->street,
22. "zip" => $this->zip,
23. "city" => $this->city,
24. );
25. }
26. }
27. ?>
The Address class will be extended later on to include the relation. The
following basic persistence mapping is used and extended for each example:
1. <?php
2. $def = new ezcPersistentObjectDefinition();
3. $def->table = "addresses";
4. $def->class = "Address";
5.
6. $def->idProperty = new ezcPersistentObjectIdProperty;
7. $def->idProperty->columnName = 'id';
8. $def->idProperty->propertyName = 'id';
9. $def->idProperty->generator = new ezcPersistentGeneratorDefinition( 'ezcPersistentSequenceGenerator' );
10.
11. $def->properties['street'] = new ezcPersistentObjectProperty;
12. $def->properties['street']->columnName = 'street';
13. $def->properties['street']->propertyName = 'street';
14. $def->properties['street']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_STRING;
15.
16. $def->properties['zip'] = new ezcPersistentObjectProperty;
17. $def->properties['zip']->columnName = 'zip';
18. $def->properties['zip']->propertyName = 'zip';
19. $def->properties['zip']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_STRING;
20.
21. $def->properties['city'] = new ezcPersistentObjectProperty;
22. $def->properties['city']->columnName = 'city';
23. $def->properties['city']->propertyName = 'city';
24. $def->properties['city']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_STRING;
25. ?>
The following extensions are necessary to the given class and persistence
mapping, to realize a simple 1:n relation. Each person will be able to have
multiple addresses, but 1 address may only refer to 1 person.
The Address class needs to be enhanced as follows, to store the id of the
Person it is related to.
1. <?php
2. class Address
3. {
4. // ...
5.
6. private $person;
7.
8. // ...
9.
10. public function getState()
11. {
12. return array(
13. // ...
14. "person" => $this->person,
15. );
16. }
17. }
18. ?>
Additionally, we need to define the new property $person in the persistence
mapping of the Address class:
1. <?php
2. // ...
3.
4. $def->properties['person'] = new ezcPersistentObjectProperty;
5. $def->properties['person']->columnName = 'person_id';
6. $def->properties['person']->propertyName = 'person';
7. $def->properties['person']->propertyType = ezcPersistentObjectProperty::PHP_TYPE_INT;
8.
9. ?>
The relation definition takes place in the persistence mapping of the Person
class in The persistent class. It needs to be extended as follows:
1. <?php
2. // ...
3.
4. $def->relations["Address"] = new ezcPersistentOneToManyRelation(
5. "persons",
6. "addresses"
7. );
8. $def->relations["Address"]->columnMap = array(
9. new ezcPersistentSingleTableMap(
10. "id",
11. "person_id"
12. ),
13. );
14. ?>
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, 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 the other one. 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.
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 persons first and last name as the primary key for the
"persons" table, you could define the relation like this:
1. <?php
2. // ...
3.
4. $def->relations["Address"] = new ezcPersistentOneToManyRelation(
5. "persons",
6. "addresses"
7. );
8. $def->relations["Address"]->columnMap = array(
9. new ezcPersistentSingleTableMap(
10. "firstname",
11. "person_firstname"
12. ),
13. new ezcPersistentSingleTableMap(
14. "lastname",
15. "person_lastname"
16. ),
17. );
18. ?>
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 returns
always an array of all related objects. It will not throw an exception if no
related object can be found, but return an empty array.
- ezcPersistentSession->addRelatedObject()
- Using this method you can build a relation between 2 persistent objects. It
will set the defined properties on the objects, but does not store those to
the database automatically. (A little exception are
ezcPersistentManyToManyRelation objects. Further details below.)
- ezcPersistentSession->removeRelatedObject()
- As the counterpart to ezcPersistentSession->addRelatedObject(), this method
is used to remove the relation between 2 objects. It won't store the given
objects for you, but only remove the necessary properties. (Again a little
exception are ezcPersistentManyToManyRelation objects. Further details
below.)
Using these methods, we can now retrieve all addresses which are related to one
person:
1. <?php
2. $person = $session->load( "Person", 1 );
3. $addresses = $session->getRelatedObjects( $person, "Address" );
4. ?>
The variable $addresses will then contain an array of all Address objects found
for the Person object with ID 1. To relate these addresses to another Person
object, we can do the following:
1. <?php
2. $personOld = $session->load( "Person", 1 );
3. $personNew = $session->load( "Person", 23 );
4. $addresses = $session->getRelatedObjects( $personOld, "Address" );
5.
6. foreach ( $addresses as $address )
7. {
8. $session->removeRelatedObject( $personOld, $address );
9. $session->addRelatedObject( $personNew, $address );
10. $session->update( $address );
11. }
12. ?>
The eczPersistentManyToManyRelation class works slightly different compared to
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 on basis of
the Person and Address example classes allowing each person to have several
addresses and each address to be used by several persons. You need to use the
original example classes for this, hence we do not need to extend those here.
The definition of the relational mapping for the Person class must be extended
as follows:
1. <?php
2. // ...
3.
4. $def->relations["Address"] = new ezcPersistentManyToManyRelation(
5. "persons",
6. "addresses",
7. "persons_addresses"
8. );
9. $def->relations["Address"]->columnMap = array(
10. new ezcPersistentDoubleTableMap( "id", "person_id", "address_id", "id" ),
11. );
12. ?>
In contrast to all other implementations of ezcPersistentRelation, the
ezcPersistentManyToManyRelation constructor expects 3 table names:
- The name of the current objects table.
- The name of the related objects table.
- The table name to store the relation records into.
A similar exception applies to the columnMap of this relation definition: It
consists of ezcPersistentDoubleTableMap instances, which carry 4 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,
which 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 3rd and 4th column. The third
column defines the column of the relation table, which maps to the 4th column
given. The latter one defines the column of your destination table to use for
mapping. So 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. Again the
example, of using a persons first and last name for the mapping:
1. <?php
2. // ...
3.
4. $def->relations["Address"] = new ezcPersistentManyToManyRelation(
5. "persons",
6. "addresses",
7. "persons_addresses"
8. );
9. $def->relations["Address"]->columnMap = array(
10. new ezcPersistentDoubleTableMap( "firstname", "person_firstname", "address_id", "id" ),
11. new ezcPersistentDoubleTableMap( "lastname", "person_lastname", "address_id", "id" ),
12. );
13. ?>
As stated earlier, the usage methods behave slightly different when dealing
with n:m relations. If you use ezcPersistentSession->addRelatedObject() the
desired relation record is inserted into the relation table. 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.
Beside that, the ezcPersistentSession->delete() method keeps track of the
relation records. If you delete a record, all it's relation records are deleted
automatically.
If you desire to use 1:1 relations, where two tables share a common primary key,
you need to define 1 table to generate the key and the other table to use the
ezcPersistentManualGenerator. For our example, if one person may only have one
address, this could look like that:
1. <?php
2. $def = new ezcPersistentObjectDefinition();
3. $def->table = "addresses";
4. $def->class = "Address";
5.
6. $def->idProperty = new ezcPersistentObjectIdProperty;
7. $def->idProperty->columnName = 'id';
8. $def->idProperty->propertyName = 'id';
9. $def->idProperty->generator = new ezcPersistentGeneratorDefinition( 'ezcPersistentManualGenerator' );
10.
11. // ...
12. ?>
And for the relation (defined in the definition file of the Person):
1. <?php
2. // ...
3.
4. $def->relations["Address"] = new ezcPersistentOneToOneRelation(
5. "persons",
6. "addresses"
7. );
8. $def->relations["Address"]->columnMap = array(
9. new ezcPersistentSingleTableMap(
10. "id",
11. "person_id"
12. ),
13. );
14. ?>
If you let both tables use an 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 working 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 Persons
ID as a foreign key.
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. This implied, that 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 you
ezcPersistentSession->getRelatedObjects() for relations that are flagged
"reverse".
For most relation types the reverse attribute of the relation definition object
is per default set to false. You can manually set it. An exception are
ezcPersistentManyToOneRelation relations. This relation type only makes sense
as a reverse relation for a ezcPersistentOneToManyRelation. Therefore the
reverse attribute is set to false for ezcPersistentManyToOneRelation and is not
publicly accessible for writing.
The following example shows the reverse relation definition for the n:m relations
example:
1. <?php
2. // ...
3.
4. $def->relations["Person"] = new ezcPersistentManyToManyRelation(
5. "addresses",
6. "persons",
7. "persons_addresses"
8. );
9. $def->relations["Address"]->columnMap = array(
10. new ezcPersistentDoubleTableMap( "id", "address_id", "person_id", "id" ),
11. );
12. $def->relations["Address"]->reverse = true;
13. ?>
With the relation definition shown above, you would still be able to get the
Person objects related 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:
1. <?php
2. $address = $session->load( "Address", 23 );
3. $persons = $session->getRelatedObjects( $address, "Person" );
4. // ...
5. ?>
While the following won't work:
1. <?php
2. // ...
3. foreach ( $persons as $person )
4. {
5. $session->removeRelatedObject( $address, $person );
6. }
7. ?>
Instead, only the other direction works, because this one is the main
direction.
1. <?php
2. // ...
3. foreach ( $persons as $person )
4. {
5. $session->removeRelatedObject( $person, $address );
6. }
7. ?>
Cascading relations is done through the flag "cascade" of a
ezcPersistentRelation implementation. All implementations, except the
ezcPersistentManyToManyRelation class support this flag. It allows you to get
all related objects for a source object automatically deleted, if the source
object is deleted.
The following example shows how to add cascading to a relation definition,
based on the example from Defining a simple relation:
1. <?php
2. // ...
3.
4. $def->relations["Address"] = new ezcPersistentOneToManyRelation(
5. "persons",
6. "addresses"
7. );
8. $def->relations["Address"]->columnMap = array(
9. new ezcPersistentSingleTableMap(
10. "id",
11. "person_id"
12. ),
13. );
14. $def->relations["Address"]->cascade = true;
15. ?>
If you now use
1. <?php
2. $person = $session->load( "Person", 1 );
3. $session->delete( $person );
4. ?>
The Person object and all related Address objects are deleted. Beware, that
this does not work with ezcPersistentManyToManyRelation instances, because it
could cause serious inconsistency of your data.