Getting started with objects with PHP V5

curlyokaySecurity

Nov 18, 2013 (3 years and 8 months ago)

118 views


Country/region
[
select
]

Terms of use


All of dW



Contents:
What are classes and
objects?
A first class
Properties
Methods
The constructor
Keywords: Can we have a
little privacy in here?
Working in class context
Inheritance
Summary
Resources
About the author
Rate this article
Related content:
PHP security, Part 1: How to
spot problems
Introduction to PHP
PHP by example, Part 1
Subscriptions:
dW newsletters

Home

Products

Services & solutions

Support & downloads

My account

developerWorks
>
Open source projects
|
SOA and Web services
>
Getting started with objects with PHP V5
Why you need to know objects and classes, and how to use
them
Level: Introductory
Matt Zandstra
(
matt@fate16.com
)
Developer and writer, Yahoo!
17 May
2005
This article describes the fundamentals of objects and classes in PHP V5, from the very
basics through to inheritance, for experienced object-oriented programmers and those
who have not yet been introduced to objects.
As a PHP programmer, you know variables and functions inside-out. Classes and objects,
though, might be a different matter. It's possible to create perfectly good systems without defining
a single class. Even if you decide to stay away from object-oriented programming in your own
code, you will likely need to know object-oriented programming. For example, if you use a third-
party library, such as those made available by PHP Extension and Application Repository
(PEAR), you will find yourself instantiating objects and calling methods.
What are classes and objects?
Put simply, a
class
is a discrete block or bundle of variables and methods. These components
usually coalesce around a single responsibility or set of responsibilities. In this article, you will
create a class that collects methods for querying and populating a dictionary of terms and
values.
A class can be used directly as a neat way of organizing data and functionality, very much like a
bunch of functions and variables. This is to ignore a powerful dimension, however. Classes can
be used to generate multiple instances in memory. Such instances are called
objects
. Each
object has access to the same set of
functions
(called
methods
in an object-oriented context),
and
variables
(called
properties
or
instance variables
), but the actual value of each variable may
differ from object to object.
Think about a unit in a role-playing game -- a tank, perhaps. A class may lay down a set of variables for tanks: defensive and
offensive capability, range, health, and so on. The class may also define a set of functions, including
move()
and
attack()
.
While the system contains one tank class, this class may be used to generate tens or hundreds of tank objects, each potentially
with its own characteristics of health or range. A class, then, is a blueprint or template for use in generating objects.
Perhaps the easiest way to understand classes and objects is to create some.
A first class
You can create a class with the
class
keyword. At its simplest, a class consists of the keyword class, a name, and a code
block:
class Dictionary {
}
The class name can contain any combination of letters and numbers, as well as the underscore character, but cannot begin with
a number.
The
Dictionary
class in the previous example is perfectly legal, even if it is of limited use. So how do you use this class to
create some objects?
$obj1 = new Dictionary();
$obj2 = new Dictionary();
$obj3 = new Dictionary();
In form at least, instantiating an object is similar to calling a function. As with a function call, you must supply parentheses. Like
functions, some classes require that you pass them arguments. You must also use the new keyword. This tells the PHP engine
that you wish to instantiate a new object. The returned object can then be stored in a variable for later use.
Properties
Within the body of a class, you can declare special variables called properties. In PHP V4, properties had to be declared with
the keyword
var
. This is still legal syntax, but mainly for the sake of backward compatibility. In PHP V5, properties should be
declared public, private, or protected. You can read about these qualifiers in
Keywords: Can we have a little privacy in here?
But
for now, declare all properties public in the examples. Listing 1 shows a class that declares two properties.
Listing 1. A class that declares two properties
class Dictionary {
public $translations = array();
public $type ="En";
}
As you can see, you can declare a property and assign its value at the same time. You can get a quick peek at the state of an
object with the
print_r()
function. Listing 2 shows that a
Dictionary
object now has more to it.
Listing 2. A look at the
Dictionary
object
$en = new Dictionary();
print_r( $en );
If we run this script, we'll see output of:
Dictionary Object
(
[translations] => Array
(
)
[type] => En
)
You can access public object properties using the object operator
'->'
. So
$en->type
means the $type property of the
Dictionary
object referenced by $en. If you can access a property, it means that you can set and get its value. The code in
Listing 3 creates two instances of the
Dictionary
class -- in other words, it instantiates two
Dictionary
objects. It changes
the
$type
property of one object and adds translations to both:
Listing 3. Creating two instances of the
Dictionary
class
$en = new Dictionary();
$en->translations['TREE'] = "tree";
$fr = new Dictionary();
$fr->type = "Fr";
$fr->translations['TREE'] = "arbre";
foreach ( array( $en, $fr ) as $dict ) {
print "type: {$dict->type} ";
print "TREE: {$dict->translations['TREE']}\n";
}
The script outputs the following:
type: En TREE: tree
type: Fr TREE: arbre
So the
Dictionary
class is now a little more useful. Individual objects can store distinct sets of keys and values, as well as a
flag that tells a client more about the kind of
Dictionary
this is.
Even though the
Dictionary
class is currently little more than a wrapper around an associative array, there is some clue to
the power of objects here. At this stage, we could represent our sample data pretty well, as shown in Listing 4.
Listing 4. Sample data
$en = array(
'translations'=>array( 'TREE' => 'tree' ),
'type'=>'En'
);
$fr = array(
'translations'=>array( 'TREE' => 'arbre' ),
'type'=>'Fr'
);
Although this data structure fulfills the same purpose as the
Dictionary
class, it provides no guarantee of the structure. If you
are passed a
Dictionary
object, you know it is designed to have a
$translations
property. Given an associative array,
you have no such guarantee. This fact makes a query like
$fr['translations']['TREE'];
somewhat hit and miss,
unless the code making the query is sure of the provenance of the array. This is a key point about objects: The type of an object
is a guarantee of its characteristics.
Although there are benefits to storing data with objects, you are missing an entire dimension. Objects can be things, but crucially
they can also do things.
Methods
Put simply, methods are functions declared within a class. They are usually -- but not always -- called via an object instance
using the object operator. Listing 5 adds a method to the
Dictionary
class and invokes it.
Listing 5. Adding a method to the
Dictionary
class
class Dictionary {
public $translations = array();
public $type ="En";
function summarize() {
$ret = "Dictionary type: {$this->type}\n";
$ret .= "Terms: ".count( $this->translations )."\n";
return $ret;
}
}
$en = new Dictionary();
$en->translations['TREE'] = "tree";
print $en->summarize();
It provides output of:
Dictionary type: En
Terms: 1
As you can see, the
summarize()
method is declared just as any function would be declared, except that is done within a
class. The
summarize()
method is invoked via a
Dictionary
instance using the object operator. The
summarize()
function accesses properties to provide a short overview of the state of the object.
Notice the use of a feature new to this article. The
$this
pseudo-variable provides a mechanism for objects to refer to their
own properties and methods. Outside of an object, there is a handle you can use to access its elements (
$en
, in this case).
Inside an object, there is no such handle, so you must fall back on
$this
. If you find
$this
confusing, try replacing it in your
mind with
the current instance
when you encounter it in code.
Classes are often represented in diagrams using the Universal Modeling Language (UML). The details of the UML are beyond
the scope of this article, but such diagrams are nonetheless an excellent way of visualizing class relationships. Figure 1 shows
the
Dictionary
class as it stands. The class name lives in the top layer, properties in the middle, and methods at the bottom.
Figure 1. The Dictionary class shown using the UML
The constructor
The PHP engine recognizes a number of "magic" methods. If they are defined, it invokes these methods automatically when the
correct circumstances arise. The most commonly implemented of these methods is the constructor method. The PHP engine
calls a constructor when the object is instantiated. It is the place to put any essential setup code for your object. In PHP V4, you
create a constructor by declaring a method with the same name as that of the class. In V5, you should declare a method called
__construct()
. Listing 6 shows a constructor that requires a
DictionaryIO
object.
Listing 6. A construtor that requires a
DictionaryIO
object
class Dictionary {
public $translations = array();
public $type;
public $dictio;
function __construct( $type, DictionaryIO $dictio ) {
$this->type = $type;
$this->dictio=$dictio;
}
//...
To instantiate a
Dictionary
object, you need to pass a type string and a
DictionaryIO
object to its constructor. The
constructor uses these parameters to set its own properties. Here is how you might now instantiate a
Dictionary
object:
$en = new Dictionary( "En", new DictionaryIO() );
The
Dictionary
class is now much safer than before. You know that any
Dictionary
object will have been initialized with
the required arguments.
Of course, there's no way yet to stop someone coming along later and changing the
$type
property or setting
$dictio
to
null. Luckily, PHP V5 can help you there, too.
Keywords: Can we have a little privacy in here?
You have already seen the public keyword in relation to property declarations. This keyword denotes a property's visibility. In
fact, the visibility of a property can be set to public, private, and protected. Properties that are public can be written to and read
from outside the class. Properties that are private can only be seen within the object or class context. Properties that are
protected can only be seen within the context of the current class or its children. (You will see this in action in the
Inheritance
section.) You can use private properties to really lock down our classes. If you declare your properties private and attempt to
access them from outside the class' scope (as shown in Listing 7), the PHP engine will throw a fatal error.
Listing 7. Attempting to access your properties from outside the class' scope
class Dictionary {
private $translations = array();
private $dictio;
private $type;
function __construct( $type, DictionaryIO $dictio ) {
$this->type = $type;
$this->dictio = $dictio;
}
// ...
}
$en = new Dictionary( "En", new DictionaryIO() );
$en->dictio = null;
This outputs the following:
Fatal error: Cannot access private property
Dictionary::$dictio in...
As a rule of thumb, you should make most properties private, then provide methods for getting and setting them if necessary. In
this way, you can control a class' interface, making some data read-only, cleaning up or filtering arguments before assigning
them to properties, and providing a clear set of rules for interacting with objects.
You can modify the visibility of methods in the same way as properties, adding public, private, or protected to the method
declaration. If a class needs to use some housekeeping methods that the outside world need not know about, for example, you
can declare them private. In Listing 8, a
get()
method provides the interface for users of the
Dictionary
class to extract a
translation. The class also needs to keep track of all queries and provides a private method,
logQuery()
, for this purpose.
Listing 8. A
get()
method provides the interface for users of the
Dictionary
class
function get( $term ) {
$value = $this->translations[$term];
$this->logQuery( $term, $value, "get" );
return $value;
}
private function logQuery( $term, $value, $kind ) {
// write log information
}
Declaring
logQuery()
as private simplifies the public interface and protects the class from having
logQuery()
called
inappropriately. As with properties, any attempt to call a private method from outside the containing class causes a fatal error.
Working in class context
The methods and properties you have seen so far all operate in object context. That is, you must access them using an object
instance, via the
$this
pseudo-variable or an object reference stored in a standard variable. In some cases, you may find that
it's more useful to access properties and methods via a class rather than an object instance. Class members of this kind are
known as
static
.
To declare a static property, place the keyword
static
after the visibility modifier, directly in front of the property variable.
This example shows a single static property:
$iodir
, which holds the path to the default directory for saving and reading
Dictionary
data. Because this data is the same for all objects, it makes sense to make it available across all instances.
Listing 9. A single static
$iodir
property
class Dictionary {
public static $iodir=".";
// ...
}
You can access a static property using the scope resolution operator, which consists of a double colon (::). The scope resolution
operator should sit between the class name and the static property you wish to access.
print Dictionary::$iodir . "\n";
Dictionary::$iodir = "/tmp";
As you can see, there is no need to instantiate a
Dictionary
object to access this property.
The syntax for declaring and accessing static methods is similar. Once again, you should place the static keyword after the
visibility modifier. Listing 10 shows two static methods that access the
$iodir
property, which is now declared private.
Listing 10. Two static methods that access the
$iodir
property
class Dictionary {
private static $iodir=".";
// ...
public static function setSaveDirectory( $dir ) {
if ( ! is_dir( $dir ) ||
! is_writable( $dir ) ) {
return false;
}
self::$iodir = $dir;
}
public static function getSaveDirectory( ) {
return self::$iodir;
}
// ...
}
Users can no longer access the
$iodir
property directory. By creating special methods for accessing a property, you can
ensure that any provided value is sane. In this case, the method checks that the given string points to a writable directory before
making the assignment.
Notice that both methods refer to the
$iodir
property using the keyword
self
and the scope resolution operator. You cannot
use
$this
in a static method because
$this
is a reference to the current object instance, but a static method is called via the
class and not an object. If the PHP engine sees
$this
in a static method, it will throw a fatal error together with an informative
message.
To call a static method from outside of its class, use the class name together with the scope resolution operator and the name of
the method.
Dictionary::setSaveDirectory("/tmp");
print Dictionary::getSaveDirectory();
There are two good reasons why you might want to use a static method. First of all, a utility operation may not require an object
instance to do its job. By declaring it static, you save client code the overhead of creating an object. Second, a static method is
globally available. This means that you can set a value that all object instances can access, and it makes static methods a great
way of sharing key data across a system.
While static properties are often declared private to prevent meddling, there is one way of creating a read-only statically scoped
property: You can declare a constant. Like its global cousin, a class constant is immutable once defined. It is useful for status
flags and for other things that don't change during the life of a process, like pi, for example, or all the countries in Africa.
You declare a class constant with the
const
keyword. For example, since a real -world implementation of a
Dictionary
object would almost certainly have a database sitting behind it, you can also assume that there will be a maximum length for
terms and translations. Listing 11 sets this as a class constant.
Listing 11. Setting
MAXLENGTH
as a class constant
class Dictionary {
const MAXLENGTH = 250;
// ...
}
print Dictionary::MAXLENGTH;
Class constants are always public, so you can't use the visibility keywords. This is not a problem because any attempt to change
the value will result in a parse error. Also notice that unlike regular properties, a class constant does not begin with the dollar
sign.
Inheritance
If you're already in any way familiar with object-oriented programming, you'll know that I have been saving the best for last. The
relationship between classes and the dynamic objects they generate allows for much flexibility in a system. Individual
Dictionary
objects encapsulate distinct sets of translation data, for example, yet the model for these varying entities is
defined in the single
Dictionary
class.
Sometimes, though, you need to inscribe variation down at the class level. Remember the
DictionaryIO
class? To recap, it
takes data from a
Dictionary
object, writes it to the file system, takes data from a file, and merges it back into a
Dictionary
object. Listing 12 shows a quick implementation that uses serialization to save and load
Dictionary
data.
Listing 12. A quick implementation using serialization
class Dictionary {
// ...
function asArray() {
return $this->translations;
}
function getType() {
return $this->type;
}
function export() {
$this->dictio->export( $this );
}
function import() {
$this->dictio->import( $this );
}
}
class DictionaryIO {
function path( Dictionary $dictionary, $ext ) {
$path = Dictionary::getSaveDirectory();
$path .= DIRECTORY_SEPARATOR;
$path .= $dictionary->getType().".$ext";
return $path;
}
function export( Dictionary $dictionary ) {
$translations = $dictionary->asArray();
file_put_contents( $this->path(
$dictionary, 'serial'),
serialize( $translations ) );
}
function import( Dictionary $dictionary ) {
$path = $this->path( $dictionary, 'serial' );
if ( ! is_file( $path ) ) return false;
$translations = unserialize(
file_get_contents( $path ) );
foreach ( $translations as $term => $trans ) {
$dictionary->set( $term, $trans );
}
}
}
$dict = new Dictionary( "En", new DictionaryIO() );
$dict->set( "TREE", "tree" );
$dict->export();
This example introduces a couple of simple
Dictionary
methods -- in particular,
asArray()
, which returns a copy of the
$translations
array. The
DictionaryIO
implementation has the virtue of simplicity. As is usual in example code, error
checking has been omitted, but even so, this is a quick and easy way of saving data to file.
Once you have deployed a library of this sort, you soon become committed to supporting its save format. Making a format
obsolete risks the goodwill of your users who may store backups in this way. But requirements change, and you may also get
complaints that the output format is not easily user-editable. Such users may wish to send export files to third parties in XML
format.
You now face a problem. How do you support both formats behind the
DictionaryIO
interface?
One solution would be to use a conditional statement inside the
export()
and
import()
methods that tests a type flag, as
shown in Listing 13.
Listing 13. Using a conditional statement inside the
export()
and
import()
methods
function export( Dictionary $dictionary ) {
if ( $this->type == DictionaryIO::SERIAL ) {
// write serialized data
} else if ( $this->type == DictionaryIO::XML ) {
// write xml data
}
}
function import( Dictionary $dictionary ) {
if ( $this->type == DictionaryIO::SERIAL ) {
// read serialized data
} else if ( $this->type == DictionaryIO::XML ) {
// read xml data
}
}
This kind of structure is an example of a bad "code smell" in that it relies upon duplication. When a change in one place (adding
a new type test, for example) requires a set of parallel changes in other places (bringing other type tests into line), code can
quickly become error-prone and hard to read.
Inheritance offers a much more elegant solution. You can create a new class
XmlDictionaryIO
that inherits the interface
laid down by
DictionaryIO
, but overrides some of its functionality.
You create a child class using the extends keyword. Here is a minimal implementation of the
XmlDictionaryIO
class:
XmlDictionaryIO extends DictionaryIO {
}
XmlDictionaryIO
is now functionally identical to
DictionaryIO
. Because it inherits all public (and protected) attributes
from
DictionaryIO
, you can do all the same things with an
XmlDictionaryIO
object that you can do with a
DictionaryIO
object. This relationship extends to object type. An
XmlDictionaryIO
object is obviously an instance of
the
XmlDictionaryIO
class, but it is also an instance of
DictionaryIO
-- in the same way that a person is a human, a
mammal, and an animal all at the same time and in that order of generalization. You can test this using the
instanceof
operator, which returns true if the object is a member of the indicated class, as shown in Listing 14.
Listing 14. Using the
instanceof
operator to test inheritance
$dictio = new XmlDictionaryIO();
if ( $dictio instanceof XmlDictionaryIO ) {
print "object is an instance of XmlDictionaryIO\n";
}
if ( $dictio instanceof DictionaryIO ) {
print "object is an instance of DictionaryIO\n";
}
This outputs:
object is an instance of XmlDictionaryIO
object is an instance of DictionaryIO
Just as
instanceof
accepts that
$dictio
is a
DictionaryIO
object, so, too, will methods accepting these objects as
arguments. This means that an
XmlDictionaryIO
object can be passed to the
Dictionary
class' constructor, even
though
DictionaryIO
is the type specified by the constructor's signature.
Listing 15 is a quick and dirty
XmlDictionaryIO
implementation that uses DOM for its XML functionality.
Listing 15.
XmlDictionaryIO
implementation
class XmlDictionaryIO extends DictionaryIO {
function export( Dictionary $dictionary ) {
$translations = $dictionary->asArray();
$doc = new DOMDocument("1.0");
$dic_el = $doc->createElement( "dictionary" );
$doc->appendChild( $dic_el );
foreach ( $translations as $key => $val ) {
$term_el = $doc->createElement( "term" );
$dic_el->appendChild( $term_el );
$key_el = $doc->createElement("key", $key );
$val_el = $doc->createElement(
"value", $val );
$term_el->appendChild( $key_el );
$term_el->appendChild( $val_el );
}
file_put_contents( $this->path(
$dictionary, 'xml'),
$doc->saveXML() );
}
function import( Dictionary $dictionary ) {
$path = $this->path( $dictionary, 'xml');
if ( ! is_file( $path ) ) return false;
$doc = DOMDocument::loadXML(
file_get_contents( $path ) );
$termlist = $doc
->getElementsByTagName( "term" );
foreach ( $termlist as $term ) {
$key = $term->getElementsByTagName( "key" )
->item( 0 )->nodeValue;
$val = $term
->getElementsByTagName( "value" )
->item( 0 )->nodeValue;
$dictionary->set( $key, $val );
}
}
}
The details of acquiring and generating XML can be taken for granted. There are plenty of ways of getting this done, including
the excellent SimpleXML extension. In summary, the
import()
method takes an XML document and uses it to populate a
Dictionary
object. The
export()
method takes the data from a
Dictionary
object and writes it to an XML file. (In the
real world, you would probably use an XML-based format called XLIFF, which is suitable for importing into third-party translation
tools.)
Notice that both
import()
and
export()
call the utility method
path()
, which doesn't exist in the
XmlDictionaryIO
class. This doesn't matter because
path()
is implemented in
DictionaryIO
. When
XmlDictionaryIO
implements a
method, it is this implementation that is invoked for an
XmlDictionaryIO
object when the method is called. When no
implementation is present, the call falls through to the parent class.
Figure 2 shows the inheritance relationship between the
DictionaryIO
and
XmlDictionaryIO
classes. The closed arrow
denotes inheritance and points from child to parent.
Figure 2. An inheritance relationship
Summary
With limited space available, it has not been possible to cover everything. There are two directions to take in further research:
One should yield breadth and the other depth. By breadth, I mean the features that were beyond the scope of this article, such
as abstract classes, interfaces, the iterator interface, reflection, exceptions, and object cloning. By depth, I mean questions of
design. While it is important to understand the range of tools available in PHP for object-oriented programming, it is equally
crucial to consider how best to work with such features. Fortunately, there are many resources available that focus on patterns
of design in an object-oriented context (see Resources).
If you are still programming with PHP V4, I hope you found enough new features to justify migrating to V5 and its core object-
oriented features. You'll soon wonder how you managed without them.
Resources
The essential overview of all PHP's object-oriented features classes and objects (PHP V5) can be found in
Chapter 19
of
the official PHP Manual.
For an article that expands on issues of type with reference to abstract classes, see
Working with Class Types: Abstract
Classes and Interfaces
.
For an overview of error handling with PHP V5, see
Exceptional Code
.
For a fantastic resource for anyone interested in object-oriented design, visit
C2 Wiki
.
The home page of the PHP language and the place to download PHP is
PHP.net
.
PHP Builder
is popular PHP site with tutorials and code libraries.
Zend.com
contains plenty of useful links and information about PHP. Zend is the PHP Optimizer.
Find complete listings of the articles, tutorials, project info, and news you need to stay up to date on developing with PHP
at developerWorks
PHP top projects
.
Visit the developerWorks
Open source zone
for extensive how-to information, tools, and project updates to help you
develop with open source technologies and use them with IBM's products.
Innovate your next open source development project with
IBM trial software
, available for download or on DVD.
Find hundreds of
discounted books on open source topics
in the Open source section of The Developer Bookstore,
including many
books on PHP
.
Get involved in the developerWorks community by participating in
developerWorks blogs
.
About the author
Matt Zandstra has been a developer, teacher, and writer specializing in Internet applications for more than 10 years. He is an
engineer at Yahoo! where he helps develop a core template management system. He is the author of
SAMS Teach Yourself
PHP in 24 Hours
, and the recently released
PHP 5: Objects, Patterns and Practice
. He has written PHP articles for
Linux
Magazine
, Zend.com, and bgz-consultants.com.
Rate this article
This content was helpful to me:
Strongly disagree (1)
Disagree (2)
Neutral (3)
Agree (4)
Strongly agree (5)
Comments?
Submit feedback
developerWorks
>
Open source projects
|
SOA and Web services
>

About IBM

Privacy

Contact