The Oracle + PHP Cookbook

Persisting PHP5 Objects in Oracle
by Barry McKay

Make PHP5 objects persist in your database-driven Web applications, taking an important step toward fully object-oriented Web app development.

Downloads for this article:
 OracleDatabaseObject package
 Sample Application
 Oracle Database 10 g
 Apache HTTP Server 1.3 and later

Published December 2005

Many developers have been won over by the elegance and efficiency of PHP5's implementation of object-orientation, and are equally drawn to the power and robustness of the world's foremost database, Oracle. However, taking the next logical step—storing PHP objects in your database for Web application development—can be intimidating.

In this HowTo, you will learn how to store and retrieve PHP5 objects as objects in an Oracle Database via an object persistence layer (available in the OracleDatabaseObject package download). This technique allows any Oracle developer to take the final step toward full object-orientation in their Web applications—and hence toward a renewed focus on application logic, rather than messy SQL and coding.

What is an Object Persistence Layer?

Before I explain the mechanics of how to make PHP objects persist in Oracle databases, it would be wise to introduce the philosophy that underpins them: the concept of an object persistence layer.

It is only recently that the world's most popular scripting language, PHP, has given us a full and proper implementation of object orientation (in PHP5). This is an exciting prospect for developers who wish to emulate the rigor and flexibility of this method, which has been available in "traditional" programming environments for many years. However, because relational databases have played such a pivotal role in the evolution of the Web, the developer wishing to make full use of the method faces an interesting dilemma: object technology simply doesn't "fit" with relational database technology. Databases expect tables, not classes; tables expect rows, not objects. The textbooks call this phenomenon impedance mismatch.

Relational databases, which pre-date object technology, can adequately model simple data grouped together as simple types; for example, a Person table can have columns for a name (varchar2), height (INT), and date of birth (DATE). In contrast, object technology supports the concept of complex data types. For instance, a Person object can have a name and a height of basic types, but can also have a partner of type Person, and a job of type Job, each type their own complex data structures.

Consequently, the Web developer who wants to reap the full benefits of the object-oriented method and store complex data types (PHP objects) has only two options: emulate or use object databases, or, more likely, simply store their data objects in an ad hoc manner. The latter practice, although popular, is entirely unacceptable as it nullifies the elegance, extensibility, and re-usability of object orientation, which are its primary benefits.

Fortunately, Oracle provides object types, PL/SQL, and REFs. These features combined provide Oracle's own implementation of object-orientation; while technically incomplete, this implementation does allow developers to easily store objects as database objects, as opposed to in rows in a table with fixed types.

However, just because both PHP and Oracle "do" object technology doesn't mean that PHP objects can be stored as Oracle objects (and vice versa). Hence the need for an object persistence layer of services that allows PHP developers to simply and efficiently store and retrieve objects to and from their Oracle database.

This operation can be performed in many ways but all methods must meet the same requirements:

  1. Objects must be stored and retrieved via simple and efficient services.
  2. Objects stored in the database must be able to contain references to other objects (complex data types).
  3. Each objects must have a unique identifier.
  4. When objects are saved, all other associated objects states must also be saved.
  5. When objects are retrieved, all other associated objects states must also be retrieved.
The OracleDatabaseObject package is my implementation of these requirements. It reflects the most elegant approach available: the use of an abstract class and data type to model a generic Oracle database object. This approach allows any class to simply inherit from this generic class, and thereby inherit all the services required for persistence—essentially encapsulating itself in a layer that can interface with Oracle Database (see figure below).

figure 1


The object persistence layer dynamically provides everything necessary to make the object persist in the database. Whenever the method save() is called to save a PHP5 object to the database, the first task of the persistence layer is to test whether any object of this type has been saved before. If not, the state of the object (property types and values) is examined. Consider the following objects:

figure 2


If the save() method is called on Person1, and this is the first object of type Person to be saved in the database, the state and their types are examined—in this case the properties Name, Age, and Partner have PHP5 types of String, Integer, and Person respectively. Then, to allow this object to persist, an Oracle object type (also called Person) is created by the layer, with its structure based on the state of its PHP5 counterpart. The layer replicates the PHP5 types in Oracle—Name, a PHP5 string, would become an Oracle VARCHAR2; Age, a PHP5 integer, would become an Oracle Number; and the PHP5 Partner (of type Person) would become an Oracle REF of type Person. (See Table 1 for an overview of these conversions.)

