title besides title

 
Showing posts with label PHP - Classes and Objects. Show all posts
Showing posts with label PHP - Classes and Objects. Show all posts

Friday, November 30, 2012

PHP : Classes & Objects - [7.1] Introduction


At first, PHP wasn't an object-oriented (OO) language. As it evolved, more and more object-oriented features appeared. First, you could define classes, but there were no constructors. Then, constructors appeared, but there were no destructors. Slowly but surely, as more people began to push the limits of PHP's syntax, additional features were added to satisfy the demand.
However, if you're the type of person who wishes PHP to be a true OO language, you'll probably be disappointed. At its heart, PHP is a procedural language. It isn't Java. But, if you're the type of person who wants to use some OO features in your code, PHP is probably right for you.
A class is a package containing two things: data and methods to access and modify that data. The data portion consists of variables; they're known as properties. The other part of a class is a set of functions that can alter a class' properties; they're called methods.
When we define a class, we don't define an object that can be accessed and manipulated. Instead, we define a template for an object. From this blueprint, we create malleable objects through a process known as instantiation . A program can have multiple objects of the same class, just as a person can have more than one book or many pieces of fruit.
Classes also live in a defined hierarchy. At the top of the chain, there is a generic class. In PHP, this class is named stdClass, for "standard class." Each class down the line is more specialized than its parent. For example, a parent class could be a building. Buildings can be further divided into residential and commercial. Residential buildings can be further subdivided into houses and apartment buildings, and so forth.
Both houses and apartment buildings have the same set of properties as all residential buildings, just as residential and commercial buildings share some things in common. When classes are used to express these parent-child relationships, the child class inherits the properties and methods defined in the parent class. This allows you to reuse the code from the parent class and requires you to write code only to adapt the new child to its specialized circumstances. This is called inheritance and is one of the major advantages of classes over functions. The process of defining a child class from a parent is known as subclassing or extending.
Objects play another role in PHP outside their traditional OO position. Since PHP can't use more than one namespace, the ability for a class to package multiple properties into a single object is extremely helpful. It allows clearly demarcated separate areas for variables.
Classes in PHP are easy to define and create:
class guest_book {
    var $comments;
    var $last_visitor;

    function update($comment, $visitor) {
        ...
    }

}

PHP : Classes & Objects - [7.2] Instantiating Objects


7.2.1 Problem

You want to create a new instance of an object.

7.2.2 Solution

Define the class, then use new to create an instance of the class:
class user {
    function load_info($username) {
       // load profile from database
    }
}

$user = new user;
$user->load_info($_REQUEST['username']);

7.2.3 Discussion

You can instantiate multiple instances of the same object:
$adam = new user;
$adam->load_info('adam');

$dave = new user;
$dave->load_info('adam');
These are two independent objects that happen to have identical information. They're like identical twins; they may start off the same, but they go on to live separate lives. 

PHP : Classes & Objects - [7.3] Defining Object Constructors


7.3.1 Problem

You want to define a method that is called when an object is instantiated. For example, you want to automatically load information from a database into an object when it's created.

7.3.2 Solution

Define a method with the same name as the class:
class user {
    function user($username, $password) {
        ...
    }
}

7.3.3 Discussion

If a function has the same name as its class, it acts as a constructor:
class user {
    var $username;

    function user($username, $password) { 
        if ($this->validate_user($username, $password)) {
            $this->username = $username;
        }
    }
}

$user = new user('Grif', 'Mistoffelees'); // using built-in constructor
PHP hasn't always had support for constructors. So people made pseudo-constructors by adopting a naming convention and calling that function after creation:
class user {
    ...

    init($username, $password) { ... }
}

$user = new user();
$user->init($username, $password);
If you see this, it's usually a result of legacy code.
However, having a standard name for all constructors makes it easier to call your parent's constructor (because you don't need to know the name of the parent class) and also doesn't require you to modify the constructor if you rename your class name. With Zend Engine 2, the naming conventions of constructors have been modified, and the new constructor name is _ _construct( ). However, for backwards compatibility, if this method isn't found, PHP tries to call a constructor with the same name as the class. 

PHP : Classes & Objects - [7.4] Destroying an Object


7.4.1 Problem

You want to eliminate an object.

7.4.2 Solution

Objects are automatically destroyed when a script terminates. To force the destruction of an object, use unset( ):
$car = new car; // buy new car
...
unset($car);    // car wreck

7.4.3 Discussion

It's not normally necessary to manually clean up objects, but if you have a large loop, unset( ) can help keep memory usage from spiraling out of control.
PHP 4 doesn't have destructors, however Zend Engine 2 supports them with the _ _destruct( ) method. 

PHP : Classes & Objects - [7.5] Cloning Objects


7.5.1 Problem

You want to make a copy of an existing object. For instance, you have an object containing a message posting and you want to copy it as the basis for a reply message.

