C H A P T E R  1

Object Orientation

The purpose of this chapter is to introduce the basic concepts of object orientation. What does it actually mean to say, “PHP is object oriented?” The simplest answer is that PHP allows for the definition and hierarchical organization of user data types. This book is about PHP 5.3, which introduced some new elements to PHP object apparatus. PHP underwent a fairly radical change since the version 4, which also included rudimentary object-oriented (OO) capabilities. In PHP 4, for instance, it wasn't possible to define visibility of methods and members. In PHP 5.3, namespaces were added.

In this chapter we will introduce the ideas of classes, inheritance, object creation, and interface definition. We will also introduce some less elementary stuff, like iterators. So, let's get started.

Classes

Classes are simply user defined types. In OO languages, a class serves as a template for creating objects or instances (functional copies) of that class. A class contains the description of the common characteristics of all items belonging to it. The purpose of a class (or classes) is to encapsulate object definition and behavior, and to hide its actual implementation from the end user and to enable the end user to employ the class objects in the documented and expected way. Encapsulation also makes programs smaller and more manageable, because the objects already contain the logic needed to handle them. There is also a feature called autoloading that helps with breaking scripts into smaller, more manageable pieces.

Before we see a simple example of a PHP class, let's introduce some more terminology:

  • Class member or property: A variable, data part of the class
  • Class method: A function defined within a class

Now we will define a class for a point in a 2-dimensional plane, defined with its Cartesian coordinates (see Listing 1-1). As it is designed purely for instructional purposes, this class has several serious drawbacks. We recommend that you don't use it as a code base for any code of your own.

Listing 1-1. A 2D Plane

<?php
class Point {
    public $x;
    public $y;

    function __construct($x,$y) {
        $this->x=$x;
        $this->y=$y;
    }
    function get_x() {
        return($this->x);
    }
    function get_y() {
        return($this->y);
    }
    function dist($p) {
        return(sqrt( pow($this->x-$p->get_x(),2)+
                     pow($this->y-$p->get_y(),2)));
    }
} // Class ends here
$p1=new Point(2,3);
$p2=new Point(3,4);
echo $p1->dist($p2)," ";
$p2->x=5;
echo $p1->dist($p2)," ";
?>

This class is not trivial; there are quite a few things to analyze and fix. First, as we have previously stated, this class describes a point in a plane, defined by its Cartesian coordinates, $x and $y. There is a keyword public to which we will return later. There is also a constructor method __construct, which is called when a new object (or instance) of the class Point is created in memory by invoking the operator new. In other words, when the line $p1=new Point(2,3) is executed, the method __construct is automatically referenced and executed, and the arguments behind the class name, in parenthesis, are passed into the __construct method for possible use.

The method __construct references the variable $this. The variable $this is the OO way of referring to the class instance itself. It always refers to the current object in focus. It is an OO equivalent of “me.” A variant of this variable is present in almost all OO-based languages, although it is called “self” in some languages.

The class constructor is the method that initializes (instantiates) objects of the given class. In this particular case, it assigns coordinates. The coordinates (the variables named $x and $y) are members of this class. Several other methods are also defined, two get methods and a method called dist, which calculates the distance between two points.

The next thing to observe is the keyword public. Marking members as “public” allows the full access to the data members that are marked public. In our script, there is a line that reads $p2->x=5;. The x-coordinate of one of our points is being manipulated directly. Such access is impossible to control, and in all but the simplest cases is highly discouraged. Good practice is to write get and set methods that will read or write into class members in a controlled way. In other words, with get and set methods, it is possible to control the values of the data members. With public members, get and set functions are redundant, because it is possible to set the members directly, as in $p2->x=5. However, with the public members, it is not possible to control the value of the members. Set and get functions can be written directly, for each member, but PHP also provides so-called “magic methods” that can be used instead of having to write two functions for each member.

It is possible to protect the members much better, with the keywords private and protected. The exact meaning of these two keywords will be explained in the next section. It is also worth noting that public is the default visibility. If the visibility for a member or method is not specified, it defaults to public. Writing

class C {
$member;
       function method() {...}
….
}