PHP5 Type

Oracle Object Type

Numeric

Number

String

VARCHAR2

Array

VARCHAR2 *

Object

Object

Associated Object

REF to Object

* As of this writing, arrays are stored in Oracle as serialized Strings as VARCHAR2 for performance purposes.
In the future, however, they may be implemented with Oracle VARRAYs.

Once the Oracle object type structure has been created to mimic its PHP5 class counterpart, the values of the properties are saved to the newly created object type's table. In accordance with requirement 4 above, when an object with associated objects (as in the above example) is saved, all dependents must also be saved. This requirement is implemented simply by ensuring the save() method of the associated object is called when the current object's internal state is examined and saved.

Knowing these implementation details are useful when evaluating exactly what the layer does, but the power of the method is that you simply do not need to. As a developer, all you have to do is develop and implement classes with interacting objects—forgetting entirely about the programming involved. With this layer of services, you can store and retrieve objects to and from your database without ever having to set up tables or deal with SQL, freeing you to consider only the logic of the application itself.

Now, let's take a look at an example.

When to Make an Object Persist

The practice of developing with classes and objects in now routine for PHP5 developers. But not all these objects should persist in your Oracle database. Thus, the first step is to decide if you need to add persistence services to an object of any given class.

As developers move toward the global use of object orientation for the Web, we will collectively create and have access to thousands of classes—but we must bear in mind that not all of them need to persist. Rather, you have to think about what abstraction your class represents, and how the class's objects will be used in your application. As a general rule of thumb:

If an object's state must be stored after the script has finished, and can be accessed and/or updated during the runtime of the script, it should persist.

For example, most object-oriented Web developers use form objects in their applications—but very rarely do they need to store them in a database. For example, you don't need to hold a form object (with its elements, target, name, and so on), because it doesn't change after it has been created; there is no need to set the properties of the object in the script and store them in the database. In contrast, if you are developing an e-calendar application, you may want entry objects for each day object that will change as people add and edit them. When an entry is added or changed, the state of the object changes too; when an entry changes, you save the updated version in the database.

So, using the example of such an application, let's walk through the process of adding persistence services to a simple TestEntry class (TestEntry.class.php) using the OracleDatabaseObject package (OracleDatabaseObject.class.php) provided in the download (in Beta at the time of writing and scheduled for a production release by end of 2005).

Adding Oracle Database Persistence to a Class

The original TestEntry class is as follows:

class TestEntry 
{

    public $title;
    public $body;
    public $author;
    public $dayName;


function display()
{

    Echo $this->title . "<BR>" . $this->body . "<BR>" . $this->author;
}

}
Traditionally it would be very complicated to store objects of this class in your Oracle database. This process would involve creating a table that resembles the internal state of the class and then writing a series of procedural functions with relational SQL to insert/update/remove each property of the class. This is a massively complicated, inefficient task—and as objects become more complex (perhaps including arrays or other objects) it becomes almost impossible.

The OracleDatabaseObject class makes this process very simple by making everything you would otherwise do manually—such as storing the internal state of your class (including the types of all attributes) and creating an Oracle object type—dynamic. The end result is that you never have to worry about creating or amending tables or types in your Oracle database; all you have to do is concentrate on the logic of your application.

So, to create TestEntry objects and store and retrieve them from the database, make your class inherits from the OracleDatabaseObject class, which has all the services you need already built in (see Table 2.)

Table 2.


Methods

Argument

Purpose

Usage Example

Objects

save()

None

Saves the current object as an object in the database

$anObject->save()

load()

None

Loads object with current objectName from the database

$anObject->load()

Classes

addDatabaseUser ( $username )

A string containing the username to be used for the database that will store objects of the class

Sets the username and password and database name for the Oracle server that will store objects of the class, allowing classes to be held on individual databases with individual usernames and passwords.
ClassName::setUserName(‘scott’)
addDatabasePass ($password)

A string containing the password to be used for the database that will store objects of the class

ClassName::setPassword(‘tiger’)

 






addDatabaseName ( $databasename )

A string containing the database name to be used for the database that will store objects of the class

ClassName::setDatabase(‘orcl’)

The procedure for making these services available to the TestEntry class is

