o.php

Objects

Classes in PHP

Objects in PHP are instantiations of class definitions. You cannot create an object instance without first defining a class. Classes must have globally unique names within the scope of the program and can inherit from other classes.

Before we take a deeper look at objects we will first look at the common bad practice of accessing an objects properties directly. We start by seeing that defining classes in PHP can be a quick process, however the short term time benefits produce long term technical debt. In the following example we define a class with three properties, $first_name, $last_name and $genre. Then we instantiate an object from the definition, provide values for its properties and finally print the result.

<?php
// Define the class
class Author {
  public 
$first_name '';
  public 
$last_name  '';
  public 
$genre '';
}

// Instantiate an object
$author = new Author();

// Assign values to its variables
$author->first_name 'Philip';
$author->last_name  'Dick';
$author->genre      'Sci-fi';

// Print its values
print $author->first_name." ".
      
$author->last_name.', '.
      
$author->genre;
?>

php> Philip Dick, Sci-fi

This all looks good, the code executes and the result is as expected. The problem with this example is that it is not forward looking. Imagine if the Author class was used within a larger program containing lots of scattered code directly accessing the first_name property.

Think what you would have to do if told to enforce that the first character of the first name is always uppercase. You would have to search the code-base to find out where the first_name property is used, then wrap the accessing code in the ucfirst() function.

However, this is not a very forward looking approach either. A preferable idea would be to add the code to a new method in the class, getFirst_name(), then update the program to call this method rather than accessing the property directly.

<?php
// Define the class
class Author {
  public 
$first_name '';
  public 
$last_name  '';
  public 
$genre '';

  
// Add the new method
  
public function getFirst_name(){
    return 
ucfirst($this->first_name);
  }
}

// Instantiate an object
$author = new Author();

// Assign values to its variables
$author->first_name 'Philip';
$author->last_name  'Dick';
$author->genre      'Sci-fi';

// Print its values
print $author->getFirst_name()." ".
      
$author->last_name.', '.
      
$author->genre;
?>

php> Philip Dick, Sci-fi

As you can see defining classes correctly in PHP is a little more verbose (as it is in most languages). When writing forward looking code you should provide getter and setter methods for each of the public facing parameters. Thus encapsulating the inner workings of the object and allowing it to be modified at a later date without detrimental effects. Below is how the original class should have been written and used.

<?php
class Author {
  private 
$first_name '';
  private 
$last_name  '';
  private 
$genre '';

  public function 
setFirst_name($str){
    
$this->first_name $str;
  }
  public function 
setLast_name($str){
    
$this->last_name $str;
  }
  public function 
setGenre($str){
    
$this->genre $str;
  }

  public function 
getFirst_name(){
    
// We uppercase the first character here
    
return ucfirst($this->first_name);
  }
  public function 
getLast_name(){
    return 
$this->last_name;
  }
  public function 
getGenre(){
    return 
$this->genre;
  }
}

// Instantiate an object
$author = new Author();

// Assign values to its variables
$author->setFirst_name('Philip');
$author->setLast_name('Dick');
$author->setGenre('Sci-fi');

// Print its values
print $author->getFirst_name()." ".
      
$author->getLast_name().', '.
      
$author->getGenre();
?>

php> Philip Dick, Sci-fi

The properties are declared private, each with their own getter and setter method. This stops anyone from accessing their values directly as they did before. It allows you to add code to either the get or set methods without worrying if it could be bypassed.

Object

In order to follow the examples in this section you will need to require() the Object.php class. This file is include as part of the oLite.php script.

<?php
require('path/to/Object.php');
?>

The static method Object::create() is a convenience for creating new PHP Objects. If the object it is asked to create has not yet been defined it creates a special kind of object.

The class definition used for this special object defines a container for an ordered map of properties. Each property has a key, which can only be a string, and a value which can be any PHP value. It also details several methods for interacting with the ordered map.

The create() method can be called in several ways. The most basic is with no arguments.

<?php
$obj 
Object::create();
?>

To create an object of a particular type, either a predefined class or an on-the-fly class, you can supply the name of the class as a string.

<?php
$obj 
Object::create('MyClass');
?>

With an object created you can optionally create properties on that object, both with or without values.

<?php
$obj 
Object::create('MyClass');

$obj->setFirst_name('Jim');
$obj->setLast_name(null);
$obj->setGenre();
?>

If you want the object to have predefined properties you can supply an array of key/values as an argument when calling create().

<?php
$props 
= array(
  
'first_name'=>'Jim',
  
'last_name' =>'Collins',
  
'genre'     =>null
);

$obj Object::create($props);
?>

Putting it all together the final example replicates the functionally of the previous sections complete Author class, including the getters and setters, but without the verbose definition. Those of you with a keen eye will note that not only is it an order of magnitude less code but there is actually no class definition at all.

<?php
$author 
Object::create('Author',
  array(
    
'first_name'=>'Charles',
    
'last_name' =>'Dodgson',
    
'genre'     =>'Fantasy'
  
)
);
?>

As you can see this is quite a departure from the normal way of creating objects. The rest of this chapter explains how the Object is used and where the getters and setters have gone.

Getters