is completely equivalent to writing :

 class C {
       public $member;
       pubic  function method() {…}
….
}

In contrast with the public class members, private class members or methods are only visible to the methods of the same class. Methods that are not connected to the class cannot access any of the private members, nor can they call any of the other private methods. If the keyword “public” is replaced with the keyword “private” for the class members $x and $y, and access is attempted, the result will be

PHP Fatal error:  Cannot access private property Point::$x in script2.1 on line 25

In other words, our neat little trick on line 25, which reads $p2->x=5, will no longer work. The constructor function has no problems whatsoever, and neither do functions get_x() and get_y(), as they are class members. This is a good thing, because it will no longer be possible to manipulate the class objects directly, potentially radically altering their behavior in a way that the class is not meant to do. In short, the class is more self-contained, like a controlled access highway – there are limited access and exit ramps.

Public and private members are now clear, but what are protected members and methods? Protected methods and members are accessible by the methods of the class they belong to, and by the methods of the classes that inherit from the base class they belong to. We will take a closer look at this in the next section.

Inheritance and Overloading

As stated in the beginning of this chapter, classes can be organized in a hierarchical way. The hierarchy is established through inheritance. In order to demonstrate inheritance, let us develop another class called employee. Some of a company's employees are managers, which will be a class that inherits from the more general employee class. Inheritance is also known as specialization. So, without further ado, let's see the class (see Listing 1-2).

Listing 1-2. Example of employee Class

<?php
class employee {
    protected $ename;
    protected $sal;
   function __construct($ename, $sal = 100) {
        $this->ename = $ename;
        $this->sal = $sal;
    }

    function give_raise($amount) {
        $this->sal+= $amount;
        printf("Employee %s got raise of %d dollars ", $this->ename, $amount);
        printf("New salary is %d dollars ", $this->sal);
    }
    function __destruct() {
        printf("Good bye, cruel world: EMPLOYEE:%s ", $this->ename);
    }
}

class manager extends employee {
    protected $dept;
    function __construct($ename, $sal, $dept) {
        parent::__construct($ename, $sal);
        $this->dept = $dept;
    }
    function give_raise($amount) {
        parent::give_raise($amount);
        print "This employee is a manager ";
    }
    function __destruct() {
        printf("Good bye, cruel world: MANAGER:%s ", $this->ename);
        parent::__destruct();
    }
} // Class definition ends here.

$mgr = new manager("Smith", 300, 20);
$mgr->give_raise(50);
$emp = new employee("Johnson", 100);
$emp->give_raise(50);
?>

This class is just an artificial example; it is not meant to be used as a template. It is worth noticing that the __construct method is public in both classes. If it wasn't public, it wouldn't be possible to create new objects of either class. When executed, this script will produce the following result:

Employee Smith got raise of 50 dollars
New salary is 350 dollars
This employee is a manager
Employee Johnson got raise of 50 dollars
New salary is 150 dollars
Good bye, cruel world: EMPLOYEE:Johnson
Good bye, cruel world: MANAGER:Smith
Good bye, cruel world: EMPLOYEE:Smith

This little example is perfect for explaining the concept of inheritance. Every manager is an employee. Note the phrase “is a” characteristics for the inheritance relationship. In this case, class employee is the parent class for the class employee. Contrary to everyday life, a class in PHP can have only a single parent; multiple inheritance is not supported.

Furthermore, parent functions can be addressed using the parent:: construct shown in the class manager. When an object of the child class is created, the constructor for the parent class is not called automatically; it is the responsibility of the programmer to call it within the constructor of the child class.

The same applies to the destructor method. The destructor method is the exact opposite of the constructor method. Constructor is called when an object is being established in memory, while the destructor is called when the object is no longer needed, or when the “unset” function is explicitly called on that object. Explicitly calling the unset function is not a common practice; it is usually used to save memory. This also means that the destructor is automatically called for all objects when the script execution is finished. Destructor methods are usually used to clean up resources, such as closing open files or disconnecting from a database. Finally, note that the destructor method of our manager class has complete access to the member ename, despite the fact that it is actually a member of the employee class. That is precisely the purpose of the protected members. If ename was a private member of the employee class, our little example would not have worked.