7.5.2 Solution

Use = to assign the object to a new variable:
$rabbit = new rabbit;
$rabbit->eat();
$rabbit->hop();
$baby = $rabbit;

7.5.3 Discussion

In PHP, all that's needed to make a copy of an object is to assign it to a new variable. From then on, each instance of the object has an independent life and modifying one has no effect upon the other:
class person {
    var $name;

    function person ($name) {
        $this->name = $name;
    }
}

$adam = new person('adam');
print $adam->name;    // adam
$dave = $adam;
$dave->name = 'dave';
print $dave->name;    // dave
print $adam->name;    // still adam
Zend Engine 2 allows explicit object cloning via a _ _clone( ) method that is called whenever an object is copied. This provides more finely-grained control over exactly which properties are duplicated. 

PHP : Classes & Objects - [7.6] Assigning Object References


7.6.1 Problem

You want to link two objects, so when you update one, you also update the other.

7.6.2 Solution

Use =& to assign one object to another by reference:
$adam = new user;
$dave =& $adam;

7.6.3 Discussion

When you do an object assignment using =, you create a new copy of an object. So, modifying one doesn't alter the other. But when you use =&, the two objects point at each other, so any changes made in the first are also made in the second:
$adam = new user;
$adam->load_info('adam');

$dave =& $adam;
$dave->load_info('dave');
The values in $adam are equal to those of $dave. 

PHP : Classes & Objects - [7.7] Calling Methods on an Object Returned by Another Method


7.7.1 Problem

You need to call a method on an object returned by another method.

7.7.2 Solution

Assign the object to a temporary variable, and then call the method of that temporary variable:
$orange = $fruit->get('citrus');
$orange->peel( );

7.7.3 Discussion

This is necessary because a parse error results from:
$fruit->get('citrus')->peel( );
Zend Engine 2 supports direct dereferencing of objects returned from a method so this workaround is no longer necessary. 

PHP : Classes & Objects - [7.8] Accessing Overridden Methods


7.8.1 Problem

You want to access a method in the parent class that's been overridden in the child.

7.8.2 Solution

Prefix parent:: to the method name:
class shape {
    function draw( ) {
        // write to screen
    }
}

class circle extends shape {
   function draw($origin, $radius) {
      // validate data
      if ($radius > 0) {
          parent::draw( );
          return true;
      }

      return false;
   }
}

7.8.3 Discussion

When you override a parent method by defining one in the child, the parent method isn't called unless you explicitly reference it.
In the Solution, we override the draw( ) method in the child class, circle, because you want to accept circle specific parameters and validate the data. However, in this case, we still want to perform the generic shape::draw( ) action, which does the actual drawing, so we call parent::draw( ) inside your method if $radius is greater than 0.
Only code inside the class can use parent::. Calling parent::draw( ) from outside the class gets you a parse error. For example, if circle::draw( ) checked only the radius, but you also wanted to call shape::draw( ), this wouldn't work:[1]
[1] In fact, it fails with the error unexpected T_PAAMAYIM_NEKUDOTAYIM, which is Hebrew for "double-colon."
$circle = new circle;
if ($circle->draw($origin, $radius)) {
    $circle->parent::draw();
}
If you want to call the constructor belonging to an object's parent but don't know the parent's class name, use get_parent_class( ) to dynamically identify the parent, then combine that with parent:: to call the parent's constructor:
class circle extends shape {
    
    function circle( ) {
        $parent = get_parent_class($this);
        parent::$parent( );
    }
}
The function get_parent_class( ) takes a class name or an object and returns the name of the object's parent. In order to maintain generality, pass $this, which is the reference to the current object. In this case, the function returns shape. Then, use parent:: to ensure PHP explicitly calls the constructor in the parent class. Calling $parent( ) without parent:: runs the risk of calling a method in circle that overrides the parent definition.
The call to parent::$parent( ) may look a little odd. However, PHP just substitutes in the parent class name for the $parent variable. Then, because there are ( )s after the variable, PHP knows it should make a method call.
It's possible to hardcode the call to parent::shape( ) directly into the circle constructor:
function circle( ) {
    parent::shape( );
}
However, this isn't as flexible as using get_parent_class( ). It is faster, so if you know your object hierarchy isn't going to change, that may be a trade-off you can benefit from.
Last, you can't chain the parent:: keyword to work back to a "grandparent" class, so, parent::parent::foo( ) doesn't work. 

PHP : Classes & Objects - [7.9] Using Property Overloading


7.9.1 Problem

You want handler functions to execute whenever you read and write object properties. This lets you write generalized code to handle property access in your class.

7.9.2 Solution

Use the experimental overload extension and write _ _get( ) and _ _set( ) methods to intercept property requests.

7.9.3 Discussion