1. include_once 'DatabaseObject.class.php'; 
2. include_once 'OracleDatabaseObject.class.php'; 
3. 
4. class TestEntry extends OracleDatabaseObject 
5. {
6.
7.     public $title;
8.     public $body;
9.     public $author;
10.    public $dayName;
11.
12. function display()
14. {
15.      echo $this->title . "<BR>" . $this->body . "<BR>" . $this->author;
16. }

17.}
Here you have given yourself access to the classes you need ( OracleDatabaseObject is an abstract class that inherits from DatabaseObject ) by include_once- ing them as shown in lines 1 and 2. You then inherit from the OracleDatabaseObject class using the PHP5 inheritance keyword extends (line 4). Inheritance works by taking all the features (methods) and properties (attributes) of a parent class and making them available to child classes. It's a simple but powerful idea, expressed in the diagram below: the initial TestEntry class inherits from the OracleDatabaseObject class, producing a new TestEntry class combining the features of both.

figure 3

(The inheritance shown on the left side produces a new version of the Entry class shown on the right.)

Next, you will create objects of your TestEntry class and use the services layer to save and retrieve objects to and from Oracle.

Creating and Using Oracle Persistent Objects

Now that your class has the services required, the process of saving objects to the database is very simple, as shown below (ExampleA.php):

1.  include_once "DatabaseObject.class.php"; 
2.  include_once "OracleDatabaseObject.class.php"; 
3.  include_once "TestEntry.class.php"; 
4. 
5.  TestEntry::addDatabaseUser('scott'); 
6.  TestEntry::addDatabasePass('tiger'); 
7.  TestEntry::addDatabaseName('orcl'); 
8.
9.  $anEntry = new TestEntry(); 
10. $anEntry->title = 'Meeting with boss - 9am'; 
11. $anEntry->body = 'Meeting with boss scheduled to discuss raise'; 
12. $anEntry->author = 'Joe Bloggs'; 
13. $anEntry->dayName = 'Tuesday'; 
14. $anEntry->objectName = 'Tuesday'; 
15. $anEntry->display(); 
16. $anEntry->save(); 
After inheriting from OracleDatabaseObject , you must, in the manner shown in lines 5-7, bind a username, password, and database name to the class by using the services shown in Table 2. This operation allows each class (and hence all objects of that class) to come under an individual username, password, and database (incidentally, providing very fine-grained security for your objects).

You may wish to have specific names for objects, however, such as Tuesday . This approach allows you to retrieve the object by calling the load() method as shown in ExampleB.php (see below) from the database quickly and easily.

The last, and perhaps most important, thing you should note is the method save()in line 14. This method, also inherited from the OracleDatabaseObject class, simply saves your object to the database.

Make sure your database user has permission to create types and tables before running these examples.

Loading Your Oracle Object

So, you've seen how easy it is to save your object in Oracle. You don't even need to touch the database! Naturally, the next step is to retrieve it—which is just as simple.

Using the example of ExampleB.php, you would load your database object into a new PHP5 object as follows:

1.  include_once "DatabaseObject.class.php"; 
2.  include_once "OracleDatabaseObject.class.php"; 
3.  include_once "TestEntry.class.php"; 
4.  
5.  TestEntry::addDatabaseUser('scott'); 
6.  TestEntry::addDatabasePass('tiger'); 
7.  TestEntry::addDatabaseName('orcl'); 
8.  
9.  $anEntry = new TestEntry(); 
10. $anEntry->objectName = "Tuesday"; 
11. $anEntry->load(); 
12. $anEntry->display(); 
This produces the same output as ExampleA: the object's state is simply reloaded from the object stored in the database without any need for complex calls and queries and is displayed by calling the display() method.
$sql = "SELECT
        *
     FROM 
        (
            SELECT
                r.*, ROWNUM as row_number 
            FROM
                ( $select ) r
        )
     WHERE row_number BETWEEN :start_row AND :end_row";
For a more extensive demonstration, see the complete sample e-calendar application available for download. It includes source code for the classes as well as UML diagrams, allowing you to more thoroughly investigate this technique.

Using Oracle/PHP5 Objects

Storing objects in your database is not just a gimmick; rather, it is the final piece in the "developing entirely within the object-oriented paradigm" puzzle. It allows you to analyze, design, develop, and test using all the techniques that are so popular in traditional, large-scale, object-oriented programming—using approaches such as business objects, UML, and design by contract—seamlessly. This is a radical idea, but the Web development world is catching up to it.


Barry McKay is a freelance Web developer and object-oriented puritan based in Glasgow, Scotland. He is a contributor to International PHP Magazine.

Send us your comments