The method get_raise exists in both classes. PHP knows which method to call for which object; this is one aspect of a fundamental principle of OO: encapsulation. Object $x belongs to the manager class and the give_raise method generated the output, “This employee is a manager,” after producing its normal output. We can rephrase this by saying that the give_raise function in the class manager overloads or supersedes the give_raise method in the employee class. Note that the meaning of the term “overload” in PHP is different from the meaning of the same term in C++ or Python, where it signifies a function (not a class method) with the same name but different argument types. Back to PHP: if the method is marked as final, it cannot be overloaded. If the method give_raise in the employee class was declared like this

final function give_raise($amount) {
….
}

overloading it in the class manager wouldn't be possible. We recommend that you try the basic OO concepts on this little script and play a bit by marking various members and methods private, protected, or public to see the results.

Finally, when speaking of inheritance, one also needs to mention abstract classes. Abstract classes cannot be instantiated; no objects belonging to them can be created. They're used primarily as templates, to force all classes that inherit from them to have the desired structure. The class is abstract if it is marked by the keyword “abstract,” like this:

abstract class A {
….
}

No object of this class can be created; PHP will throw a runtime error and stop the script execution. It is also possible to declare abstract methods of an abstract class. This is done like this

abstract class A {
abstract protected method(...);
}

This is done to force classes that extend the abstract class to implement the specified method.

Abstract classes are usually used as templates for the classes that extend them. A good example of the abstract classes can be found in the Standard PHP Library (SPL). Classes for the sorted heap (SplMinHeap, SplMaxHeap) extend the abstract class SplHeap and implement the compare method differently. SplMinHeap will sort the elements from the smallest to the largest, while SplMaxHeap will sort them the other way around. Common characteristics of both classes are contained in the abstract class SplHeap, which is documented here:

http://ca2.php.net/manual/en/class.splheap.php

Rather than inventing an artificial example of abstract classes, let's see how they are used in SPL. Here is a brief example of how to use the SplMinHeap class

<?php
$heap = new SplMinHeap();
$heap->insert('Peter'),
$heap->insert('Adam'),
$heap->insert('Mladen'),
foreach ($heap as $h) {
    print "$h ";
}
?>

When executed, the output will be:

Adam
Mladen
Peter

Names are sorted in alphabetic order–not quite the way they were inserted into the heap. Later we will see how is it possible to use an object of the SplMaxHeap class in a loop, as if it was an array.

Now, let's turn our attention to more practical OO programming techniques. For example, you may have been wondering how we make classes available to PHP scripts. Classes are usually written to be re-used over and over again. The obvious answer is that we create separate files which we can then include with require or include directives, but that can soon get awkward or cumbersome as the files multiply. It turns out that PHP has a tool to help with precisely that problem–namely, the function called __autoload. That function takes a class name as an argument and will be called whenever PHP cannot find a class definition in the currently executing script. Essentially, the __autoload function is a trap handler for a “class not found” exception error. We will get back to the exceptions later. Our example in Listing 1-2 can now be rewritten in two files (see Listing 1-3).

Listing 1-3. Listing 1-2 Rewritten in Two Files

File script1.3.php:
<?php
function __autoload ($class) {
    require_once("ACME$class.php");
}

$x = new manager("Smith", 300, 20);
$x->give_raise(50);
$y = new employee("Johnson", 100);
$y->give_raise(50);
?>

File ACMEmanager.php:

<?php
class employee {
    protected $ename;
    protected $sal;
    // Note that constructor is always public. If it isn't, new objects cannot
    // be created.
    function __construct($ename, $sal = 100) {
        $this->ename = $ename;
        $this->sal = $sal;
    }
    function give_raise($amount) {
        $this->sal+= $amount;
        printf("Employee %s got raise of %d dollars ", $this->ename, $amount);
        printf("New salary is %d dollars ", $this->sal);
    }
    function __destruct() {
        printf("Good bye, cruel world: EMPLOYEE:%s ", $this->ename);
    }
} // End of class "employee"

