Object-oriented programming II in PHP

In this chapter of the PHP tutorial, we continue describing OOP in PHP.

The static keyword

We can declare class properties and methods to be static. The static properties and methods do not belong to the instance of the class. They belong to the class itself. They are accessible through the scope resolution operator ::.

staticmethod.php
<?php

class Sys {

    public static function println($string) {
    
        echo "$string\n";
    }
}

Sys::println("PHP");
Sys::println("PERL");
Sys::println("Python");
Sys::println("Pike");

?>

In the above PHP script, we have a static println() method. It prints a string and starts a new line. This example is inspired by the Java language.

Sys::println("PHP");

We do not need an object to call println() method. We call static methods by specifying the class name, followed by double colon operator and the method name.

$ php static1.php 
PHP
PERL
Python
Pike

This is the output of the script.

staticvariable.php
<?php

class Math {

    public static $PI = 3.14159265359;
}

echo Math::$PI . "\n";

?>

And now we have an example with a static variable.

echo Math::$PI . "\n";

We access the variable by specifying the class name, followed by the scope resolution operator and the variable name.

The final keyword

Final methods cannot be overridden and final classes cannot be extended. The final keyword is a matter of design of the application. Some classes should not be extended and some methods should not be overridden. This behaviour is enforced by the final keyword.

finalmethod.php
<?php

class Base {

    final function say() {
        echo "Base class";
    }
}

class Derived extends Base {

    function say() {
        echo "Derived class";
    }
}

?>

This PHP script won't compile. We get an error "Cannot override final method Base::say()".

finalclass.php
<?php

final class Math {

    static function getPI() {
        return 3.141592;
    }
}

class DerivedMath extends Math {

    function say() {
        echo "DerivedMath class";
    }
}

?>

In the previous PHP script, we have a prototype base Math class. The sole purpose of this class is to provide some helpful methods and constants to the programmer. (In our case we have only one method for simplicity reasons.) It is not created to be extended. To prevent uninformed other programmers to derive from this class, the creators made the class final. If you try to run this PHP script, you get the following error: "Fatal error: Class DerivedMath may not inherit from final class (Math)".

Deep copy vs shallow copy

Copying of data is an important task in programming. Object is a composite data type in OOP. Member field in an object may be stored by value or by reference. Copying may be performed in two ways.

A shallow copy copies all values and references into a new instance. The data to which a reference is pointing is not copied; only the pointer is copied. The new references are pointing to the original objects. Any changes to the reference members affect both objects.

A deep copy copies all values into a new instance. In case of members that are stored as references a deep copy performs a deep copy of data that is being referenced. A new copy of a referenced object is created, and the pointer to the newly created object is stored. Any changes to those referenced objects will not affect other copies of the object. Deep copies are fully replicated objects.

In PHP, we have a copy keyword which performs a shallow copy by default. It calls the object's __clone() method. We can implement the method to create our custom deep copy. In PHP all objects are assigned by reference.

The next two examples perform a shallow and a deep copy on objects.

shallowcopy.php
<?php

class Object {

    public $id;
    public $size;
    public $color;

    function __construct($id, $size, $color) {
    
        $this->id = $id;
        $this->size = $size;
        $this->color = $color;
    }
}

class Color {

    public $red;
    public $green;
    public $blue;

    function __construct($red, $green, $blue) {
    
        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}

$color = new Color(23, 42, 223);

$object1 = new Object(23, "small", $color);
$object2 = clone $object1;

$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

print_r($object1);
print_r($object2);

?>

In the above PHP script, we define two custom objects: Object and Color. The Object object will have a reference to the Color object.

$color = new Color(23, 42, 223);

We create an instance of the Color object.

$object1 = new Object(23, "small", $color);

An instance of the Object object is created. It passes the instance of the Color object to its constructor.

$object2 = clone $object1;

We perform a shallow copy of the Object object.

$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

Here we modify the member fields of the cloned object. We increment the id, change the red part of the color object, and change the size to "big".

print_r($object1);
print_r($object2);

We use the print_r() function to compare the results.

$ php shallowcopy.php 
Object Object
(
    [id] => 23
    [size] => small
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)
Object Object
(
    [id] => 24
    [size] => big
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)

We can see that the ids are different: 23 vs 24. The size is different: "small" vs "big". But the red part of the color object is same for both instances: 255. Changing member values of the cloned object did not affect the original object. Changing members of the referenced object has affected the original object too. In other words, both objects refer to the same color object in memory.

To change this behaviour, we will do a deep copy next.

deepcopy.php
<?php

class Object {

