Object-oriented programming is a way to group functions and data together into a prepackaged unit. This unit is known as an object.
Many people prefer OOP because it encourages a behavior known as encapsulation. Inevitably, whenever you write code, there's some part?the way you store the data, what parameters the functions take, how the database is organized?that doesn't work as well as it should. It's too slow, too awkward, or doesn't allow you to add new features, so you clean it up.
Fixing code is a good thing, unless you accidently break other parts of your system in the process. When a program is designed with a high degree of encapsulation, the underlying data structures and database tables are not accessed directly. Instead, you define a set of functions and route all your requests through these functions.
For example, you have a database table that stores names and email addresses. A program with poor encapsulation directly accesses the table whenever it needs to fetch a person's email address:
$name = 'Rasmus Lerdorf'; $db = mysql_connect( ); $result = mysql_query("SELECT email FROM users WHERE name LIKE '$name'", $db); $row = mysql_fetch_assoc($r); $email = $row['email'];
A better-encapsulated program uses a function instead:
function getEmail($name) { $db = mysql_connect( ); $result = mysql_query("SELECT email FROM users WHERE name LIKE '$name'", $db); $row = mysql_fetch_assoc($r); $email = $row['email']; return $email } $email = getEmail('Rasmus Lerdorf');
Using getEmail( ) has many benefits, including reducing the amount of code you need to write to fetch an email address. However, it also lets you safely alter your database schema because you only need to change the single query in getEmail( ) instead of searching through every line of every file, looking for places where you SELECT data from the users table.
It's hard to write a well-encapsulated program using functions, because the only way to signal to people "Don't touch this!" is through comments and programming conventions.
Objects allow you to wall off implementation internals from outside access. This prevents people from relying on code that may change and forces them to use your functions to reach the data. Functions of this type are known as accessors, because they allow access to otherwise protected information. When redesigning code, if you update the accessors to work as before, none of the code will break.
More information on encapsulation appears later, but first, here's an introduction to using objects in PHP 5.
Typically, objects represent "real-world" or tangible entities, such as a person. Here is one version of a Person object in PHP:
$rasmus = new Person; $rasmus->setName('Rasmus Lerdorf'); print $rasmus->getName( ); Rasmus Lerdorf
The first line assigns a value to a $rasmus variable. This value is an object of type Person. Person is a previously specified structure containing code that describes how a "Person object" should operate. This structure is called a class.
The difference between an object and a class is that an object is an actual variable that you can manipulate. You can pass it to functions, delete it, copy it, and so forth. It holds a specific set of data.
A class is the template that defines how the object can be used and what data it can hold.
Convert a class into an object by using the new keyword:
$rasmus = new Person;
This command causes PHP to look for a class labeled as Person, create a new copy, and assign it to $rasmus. This process is known as instantiating an object or creating a new instance of a class.
For now, don't worry about the actual syntax for defining Person. Also, it's not necessary to know how Person stores data. This information is encapsulated away, and you're forced to work without it. (And this is a good thing!)
What you do need to know is that Person allows you to call something that looks like a function called setName( ):
$rasmus->setName('Rasmus Lerdorf');
When you define a class, you can specify functions that belong to the class. To call an object's function, place an arrow (->) after the object and then add the function name. This tells PHP to invoke the setName( ) function on that specific instance of the class.
The proper term for setName( ) isn't "function"; it's a method or an object method. Here's how you use the term in a full sentence: "I called the setName( ) method on the object," or "You must call the object's setName( ) method."
The setName( ) method sets the "name" attribute of $rasmus. In this case, it's set to Rasmus Lerdorf.
You can retrieve this value by calling getName( ):
print $rasmus->getName( ); Rasmus Lerdorf
The getName( ) method looks up the value stored by an earlier call to setName( ) and returns it. Because of encapsulation, you don't know how Person stores this data?the specific details are irrelevant.
In object-oriented programming, there is an implicit contract between the class's author and the users of the class. The users agree not to worry about the implementation details. The author agrees that as long as a person uses accessor methods, such as setName( ) and getName( ), they'll always work, even if the author redesigns the class.
The full details on class creation come later, but here's a first look at the elements of a simple class. For example, the Person class can look like this:
class Person { setName($name) { $this->name = $name; } getName( ) { return $this->name; } }
You define and name a class like you define and name a function, except that you use the word class instead of function, and parentheses (( )) don't follow the class name.
Inside the class, declare class methods like you declare regular functions:
function setName($name) { $this->name = $name; } function getName( ) { return $this->name; }
These two methods store and return a name using a special class variable called $this. That's why $rasmus->getName( ) is able to remember and return the value passed in by $rasmus->setName('Rasmus Lerdorf').
That's all for now on writing classes. It's time to go back to using classes and objects.
In PHP 5, you can call a method on an object returned by a function:
function getRasmus( ) { $rasmus = new Person; $rasmus->setName('Rasmus Lerdorf'); return $rasmus; } print getRasmus( )->getName( ); Rasmus Lerdorf
This isn't possible in PHP 4. Instead, you store the object in a temporary variable as an intermediate step:
function getRasmus( ) { $rasmus = new Person; $rasmus->setName('Rasmus Lerdorf'); return $rasmus; } $rasmus = getRasmus( ) print $rasmus->getName( );
Calling setName( ) on different objects causes that method to operate on a different set of data. Each instance operates independently of every other instance, even if they're of the same class.
$rasmus = new Person; $zeev = new Person; $rasmus->setName('Rasmus Lerdorf'); $zeev->setName('Zeev Suraski'); print $rasmus->getName( ); print $zeev->getName( ); Rasmus Lerdorf Zeev Suraski
This example creates two instances of the Person class, $rasmus and $zeev. These objects are separate, so the call $zeev->setName('Zeev Suraski'); doesn't undo the earlier call of $rasmus->setName('Rasmus Lerdorf');.
Besides methods, objects also have properties. A property is to an object as an element is to an array. You refer to it by a name and can store in it every type of data: strings, arrays, and even other objects.
The syntax for property access is like method access, except that there are no parentheses following the property name:
$rasmus = new Person; $rasmus->name = 'Rasmus Lerdorf'; print $rasmus->name; Rasmus Lerdorf
This assigns the string Rasmus Lerdorf to the name property of $rasmus. Then, it retrieves the string and prints it out. An object with only properties and no methods is more or less a fancy array.
When you attempt to instantiate a class that's not defined, PHP 4 dies with a fatal error because it can't locate what you're looking for. PHP 5 solves this problem by loading the missing code on the fly with its new autoload feature.
Extensive use of classes requires you to either define all your classes in a single file or else place an include statement for each class you use at the top of every script. Since PHP 5 calls _ _autoload( ) when you instantiate undefined classes, you can make it include all the classes used by your script with only a little work:
function _ _autoload($class_name) { include "$class_name.php"; } $person = new Person;
The _ _autoload( ) function receives the class name as its single parameter. This example appends a .php extension to that name and tries to include a file based on $class_name. So, when you instantiate a new Person, it looks for Person.php in your include_path.
If you adopt the PEAR-style naming convention of placing an underscore between words to reflect the file hierarchy, use the code in Example 2-1.
function _ _autoload($package_name) { // split on underscore $folders = split('_', $package_name); // rejoin based on directory structure // use DIRECTORY_SEPARATOR constant to work on all platforms $path = join(DIRECTORY_SEPARATOR, $folders); // append extension $path .= '.php'; include $path; }
With the code in Example 2-1, you can do the following:
$person = new Animals_Person;
If the class isn't defined, Animals_Person gets passed to _ _autoload( ). The function splits the class name on underscore ( _ ) and joins it on DIRECTORY_SEPARATOR. This turns the string into Animals/Person on Unix machines (and Animals\Person on Windows).
Next, a .php extension is appended, and then the file Animals/Person.php is included for use.
While using _ _autoload( ) slightly increases processing time during the addition of a class, it is called only once per class. Multiple instances of the same class does not result in multiple calls to _ _autoload( ).
Although using properties instead of accessor methods is less work up front, it's not a good idea, because it reduces encapsulation. Reading and writing to name directly instead of calling setName( ) and getName( ) undermines the layer of abstraction that prevents code from breaking after redesigns. Since this is a big benefit of object-oriented programming, you should avoid using properties instead of accessor methods.
PHP 5 allows you to enforce the distinction between what should and shouldn't be accessed directly. All of the methods and properties shown so far have been public methods and properties. This means anyone can call or edit them.
In PHP 4, all properties and methods are public. In PHP 5, however, you can use the private label to restrict access to only those methods defined inside the class. When this label is applied to a method or property, it is known as private. Marking something as private signals that it may change in the future, so people shouldn't access it or they'll violate encapsulation.
This is more than a social convention. PHP 5 actually prevents people from calling a private method or reading a private property outside of the class. Therefore, from an external perspective, these methods and properties might as well not exist because there's no way to access them. More information on access control appears in Section 2.3.3, later in this chapter.
Objects in PHP 5 also have the ability to call constructors and destructors. A constructor is a method that is called automatically when an object is instantiated. Depending upon how the constructor is implemented, you may be able to pass it arguments.
For example, a constructor for a class that represents a database may take the address of the database you wish to connect to, as well as the username and password necessary for authentication:
$db = new Database('db.example.com', 'web', 'jsd6w@2d');
This creates an new instance of a Database class and passes three pieces of information to the constructor. The class's constructor will use that data to create a connection to the database and then store the result handle in a private property.
PHP 4 has object constructors, but object destructors are new to PHP 5. Destructors are like constructors, except that they're called when the object is deleted. Even if you don't delete the object yourself using unset( ), PHP 5 still calls the destructor when it determines that the object is no longer used. This may be when the script ends, but it can be much earlier.
You use a destructor to clean up after an object. For instance, the Database destructor would disconnect from the database and free up the connection. Unlike constructors, you cannot pass information to a destructor, because you're never sure when it's going to be run.