class manager extends employee {
    protected $dept;
    function __construct($ename, $sal, $dept) {
        parent::__construct($ename, $sal);
        $this->dept = $dept;
    }
    function give_raise($amount) {
        parent::give_raise($amount);
        print "This employee is a manager ";
    }
    function __destruct() {
        printf("Good bye, cruel world: MANAGER:%s ", $this->ename);
        parent::__destruct();
    }
} // End of class "manager"

This code is completely equivalent to the original script1.2.php in Listing 1-2, except it is much easier to read, because the most important part is contained in the file script1.3.php in Listing 1-3. The other file, ACMEmanager.php, only contains the class declarations. If we're not really interested in the internals of the class declarations, we don't have to read them; we only have to know how the objects of the declared classes behave. Also, note that the file is named after the first class that is being instantiated. When that file is loaded, the class employee will also be defined, because the definition of both classes is in the same file.

The second thing to note is that the class name was prefixed by “ACME.” This is to alert the reader of the possibility of creating specialized, per-project class libraries. The function __autoload implemented here uses require_once instead of an include directive. The reason for that is the behavior of PHP, which will terminate the script if a file requested by the require directive is not available. The execution will proceed if the file simply included by an include directive is not available. Executing a script that depends on class definitions without those definitions being available doesn't make much sense.

Also, class definition files should not have a trailing ?> defined. This is because they can often be autoloaded or included in a “header” file before the page is assembled, and any extra white space between the ?> and EOF will be injected into the html output stream of the page at the start. PHP is quite happy not to have the trailing ?>, omitting it is a best practice. This is probably the single biggest cause of “output already started” errors when using the header() function to send HTTP headers back to the browser, and for the unaware it is a death trap.

Miscellaneous “Magic” Methods

Most of the methods that are collectively called “magic methods” deal with the missing members and methods that are not defined in the class itself. The reason for that is a widely adopted practice of defining members in an associative array instead of defining them as separate class variables. That way of defining the data members is easy to follow, extend and modify, which has contributed to the popularity of defining an array of class members. Without the “magic functions,” it wouldn't be possible to access those members in a transparent and understandable way.

The first pair of special methods that deserves mentioning consists of __get and __set methods.

The __get and __set Methods

Method __set is called when a value is assigned to a non-existing member. Method __get is called when access is attempted to a non-existing member. Listing 1-4 is an example.

Listing 1-4. Example of _set and _get Methods

# Demonstration of __get and __set functions.
# Non-existing property "speed_limit" is being set and read.
<?php
class test1 {
    protected $members = array();
    public function __get($arg) {
        if (array_key_exists($arg, $this->members)) {
            return ($this->members[$arg]);
        } else { return ("No such luck! "); }
    }
    public function __set($key, $val) {
        $this->members[$key] = $val;
    }
    public function __isset($arg) {
        return (isset($this->members[$arg]));
    }
}
$x = new test1();
print $x->speed_limit;
$x->speed_limit = "65 MPH ";
if (isset($x->speed_limit)) {
    printf("Speed limit is set to %s ", $x->speed_limit);
}
$x->speed_limit = NULL;
if (empty($x->speed_limit)) {
    print "The method __isset() was called. ";
} else {
    print "The __isset() method wasn't called. ";
}
?>

When executed, this script will produce the following result:

No such luck!
Speed limit is set to 65 MPH
The method __isset() was called.

Class member speed_limit is not defined but referencing it did not produce an error because the __get method was executed when we referenced non-existing member and the __set method was executed when an assignment was made to a non-existing member. It is a frequent practice to have all members (or properties) defined in an associative array and refer to them as if they were defined as separate class properties. That practice makes classes easier to extend.

The __isset Method

In addition to the __get and __set methods, Listing 1-4 also demonstrates the use of the __isset function, which is used for examining whether a non-existing property, usually defined as an array element, is set (has a value). Of course, there's also the __unset function. It is called when unset is called on a non-existing property. The __isset method is also called when checking whether the variable is empty by using the empty() function. The empty() function tests whether the argument is set, and whether its length is greater than 0. It returns true if the argument is not set or if its length is equal to zero; otherwise, it returns false. In particular, when the argument is set to an empty string, the empty() function will also return true.