    public $id;
    public $size;
    public $color;

    function __construct($id, $size, $color) {
    
        $this->id = $id;
        $this->size = $size;
        $this->color = $color;
    }

    function __clone() {
    
        $red = $this->color->red;
        $green = $this->color->green;
        $blue = $this->color->blue;
        $this->color = new Color($red, $green, $blue);
    }
}

class Color {

    public $red;
    public $green;
    public $blue;

    function __construct($red, $green, $blue) {
    
        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}

$color = new Color(23, 42, 223);

$object1 = new Object(23, "small", $color);
$object2 = clone $object1;

$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

print_r($object1);
print_r($object2);

?>

In this PHP script, we have implemented the __clone() method.

function __clone() {
    $red = $this->color->red;
    $green = $this->color->green;
    $blue = $this->color->blue;
    $this->color = new Color($red, $green, $blue);
}

Inside the __clone() method, we copy the red, green, and blue member fields and create a new Color object. Now, the $color field points to a different Color object.

$ php deepcopy.php 
Object Object
(
    [id] => 23
    [size] => small
    [color] => Color Object
        (
            [red] => 23
            [green] => 42
            [blue] => 223
        )

)
Object Object
(
    [id] => 24
    [size] => big
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)

Now the red part of the referenced Color object is not the same. The original object has retained its previous 23 value.

Exceptions

Exceptions are designed to handle the occurrence of exceptions, special conditions that change the normal flow of program execution. Exceptions are raised or thrown and initiated.

During the execution of our application, many things might go wrong. A disk might get full and we cannot save our file. An Internet connection might go down and our application tries to connect to a site. All these might result in a crash of our application. To prevent happening this, we must cope with all possible errors that might occur. For this, we can use the exception handling.

Exceptions are available since PHP 5. Most PHP errors still use the old error reporting and not exceptions. With a set_error_handler() we can do a workaround for this.

zerodiv.php
<?php

set_error_handler("error_handler");

function error_handler($errno, $errstring, $errfile, $line, $trace) {
    throw new ErrorException($errstring, $errno, 0,  $errfile, $line);
}

try {

    $a = 0;
    $b = 32;
    $c = $b / $a;
} catch(ErrorException $e) {

   echo "Error occurred\n";
   echo $e->getMessage(), "\n";
}

?>

In the above PHP script, we intentionally divide a number by zero. This leads to an error. The error is not an exception and is not caught by the catch keyword.

set_error_handler("error_handler");

The set_error_handler() function sets a user defined error handler function.

function error_handler($errno, $errstring, $errfile, $line, $trace) {
    throw new ErrorException($errstring, $errno, 0,  $errfile, $line);
}

Inside the set_error_handler() function, we throw an ErrorException. This exception is later caught by the catch keyword.

try {

    $a = 0;
    $b = 32;
    $c = $b / $a;
}

The code that we are checking against an error is put inside the block following the try keyword.

} catch(Exception $e) {
   echo $e->getMessage();
}

The catch keyword is used to catch an exception. To find out more info, we call the getMessage() method on the exception object.

$ php zerodiv.php 
Error occurred
Division by zero

This is the output of our PHP script.

The Exception is a base class for all exceptions. We can create our own exceptions derived from this base class.

myexception.php
<?php

define("LIMIT", 333);

class BigValueException extends Exception {