Property overloading allows you to seamlessly obscure from the user the actual location of your object's properties and the data structure you use to store them.
For example, the pc_user class shown in Example 7-1 stores variables in an array, $data.
Example 7-1. pc_user class
require_once 'DB.php';

class pc_user {

    var $data = array();

    function pc_user($user) {
        /* connect to database and load information on 
         * the user named $user into $this->data
         */
         
         $dsn = 'mysql://user:password@localhost/test';
         $dbh = DB::connect($dsn);
         if (DB::isError($dbh)) { die ($dbh->getMessage()); }

         $user = $dbh->quote($user);
         $sql = "SELECT name,email,age,gender FROM users WHERE user LIKE '$user'";
         if ($data = $dbh->getAssoc($sql)) {
             foreach($data as $key => $value) {
                 $this->data[$key] = $value;
             }
         }
    }

    function __get($property_name, &$property_value) {
        if (isset($this->data[$property_name])) {
            $property_value = $this->data[$property_name];
            return true;
        }

        return false;
    }

    function __set($property_name, $property_value) {
        $this->data[$property_name] = $property_value;
        return true;
    }
}
Here's how to use the pc_user class:
overload('pc_user');

$user = new pc_user('johnwood');
$name = $user->name;                // reads $user->data['name']
$user->email = 'jonathan@wopr.mil'; // sets  $user->data['email']
The class constructor connects to the users table in the database and retrieves information about the user named $user. When you set data, _ _set( ) rewrites the element inside of $data. Likewise, use _ _get( ) to trap the call and return the correct array element.
Using an array as the alternate variable storage source doesn't provide many benefits over a nonoverloaded object, but this feature isn't restricted to simple arrays. For instance, you can make $this->email return the get_name( ) method of an email object. You can also avoid pulling all the user information from the database at once and request it on demand. Another alternative is to use a more persistent storage mechanism, such as files, shared memory, or a database to hold data. 

PHP : Classes & Objects - [7.10] Using Method Polymorphism


7.10.1 Problem

You want to execute different code depending on the number and type of arguments passed to a method.

7.10.2 Solution

PHP doesn't support method polymorphism as a built-in feature. However, you can emulate it using various type-checking functions. The following combine( ) function uses is_numeric(), is_string(), is_array(), and is_bool():
// combine() adds numbers, concatenates strings, merges arrays,
// and ANDs bitwise and boolean arguments
function combine($a, $b) {
    if (is_numeric($a) && is_numeric($b)) {
        return $a + $b;
    }

    if (is_string($a)  && is_string($b))  {
        return "$a$b";
    }

    if (is_array($a)   && is_array($b))   {
        return array_merge($a, $b);
    }

    if (is_bool($a)    && is_bool($b))    {
        return $a & $b;
    }

    return false;
}

7.10.3 Discussion

Because PHP doesn't allow you to declare a variable's type in a method prototype, it can't conditionally execute a different method based on the method's signature, as can Java and C++. You can, instead, make one function and use a switch statement to manually recreate this feature.
For example, PHP lets you edit images using GD. It can be handy in an image class to be able to pass in either the location of the image (remote or local) or the handle PHP has assigned to an existing image stream. Example 7-2 shows a pc_Image class that does just that.

PHP : Classes & Objects - [7.11] Finding the Methods and Properties of an Object


7.11.1 Problem

You want to inspect an object to see what methods and properties it has, which lets you write code that works on any generic object, regardless of type.

7.11.2 Solution

Use get_class_methods( ) and get_class_vars( ) to probe an object for information:
// learn about cars
$car_methods = get_class_methods('car');
$car_vars    = get_class_vars('car');

// act on our knowledge
if (in_array('speed_away', $car_methods)) {
    $getaway_van = new car;
    $getaway_van->speed_away( );
}

7.11.3 Discussion

It's rare to have an object and be unable to examine the actual code to see how it's described. Still, these functions can be useful for projects you want to apply to a whole range of different classes, such as creating automated class documentation, generic object debuggers, and state savers, like serialize( ).
Both get_class_methods( ) and get_class_vars( ) return an array of values. In get_class_methods( ), the keys are numbers, and the values are the method names. For get_class_vars( ), both variable names and default values (assigned using var) are returned, with the variable name as the key and the default value, if any, as the value.
Another useful function is get_object_vars( ) . Unlike its sister function get_class_vars( ), get_object_vars( ) returns variable information about a specific instance of an object, instead of a generic newly created object.
As a result, you can use it to check the status of an object as it currently exists in a program:
$clunker = new car;
$clunker_vars = get_object_vars($clunker); // we pass the object, not the class
Since you want information about a specific object, you pass the object and not its class name. But, get_object_vars( ) returns information in the same format as get_class_vars( ).
This makes it easy to write quick scripts to see if you're adding new class variables:
$new_vars = array_diff(array_keys(get_object_vars($clunker)),
                       array_keys(get_class_vars('car')));