The __call method

Finally, when talking about non-existing members, the __call function gets called when a non-existing method is called. In my experience, that method is used relatively rarely, but Listing 1-5 is a little example, just for completeness.

Listing 1-5. The __call Function Is Called When a Non-existing Method Is Called.

<?php
# Demonstrating the use of "__call" method
class test2 {
    function __call($name, $argv) {
        print "name:$name ";
        foreach ($argv as $a) {
            print " $a ";
        }
    }
}
$x = new test2();
$x->non_existing_method(1, 2, 3);
?>

When executed, that script will produce the following result:

name:non_existing_method
        1
        2
        3

The method non_existing_method is, of course, not defined, but the call succeeds, nevertheless.

The __toString() method

The last “magic” method that will be mentioned here, and the only one that has nothing to do with the non-existing members or methods, is __toString(). It is used when an object is converted to string – either explicitly, by casting it using the explicit cast (string), or implicitly, by passing it as an argument to a function that expects string arguments, like “print.” Listing 1-6 is an example.

Listing 1-6. An Example of the _toString() Method
<?php
# Demonstrating the use of "__toString" method
class test2 {
    protected $memb;
    function __construct($memb) {
        $this->memb = $memb;
    }
    function __toString() {
        return ("test2 member. ");
    }
}
$x = new test2(1);
print $x;
?>

When executed, this script will produce the following output:

test2 member.

The return value of the __toString function is printed. This function is called when the underlying object is used in the string context. This function is really useful when one needs to print complex objects consisting of strange members, such as network or database connections or other binary objects.

Copying, Cloning, and Comparing Objects

In the beginning of this chapter, I discussed what classes were and how to create and handle complex objects. Now it is time to discuss some aspects of the internal object handling. When an object is created using a statement such as $x=new class(....), the variable $x is a reference to the object. What happens when we execute something like $x=$y? It's very simple: the original object pointed to by the handle $x is thrown away, with its destructor called, and $x is made to point to the object $y. Listing 1-7 is a little script that demonstrates the behavior.

Listing 1-7. When Executing $x=$y

<?php
# Demonstrating shallow copy.
class test3 {
    protected $memb;

    function __construct($memb) {
        $this->memb = $memb;
    }
    function __destruct() {
        printf("Destroying object %s... ", $this->memb);
    }
}
$x = new test3("object 1");
$y = new test3("object 2");
print "Assignment taking place: ";
$x = $y;
print "End of the script ";
?>

When this script is executed, it produces the following output:

Assignment taking place:
Destroying object object 1...
End of the script
Destroying object object 2...

Object 1 is destroyed during the assignment, when $x=$y is executed. Why did object 2 get destroyed? The answer to that is very simple: the destructor is called whenever an object goes out of scope. When the script is done, all surviving objects will go out of scope, and destructor will be called for each and every one of them. That is also the reason for enclosing the assignment between two print commands. Also, please note that destructor is executed only once, despite the fact that there are two references to the underlying object, $x and $y. Destructor is called once per object, not once per reference. This way of copying objects is called “shallow” copy, because no real copy of the object is ever created; only the reference is changed.

In addition to the previously seen “shallow” copy, there is also a “deep” copy, resulting in a new object. This “deep” copying is accomplished by using the “clone” operator, as shown in Listing 1-8.

Listing 1-8. Deep Copy Using the Clone Operator

<?php
# Demonstrating deep copy.
class test3a {
    protected $memb;
    protected $copies;
    function __construct($memb, $copies = 0) {
        $this->memb = $memb;
        $this->copies = $copies;
    }
    function __destruct() {
        printf("Destroying object %s... ", $this->memb);
    }
    function __clone() {
        $this->memb.= ":CLONE";
        $this->copies++;
    }

    function get_copies() {
        printf("Object %s has %d copies. ", $this->memb, $this->copies);
    }
}
$x = new test3a("object 1");
$x->get_copies();
$y = new test3a("object 2");
$x = clone $y;
$x->get_copies();
$y->get_copies();
print "End of the script, executing destructor(s). ";
?>