    public function __construct($message) {
        parent::__construct($message);
    }
}

$a = 34325;

try {
    if ($a > LIMIT) {
        throw new BigValueException("Exceeded the maximum value allowed\n");   
    }
} catch (BigValueException $e) {
    echo $e->getMessage();   
}

?>

Let's say, we have a situation in which we cannot deal with big numbers.

define("LIMIT", 333);

Numbers bigger than this constant are considered to be "big" by our PHP script.

class BigValueException extends Exception {

We have a BigValueException class. This class derives from the Exception class through the extends keyword.

public function __construct($message) {
    parent::__construct($message);
}

Inside the constructor, we call the parent's constructor.

if ($a > LIMIT) {
    throw new BigValueException("Exceeded the maximum value allowed\n");   
}

If the value is bigger than the limit, we throw our custom exception. We give the exception a message "Exceeded the maximum value allowed".

} catch (BigValueException $e) {
    echo $e->getMessage();   
}

We catch the exception and print its message to the console.

Constructor overloading

PHP does not support diret constructor overloading. In other words, each class can only have one constructor defined. Many programmers that already know Java or C# languages are looking for a similar feature in PHP. There are two ways how we can handle this.

The first solution is based on the func_get_args() function. The second solution uses a factory pattern.

constructors.php
<?php

class Book {
    
    private $author = "not specified";
    private $title = "not specified";
    private $year = "not specified";

    public function __construct() {
    
        $args = func_get_args();
    
        foreach(["title", "author", "year"] as $item) {
            
            if(empty($args)) {
                break;
            }
    
            $this->$item = array_shift($args);
        }
    }
    
    public function __toString() {
        return "Author: $this->author\nTitle: $this->title\nPublished: $this->year\n\n";
    }
}

$book1 = new Book("Stephen Prata", "C Primer Plus");
echo $book1;

$book2 = new Book("Joshua Bloch", "Effective Java", 2008);  
echo $book2;

?>

In the above script, we have a Book class. We instantiate the class with 2 and 3 parameters.

private $author = "not specified";
private $title = "not specified";
private $year = "not specified";

We have three member fields defined. Their initial value is "not specified".

$args = func_get_args();

The func_get_args() function returns an array comprising a function's argument list. So the idea is: the code inside the constructor is dynamic; it depends on the arguments passed to it.

foreach(["title", "author", "year"] as $item) {

We go through all member fields using the foreach keyword.

$this->$item = array_shift($args);

One of the most basic tasks in constructors is to initialize the member fields of the class. This is done by the above code line. The array_shift() function removes the first item from the array and returns it.

$book1 = new Book("Stephen Prata", "C Primer Plus");
...
$book2 = new Book("Joshua Bloch", "Effective Java", 2008);

We have two different constructors. The first takes 2 parameters, the second takes 3.

$ php constructors.php
Author: C Primer Plus
Title: Stephen Prata
Published: not specified

Author: Effective Java
Title: Joshua Bloch
Published: 2008

This is the outcome of the script.

The next code example simulates constructor overloading using the factory pattern. It is one of the creational patterns in OOP. The pattern creates objects without specifying the exact class of object that will be created. More generally, the term factory method is often used to refer to any method whose main purpose is creation of objects.

factory.php
<?php

class Cat {

    private $name = "unspecified";
    private $age = "unspecified";

    public static function withName($name) {
    
        $cat = new Cat();
        $cat->name = $name;

        return $cat;
    }

    public static function withAge($age) {
    
        $cat = new Cat();
        $cat->age = $age;

        return $cat;
    }

    public static function fullCat($name, $age) {
    
        $cat = new Cat();
        $cat->name = $name;
        $cat->age = $age;

        return $cat;
    }

    public function __toString() {
        return "Name: $this->name, Age: $this->age\n";
    }
}

$cici = Cat::withName("Cici");
echo $cici;

$missy = Cat::withAge(6);
echo $missy;

$lucky = Cat::fullCat("Lucky", 4);
echo $lucky;

?>

We have a Cat factory class in the above PHP script. It has three different static functions. Each of them returns a specific cat object.

private $name = "unspecified";
private $age = "unspecified";

We have two member fields. Their initial value is "unspecified".

public static function withName($name) {
    $cat = new Cat();
    $cat->name = $name;

    return $cat;
}

Here is a static withName() function. This function creates an instance of the Cat class. It sets the name member field and returns the object.

$cici = Cat::withName("Cici");
echo $cici;

We create an instance of the cat with one of the factory methods. We echo the object. i.e. call the __toString() method of the class.

$ php factory.php 
Name: Cici, Age: unspecified
Name: unspecified, Age: 6
Name: Lucky, Age: 4

The output of the script.

In this part of the PHP tutorial, we continued the discussion of the object-oriented programming in PHP.