Values can be retrieved from objects by using key name strings in expressions. The getter method, getKey_name(), is the preferred access pattern as it supplies automatic future proofing for your code (to be discussed later). It should be noted that as key names may also be method names it is beneficial to limit their useable characters to a-z, A-Z, 0-9 and _.

<?php
$author
->first_name;         // Charles
$author->getFirst_name();    // Charles
$author->get('first_name');  // Charles
?>

The value null is return if an attempt is made to retrieve a nonexistent property.

<?php
$author
->middle_name;        // null
$author->getFirstName();     // null
$author->get('first-name');  // null
?>

An optional default value can be provided when calling either of the method invocations.

<?php
$author
->getFirstName('N/A');                 // N/A
$author->get('first-name''Not Available');  // Not Available
?>


Setters

A value in an object can be updated by assignment. If the property name has already been defined then it is replaced by the new value. Here the setter method, setKey_name(), is the preferred form of assignment as it builds in future proofing for your code (to be discussed later). As before it should also be noted that key names are also method names, it is beneficial to limit their useable characters to a-z, A-Z, 0-9 and _.

<?php
$author
->first_name 'Charles';
$author->setFirst_name('Charles');
$author->set('first_name''Charles');
?>

If the object does not already have the property, it is appended.

<?php
$author
->middle_name 'Charles';
$author->setPen_name('Lewis Carroll');
$author->set('gender''male');
?>


Reference

Objects are passed around via reference, they are never copied.

<?php
$author 
Object::create('Author');
$x $author;
$x->first_name 'Douglas';
$name $author->first_name;
        
// $name is 'Douglas' because $x and $author
        // are references to the same object

$a Object::create(); $b Object::create(); $c Object::create();
        
// $a, $b and $c all refer to
        // a different empty object

$a $b $c Object::create();
        
// $a, $b and $c all refer to
        // the same empty object
?>


Prototype

Every object inheriting directly from Object can be linked to a prototype object from which it can inherit properties. When you create an object you can select the object to be its prototype.

<?php
$author 
Object::create('Author');
$author->first_name 'Douglas';

$myAuthor Object::create('Author'$author);
$myAuthor->first_name// Douglas
?>

The prototype link has no effect on updating, when we make changes to an object its prototype is not touched.

<?php
$myAuthor
->first_name 'Toby';
$myAuthor->first_name// Toby
$author->first_name;   // Douglas
?>

The prototype link is only used in retrieval. If we make a request for a property that the object does not have, it will call its prototype object in an attempt to retrieve it. This will carry on as long as the requested object has a prototype. If the property is not found anywhere in the prototype chain then null is returned.

The prototypal relationship is dynamic, if we add a new property to prototype it is immediately available to all the objects based on that prototype.

<?php
$author
->topic 'Computer Science';
$myAuthor->topic// Computer Science
?>


Reflection

It is easy to inspect an object to see what properties it has. There are two options, call the local objects properties() method and retrieve an array of all its properties.

<?php
$author
->properties(); // array
?>

Or retrieve an array of all the properties from all the objects in the prototypal chain. You can do this by passing the boolean value true as an argument to properties().

<?php
$author
->properties(true); // array
?>

There is also the helper method hasOwnProperty() which tests if a property is contained by the object it is called on.

<?php
$author
->hasOwnProperty('topic');   // true
$myAuthor->hasOwnProperty('topic'); // false
?>

Depending on your needs anyone one of these methods can be employed.

Enumeration

The foreach statement can loop over all the local properties within an object. Any properties provided by prototype chain are ignored.

<?php
$author 
Object::create('Author');
$author->setFirst_name('Jim');
$author->setLast_name('Collins');
$author->setGenre('Research');

foreach(
$author as $key=>$value) print "{$key}{$value}";
?>

php> first_name: Jim
php> last_name: Collins
php> genre: Research


Remove

The remove() method is used to delete a property from an object. Removing a property does not effect any of the objects in the prototypal chain. By removing a property it may allow a prototypal property to appear.

<?php
$author 
Object::create('Author');
$author->first_name 'Jim';
count($author->properties()); // 1

// Remove the "first_name" property
$author->remove('first_name');
count($author->properties()); // 0
?>


Copying

When an object is copied it will copy of all of the object's properties. The prototype chain will remain as references to other objects. This means if a change happens in the prototype chain it will be reflected in the cloned object.

<?php
$author 
Object::create('Author');
$author->setFirst_name('Arthur');
$author->setLast_name('Doyle');
$author->setGenre('Fiction');

// Copies the Author with all its properties
$copy $author->copy();
// Test the Object and Properties
$copy->equals($author); // true
?>

You can perform a shallow copy which does the same as above but empties the object properties. This is useful for duplicating an objects prototype chain but not it data.

<?php
// Copies the Author with no properties
$copy $author->copy(true);
// Test the Object and Properties
$copy->equals($author); // false
// Get the objects name
$copy->toString(); // Author
?>

The equals() method used in the examples above checks if the provided object has the same class name, properties and prototypal chain as its self. It returns a boolean true or false.

The toString() method simply returns the objects class name as a string.

You can touch it, smell it, taste it so sweet - Mike Patton