The deep copying is done on the line which says $x = clone $y. When that line is executed, a new copy of the object $y is created, and the function __clone is called in order to help arrange the new copy the way the script needs it. The output of this script looks like this:

Object object 1 has 0 copies.
Destroying object object 1...
Object object 2:CLONE has 1 copies.
Object object 2 has 0 copies.
End of the script, executing destructor(s).
Destroying object object 2...
Destroying object object 2:CLONE...

The newly created copy residing in $x has a member value “Object object 2:CLONE” and the number of copies set to 1, as a result of the actions of the __clone method. Also, please note that the constructor was executed twice, once for the original and once for the clone. Cloning is not used as frequently as the assignment by reference, but it is nice to have that possibility when needed.

How are objects compared? There are several cases to consider, depending on the comparison criteria. When exactly do we call two object variables $x and $y “equal?” There are the following three equally logically valid possibilities:

  • Objects of the same class have all members equal.
  • Objects are references to the same object of the same class.
  • Some other custom-made criteria are used.

The standard equality operator, == tests for the first possibility. The expression $x==$y holds true if and only if the corresponding members of $x and $y are equal to each other.

The second possibility, that $x and $y are references to the same object, is tested by the special operator === (3 consecutive equal signs). The expression $x===$y holds true if and only if both $x and $y are references to the same object. Note that the usual assignment, like $x=$y, will have the expression $x===$y return true, while cloning will break the equality. If there is no custom __clone method, the original and the clone will be equal, with equality defined as for the == operator.

What can we do about the third possibility, a custom definition of equality? Well, in that case, we have to write a custom function and compare the returned values. When writing functions that take arguments of a specific class, it is possible to force the arguments to be of the required type by listing the argument type in front of the formal argument name. That would look like this:

function test_funct(test3a $a) {….}

In this case, we require the argument $a to be of the type test3a. This can only be done for object type and for arrays, by entering the keyword array instead of the object name. PHP5 is still a weakly typed language and forcing the argument types with the classic types, like int is not supported.

Interfaces, Iterators, and Abstract Classes

Another classic type of object in the OO world is an interface. An interface is an object that describes a set of methods that a class may choose to implement. An interface looks like this:

interface interf {
  public function f1($x,$y,...,);
  public function f2(....);
  ….
  public function fn(...);
}

Note that there is no specification of the method code, just the name and the number of arguments. A class can choose to implement an interface, like this:

class c extends parent implements interf {
(all functions listed in the interface must now be defined)

)

Interfaces can inherit from one another, just like classes do. The syntax is also identical:

interface interf2 extends interf1 {
   function f1(...);
}

The new interface interf2 will contain all the functions from the interface interf1, plus the new ones, defined by interf2. Listing 1-9 is an example.

Listing 1-9. Example of the New Interface interf2

<?php
interface i1 {
    public function f1($a);
}   
interface i2 extends i1 {
    public function f2($a);
}
class c1 implements i2 {
    private $memb;
    function __construct($memb) {
        $this->memb = $memb;
    }
    function f2($x) {
        printf("Calling F2 on %s with arg: %s ", $this->memb, $x);
    }
}

$x = new c1("test");
$x->f2('a'),

When this script is executed, an error is produced because the function f1 from the interface i1 was not defined the erroneous output follows:

Fatal error: Class c1 contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (i1::f1) in /home/mgogala/work/book/script2.6.php on line 17

Interfaces are standard structures in Java programming, and are somewhat less common in a scripting language such as PHP. The example we're about to see is about the interface Iterator, which is an integral part of the PHP language. An iterator is an object of a class that implements the internal PHP interface called Iterator. Interface Iterator is defined as follows:

interface Iterator {
    public function rewind();        // Returns the iterator the beginning
    public function next();          // Get to the next member
    public function key();           // Get the key of the current object.
    public function current();       // Get the value of the current object
    public function valid();         // Is the current index valid?
}

Any class that implements the interface Iterator can be used in for loops, and its objects are called iterators. Listing 1-10 is an example.

Listing 1-10. Any Class That Implements the Interface Iterator Can Be Used in for Loops