You extract the variable names using array_keys( ) . Then, with the help of array_diff( ), you find which variables are in the $clunker object that aren't defined in the car class.
If you just need a quick view at an object instance, and don't want to fiddle with get_class_vars( ), use either var_dump( ) , var_export( ), or print_r( ) to print the object's values. Each of these three functions prints out information in a slightly different way; var_export( ) can optionally return the information, instead of displaying it. 

PHP : Classes & Objects - [7.12] Adding Properties to a Base Object


7.12.1 Problem

You want to create an object and add properties to it, but you don't want to formally define it as a specific class. This is useful when you have a function that requires an object with certain properties, such as what's returned from mysql_fetch_object( ) or imap_header( ).

7.12.2 Solution

Use the built-in base class, stdClass:
$pickle = new stdClass;
$pickle->type = 'fullsour';

7.12.3 Discussion

Just as array( ) returns an empty array, creating an object of the type stdClass provides you with an object without properties or methods.
Like objects belonging to other classes, you can create new object properties, assign them values, and check those properties:
$guss = new stdClass;

$guss->location = 'Essex';
print "$guss->location\n";
$guss->location = 'Orchard';
print "$guss->location\n";
Essex
Orchard
Methods, however, can't be defined after an object is instantiated.
It is useful to create objects of stdClass when you have a function that takes a generic object, such as one returned from a database fetching function, but you don't want to actually make a database request. For example:
function pc_format_address($obj) {
    return "$obj->name <$obj->email>";
}

$sql = "SELECT name, email FROM users WHERE id=$id";
$dbh = mysql_query($sql);
$obj = mysql_fetch_object($dbh);
print pc_format_address($obj);
David Sklar <david@example.com>
The pc_print_address( ) function takes a name and email address and converts it to a format as you might see in the To and From fields in an email program. Here's how to call this function without calling mysql_fetch_object( ):
$obj = new stdClass;
$obj->name = 'Adam Trachtenberg';
$obj->email = 'adam@example.com';
print pc_format_address($obj);
Adam Trachtenberg <adam@example.com>

PHP : Classes & Objects - [7.13] Creating a Class Dynamically


Recipe 7.13 Creating a Class Dynamically

7.13.1 Problem

You want to create a class, but you don't know everything about it until your code is executed.

7.13.2 Solution

Use eval( ) with interpolated variables:
eval("class van extends $parent_class {
    function van() {
        \$this->$parent_class();
    }
};");

$mystery_machine = new van;

7.13.3 Discussion

While it's okay in PHP to use variable names to call functions or create objects, it's not okay to define functions and classes in a similar manner:
$van( );                     // okay
$van = new $parent_class    // okay
function $van( ) {};         // bad
class $parent_class {};     // bad
Trying to do either of the last two examples results in a parser error because PHP expects a string, and you supplied a variable.
So, if you want to make a class named $van and you don't know beforehand what's going to be stored in $van, you need to employ eval( ) to do your dirty work:
eval("class $van {};");
There is a performance hit whenever you call eval( ), so high traffic sites should try to restructure their code to avoid this technique when possible. Also, if you're defining your class based on input from users, be sure to escape any potentially dangerous characters. 

PHP : Classes & Objects - [7.14] Instantiating an Object Dynamically


7.14.1 Problem

You want to instantiate an object, but you don't know the name of the class until your code is executed. For example, you want to localize your site by creating an object belonging to a specific language. However, until the page is requested, you don't know which language to select.

7.14.2 Solution

Use a variable for your class name:
$language = $_REQUEST['language'];
$valid_langs = array('en_US' => 'US English', 
                     'en_GB' => 'British English', 
                     'es_US' => 'US Spanish',
                     'fr_CA' => 'Canadian French');

if (isset($valid_langs[$language]) && class_exists($language)) {
    $lang = new $language;
}

7.14.3 Discussion

Sometimes you may not know the class name you want to instantiate at run-time  but you know part of it. For instance, to provide your class hierarchy a pseudo-namespace, you may prefix a leading series of characters in front of all class names; this is why we often use pc_ to represent PHP Cookbook or PEAR uses Net_ before all Networking classes.
However, while this is legal PHP:
$class_name = 'Net_Ping';
$class = new $class_name;               // new Net_Ping
This is not:
$partial_class_name = 'Ping';
$class = new "Net_$partial_class_name"; // new Net_Ping
This, however, is okay:
$partial_class_name = 'Ping';
$class_prefix = 'Net_';

$class_name = "$class_prefix$partial_class_name";
$class = new $class_name;               // new Net_Ping
So, you can't instantiate an object when its class name is defined using variable concatenation in the same step. However, because you can use simple variable names, the solution is to preconcatenate the class name.