<?php
class iter implements iterator {
    private $items;
    private $index = 0;
    function __construct(array $items) {
        $this->items = $items;
    }
    function rewind() {
        $this->index = 0;
    }
    function current() {
        return ($this->items[$this->index]);
    }
    function key() {
        return ($this->index);
    }
    function next() {
        $this->index++;
        if (isset($this->items[$this->index])) {
            return ($this->items[$this->index]);
        } else {
            return (NULL);
        }
    }

    function valid() {
        return (isset($this->items[$this->index]));
    }
}
$x = new iter(range('A', 'D'));
foreach ($x as $key => $val) {
    print "key=$key value=$val ";
}

This is a very simple, albeit a very typical, example of a PHP iterator. When executed, this script produces the following output:

key=0   value=A
key=1   value=B
key=2   value=C
key=3   value=D

The main part of the script, the part that was the cause of the whole exercise, is the loop at the very bottom. This syntax is normally used for arrays, but $x isn't an array, it's an object of the class iter. Iterators are objects that can behave as arrays. That is achieved by implementing the interface Iterator. In what type of situations is that applicable? Lines of a file or rows returned from a cursor can easily be iterated through. Note that we still cannot use the expression $x[$index]; the counting variable is only used to advance through the array. It is a fairly trivial exercise to do it by implementing the Iterator interface. Listing 1-11 is an example.

Listing 1-11. Implementing the Interface Iterator

<?php
class file_iter implements iterator {
    private $fp;
    private $index = 0;
    private $line;
    function __construct($name) {
        $fp = fopen($name, "r");
        if (!$fp) {
            die("Cannot open $name for reading. ");
        }
        $this->fp = $fp;
        $this->line = rtrim(fgets($this->fp), " ");
    }
    function rewind() {
        $this->index = 0;
        rewind($this->fp);
        $this->line = rtrim(fgets($this->fp), " ");
    }
    function current() {
        return ($this->line);
    }
    function key() {
        return ($this->index);
    }

    function next() {
        $this->index++;
        $this->line = rtrim(fgets($this->fp), " ");
        if (!feof($this->fp)) {
            return ($this->line);
        } else {
            return (NULL);
        }
    }
    function valid() {
        return (feof($this->fp) ? FALSE : TRUE);
    }
}
$x = new file_iter("qbf.txt");
foreach ($x as $lineno => $val) {
    print "$lineno: $val ";
}

The “qbf.txt” file is a small text file containing the famous pangram, the phrase that contains all letters of the alphabet:

quick brown fox
jumps over
the lazy dog

This script will read that file and print it out on the screen, each line preceded by the line number. It uses the normal file operations like fopen, fgets, and rewind. The .rewind function is not just a method name in the iterator interface; it is also a core function for the file manipulation. It modifies the file handle to point back to the beginning of the file.

Line numbers start from 0, to make files as similar to arrays as possible. So far, we've seen files and arrays being turned into iterators. Any type of entity that has “get next” and “am I done?” methods can be represented by an iterator structure and looped through. One such example is a .database cursor. It has a “get next” method, known as “fetch” and it is also able to tell when the last record is retrieved, by using the handle status. Implementation of the iterator class for the database cursor is very similar to what was done for files in Listing 1-11. This file_iter class is just an example. PHP5 contains a set of internal classes named the Standard PHP Library, similar to the STL of C++ fame. A much more elaborate class belonging to SPL is SplFileObject, and yes, it does implement the iterator class. Our entire script could have been written more simply like this:

<?php
$x = new SplFileObject("qbf.txt","r");
foreach ($x as $lineno => $val) {
    if (!empty($val)) {print "$lineno: $val"; }
}
?>

Note that the new line characters are no longer stripped from the line and that we have to test the lines for being empty. The SplFileObject class would advance past the end of file, should we neglect to test for the empty lines. It is, however, still a class that makes life much easier. The only really useful function that is missing from SplFileClass is fputcsv, the function that outputs arrays in CSV format. It is, however, easy enough to write.

There are other useful classes and interfaces in the SPL. The full description of the SPL is beyond the scope of this book, but the interested reader can find the documentation here:

www.php.net/manual/en/book.spl.php

There is also a standard set of classes that implements iterator for the database cursors and queries. :This set of classes is called ADOdb, and it enables the programmer to loop through a query result using the foreach loop, just like through a file or an array. The ADOdb set of classes will be covered in more detail later in this book.

What is the difference between an abstract class and an interface? Both are used as templates for other classes inheriting from them or implementing them, but an abstract class is much more stringent and defines the structure in a much stricter sense. In addition to abstract methods, an abstract class can have real members and methods – even final methods, which cannot be overloaded but can only be used as is.

Class Scope and Static Members

So far, we've only been working with members and methods, which were defined in the object scope; each object had its own, separate members and methods. There are also members and methods that exist in class scope, which means that they're common to all objects of the class. The problem that we're trying to solve is the following: how could we count the objects of a particular class that were created in a script? We obviously need a counter that will be class specific, rather than object specific. Variables and methods that are declared in class scope, rather than the scope of each object, are called static variables. Figure 1-12 is an example.

Listing 1-12. Example of Static Variables

<?php
class test4 {
    private static $objcnt;
    function __construct() {
        ++self::$objcnt;
    }
    function get_objcnt() {
        return (test4::$objcnt);
    }
    function bad() {
        return($this->objcnt);
    }
}
$x = new test4();
printf("X: %d object was created ", $x->get_objcnt());
$y = new test4();
printf("Y: %d objects were created ", $y->get_objcnt());
print "Let's revisit the variable x: ";
printf("X: %d objects were created ", $x->get_objcnt());
print "When called as object property, PHP will invent a new member of X... ";
printf("and initialize it to:%d ", $x->bad());
?>

When this script is executed, the output looks like this:

X: 1 object was created
Y: 2 objects were created
Let's revisit the variable x:
X: 2 objects were created
When called as object property, PHP will invent a new member of X...
and initialize it to:0

The variable test4:$objcnt is a static variable that exists in the class scope. When it was incremented to 2, during the creation of $y, the change was also visible in the $x. If, on the other hand, the attempt is made to access it as an object property, as in the function bad, PHP will invent a new and public object property and call it objcnt. Life has just become much more confusing. The fact that an object is declared static, in class scope, doesn't have anything to do with its visibility. There can be public, private, and protected static objects, with the same restrictions as for the normal methods and members. Also, please note that the same variable was called self::$objcnt in constructor while it was called test4::$objcnt. The keyword self is an abbreviation for “this class,” but it always refers to the class in which it was defined. In other words, self doesn't propagate with inheritance, it stays the same. Listing 1-13 is a little example.

Listing 1-13. The Keyword self Always Refers to the Class in Which It Was Defined

<?php
class A {
    protected static $prop = 2;
    function __construct() {
        self::$prop*= 2;
    }
    function get_prop() {
        return (self::$prop);
    }
}
class B extends A {
    protected static $prop = 3;
    function __construct() {
        self::$prop*= 3;
    }
    #    function get_prop() {
    #        return(self::$prop);
    #    }
    
}
$x = new A();
$y = new B();
printf("A:%d ", $x->get_prop());
printf("B:%d ", $y->get_prop());
?>

If the code of the get_prop function in class B is commented out, both rows will print out number 4, as both functions will be called in the context of the class A. If the get_prop function in the class be is uncommented, the line that reads printf("B:%d ", $y->get_prop()); will print number 9. My personal preference is to always call the class variables with the proper class name. It reduces confusion and makes the code instantly more readable.

In addition to static members, there are static methods, too. They are also called in the class context: class::static_method(...). It is important to note that there is no serialization of any kind; it's the sole responsibility of the user.

Summary

In this chapter, you learned quite a lot about PHP classes and objects. You should now be familiar with the concepts of classes, methods, and members, as well as constructors, destructors, inheritance, overloading, interfaces, abstract classes, static methods, and iterators. This chapter is by no means a complete reference of the PHP5 object features, but it covers the main points and should give you a solid foundation on which to build. The official documentation at www.php.net is an excellent resource, and covers everything that has been omitted from this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.103.219