Abstract classes and interfaces
1. A prospective audience
This clause{article} is intended for skilled PHP-programmers, which want to understand how to work with types of classes in PHP 5 through reduction of types, abstract classes and interfaces. It is supposed, that readers are already familiar with object-oriented programming, including the device of classes and the mechanism of inheritance.
2. Introduction
The majority of PHP-programmers know, that support of object-oriented programming in Zend Engine II is considerably improved. Three new opportunities - reduction of types, abstract classes and interfaces define{determine} opportunities of installation and check of types of classes. In this clause{article} we shall see, how these opportunities help to write a better code and to prevent occurrence of mistakes.
3. Types of classes
In PHP there are 12 predetermined types of the data, including simple types, for example, the whole and with a floating point, complex types (files and objects) and special types (resources and NULL). As PHP supports classes and objects, you also have opportunity to define{determine} own types. Actually, each time at creation of a class you define{determine} new type of the data.
Simple class:
<? php
class Question {
function answer ($response_s) {
//..
}
}
?>
Having created class Question, you also have automatically defined{determined} also type of data Question. The object of class Question belongs to type of data Question, the same as also objects of classes, pronasledovannykh from class Question.
The class inheriting Question:
<? php
class ResourceQuestion extends Question {
//..
function addResourceURL ($url_s) {
//..
}
}
$question = new ResourceQuestion ();
?>
At object $question a complex accessory{belonging}. He belongs to type ResourceQuestion, but at the same time he belongs and to type Question, as class ResourceQuestion pronasledovan from class Question.
4. Why types of classes are important?
If you know type of object, you know his characteristics and an opportunity. If you will say to me, that the object $question belongs to type Question, I at once shall understand, that he has method answer () which I can call. And if you to me will say, that $question also is object of class ResourceQuestion, you also will let to me know, that the object has also method answer (), and a method setResourceURL (). Having armed with this information on object $question, I can normally work with it .
5. We check and set types
In PHP 5 the new operator instanceof by means of which it is possible to check up type of object has appeared. The operator instanceof accepts two parameters. The first is the tested object, the second - type, on an accessory{a belonging} to which the object is checked. Accordingly, to check up, that the object $question belongs to type Question, the following syntax is used:
if ($question instanceof Question) {
print " $question is type Questionn ";
}
Most likely, for your methods and functions types of classes transmitted to him and arguments are not indifferent. You are sure, what other programmers at job with your classes will always pass them correct parameters? And how these wrong parameters can affect job of your scripts?
Let's expand class Question. It is a part of strongly simplified library " Quiz ". Object Marker responsible for check of the answer of the user on a question is necessary for each object Question. In this example class Question accepts object Marker as argument of the designer.
<? php
class Question {
private $marker;
public $prompt = " ";
function __ construct ($prompt_s, $marker) {
$this-> prompt = $prompt_s;
$this-> marker = $marker;
}
}
?>
For a code causing __ construct (), can respond other programmer, therefore we shall present, what will take place if in the designer will be transferred{handed} wrong type of object? The right answer - "anything", and in it also consists a problem. We shall simply save object Marker in the attribute ready to further use in a method answer (). Esi in the designer has been transferred{handed} object not that type, we do not learn{find out} it until we shall call a method answer ():
<?
class Question {
private $marker;
public $prompt = " ";
function __ construct ($prompt_s, $marker) {
$this-> prompt = $prompt_s;
$this-> marker = $marker; // here there will be a mistake
}
function answer ($response_s) {
$this-> marker-> mark ($response_s); // we shall see consequences of the arisen mistake here
}
}
?>
It is that situation in which check of types can prevent occurrence of problems further. If we do not want, that in our class the data of wrong type have made the way, and have hidden, expecting a suitable opportunity to deliver to us there are more than troubles, we should check manually type of argument $marker:
function __ construct ($prompt_s, $marker) {
if (! ($marker instanceof Marker)) {
die (" the object such as Marker Was expected ");
}
$this-> prompt = $prompt_s;
$this-> marker = $marker; // the correct type is guaranteed
}
However, manual check of each argument - business the extremely labour-consuming and increasing sizes of your scripts. In a reality the majority of us will be simple to hope, that the circuit imenovanija and the documentation in a sufficient measure specify expected types of arguments.
In PHP 5 this problem is solved at the expense of strict typification of classes. Simply having written before argument the name of type, you will specify, that PHP should watch{keep up}, that the transferred{handed} data were the specified type:
<?
function __ construct ($prompt_s, Marker $marker) {
$this-> prompt = $prompt_s;
$this-> marker = $marker;
}
Having written ' Marker ' before a variable $marker, we have rigidly set its{her} type. It means, that transfer to object of any other type, except for type Marker will call the following mistake:
Fatal error: Argument 2 must be an object of class Marker in...
Now, when cause our method answer (), we can be sure, that attribute Question:: $marker contains object such as Marker (nevertheless, all of us equally should check, that his value not NULL.)
6. We work with types of classes: abstract classes
You already saw, how the user types of the data guarantee correct interfaces. Actually, it is possible to go further away and to separate the interface from his realization.
Realizations of classes Question and Marker:
<? php
class Question {
private $marker;
public $prompt = " ";
public $score = 0;
public $response = " ";
function __ construct ($prompt_s, Marker $marker) {
$this-> prompt = $prompt_s;
$this-> marker = $marker;
}
function answer ($response_s) {
$this-> score = 0;
$this-> response = $response_s;
if ($this-> marker-> mark ($response_s)) {
$this-> score = 1;
}
}
}
class Marker {
protected $condition;
function __ construct ($condition_s) {
$this-> condition = $condition_s;
}
function mark ($response_s) {
return ($this-> condition == $response_s);
}
}
?>
In this example we have expanded class Question, and now he supports the counter equal to unit or zero. Class Marker stores{keeps} a right answer. When he is caused in class Question, he simply compares a line from $condition with transferred{handed} to a method mark () the answer of the user. If lines coincide, the method returns true. the small example checking job of these classes:
$q = new Question (" how much it is necessary beans that from was 5 ",
new Marker (' five '));
$responses = array ("five", "six");
foreach ($responses as $response) {
$q-> answer ($response);
print " result: for the answer $response a rating {$q-> score} n ";
}
// A conclusion:
// Result: for the answer five rating 1
// Result: for the answer six rating 0
For reflection of mutual relations between classes it is possible to use diagrams. More often for this purpose use Unified Modeling Language (UML). On this diagram mutual relations between classes Question and Marker are shown.
Meanwhile logic of ratings of answers in class Marker syrovata. What will take place, if requirements become more complex ? The manager of quizes most likely will demand the greater flexibility in creation of criteria of a rating. Probably, she needs to create questions of type: " Name keywords of the control of access ", thus the user should specify them all through a point.
You can correct class Marker that he checked exact concurrence and concurrence of items{points} of the list. Any way is necessary for this purpose to distinguish types of conditions. An example using constants as flags though you can introduce instead of it any markers in the text of conditions:
class Marker {
protected $condition;
protected $condWords;
const MATCH = 1;
const CLIST = 2;
private $type = MATCH;
function __ construct ($condition_s, $type=1) {
$this-> type = $type;
if ($type == self:: CLIST) {
$this-> condWords =
preg_split ("/s *, s* / ", $condition_s);
} else {
$this-> condition = $condition_s;
}
}
function mark ($response_s) {
if ($this-> type == self:: CLIST) {
// We sell concurrence to item{point} of the list
return true;
} else {
return ($this-> condition == $response_s);
}
}
}
In class Marker two constants are used: Marker:: MATCH and Marker:: CLIST to trace a way of a rating of answers for which the class is called. If property Marker:: $type contains the value determined in Marker:: MATCH, method Marker:: mark () compares lines. If Marker:: $type contains the value determined in Marker:: CLIST the object segments a line of the answer and compares each of them.
We establish{install} value for flag Marker:: $type in the designer. The argument $type in definition of a method of the designer sets a default value which can be changed by the user.
In this example we while have passed over logic of a rating to not spend a place and to be focused on design of a code as such. You have noticed similar sites in the designer and a method mark ()? You can work with such code until the client will not want support of regular expressions and arithmetic calculation of right answers. By this moment our class Marker it becomes unreasonable razdutym. And, complexity will be not only in size - in fact it is necessary to support two steps in processing answers. The first in the designer, if necessary resulting{bringing} answer to a question to the necessary kind, and the second in a method mark (), making processing of the answer of the user. Necessity of them can support to make support of a code extremely labour-intensive process.
The situation at which you should hold two identical conditional designs usually means, that it is necessary for you to separate these realizations from the interface. It can be made by means of an abstract class.
The abstract class is created with the help of addition of a keyword abstract to the usual announcement of a class.
<? php
abstract class MyClass {
//..
}
?>
After you so will make, it becomes impossible to create objects of this class. Abstract classes are intended to create their subclasses. Abstract classes can have both usual attributes and methods, and abstract methods. At the announcement of an abstract method the keyword abstract should stand on the first place. Abstract classes should not contain realization of a method - they only describe the interface of a method.
abstract class MyClass {
abstract function aMethod ($an_arg);
}
This code changes class Marker, doing{making} his abstract:
<? php
abstract class Marker {
protected $condition;
function __ construct ($condition_s) {
$this-> condition = $condition_s;
$this-> handleCondition ($condition_s);
}
/* We do{make} an opportunity to receive protected-attribute condition */
public function getCondition () {
return $this-> condition;
}
abstract protected function handleCondition ($condition_s);
abstract function mark ($response_s);
}
?>
In this code the keyword abstract for updating as class Marker, and his methods handleCondition () and mark () is used. The designer saves the raw line with a condition (transferred{handed} through argument $condition_s) in attribute, and then causes an abstract method handleCondition (), leaving details of its{her} further processing to affiliated classes.
Any affiliated class of an abstract super class should include realization of all abstract methods of the parent or itself should be abstract. If you will not make it, PHP will give out a mistake:
class WrongMarker extends Marker {
}
// Fatal error: Cannot instantiate abstract class WrongMarker in..
At realization of abstract methods, you should define{determine} the same quantity{amount} of arguments, specifying type of arguments, everywhere, where he has been specified in definition of an abstract method. You should be convinced also, that the level of access to a method (public, protected or private) is not more rigid, than declared in an abstract ancestor. Easier speaking, the announcement of your method should coincide with the announcement of an abstract method which you realize completely.
Thus, the abstract class guarantees realization for all methods. Two affiliated classes Marker.
<? php
class MatchMarker extends Marker {
protected function handleCondition ($condition_s) {
// Realization is not required
}
function mark ($response_s) {
return ($this-> condition == $response_s);
}
}
class ListMarker extends Marker {
protected $condWords = array ();
protected function handleCondition ($condition_s) {
$this-> condWords = preg_split ("/s *, s* / ", $condition_s);
}
function mark ($response_s) {
$respWords = preg_split ("/s *, s* / ", $response_s);
$commonTerms = array_intersect ($this-> condWords, $respWords);
if (count ($commonTerms) == count ($this-> condWords)) {
return true;
}
return false;
}
}
?>
Bearing{Taking out} various realizations in separate classes, we get rid of complex conditions and use of flags which so complicated previous class Marker. Certainly, your code all the same should define{determine} somehow what from classes to use, but it is possible to do{make} it with the help of separate check.
Class MatchMarker realizes simple comparison of lines. ListMarker too realizes rather rough variant of comparison. The class accepts a line of conditions and the answer of the user as the list of the words divided{shared} by a point and uses regular expression to clean{remove} superfluous blanks, and only. Input of the user should contain not less terms, than in a condition, and each term should on a spelling in accuracy coincide with the answer, differently the answer to a question will not be included as correct. Such code is insufficiently flexible for real use, but for our problems{tasks} he will approach.
Code for check of our classes:
$questions [] = new Question (" How much types of the data in PHP? ", new MatchMarker ("12"));
$questions [] = new Question (" Name the keywords determining access in classes. ", new ListMarker (" private, public, protected "));
$answers = array ("12", " private, public, protected ");
foreach ($questions as $q) {
$q-> answer (array_shift ($answers));
print " Search: {$q-> prompt} n ";
print " the Answer: {$q-> response} n ";
print " the Bill: {$q-> score} nn ";
}
// A conclusion:
// Search: How much types of the data in PHP?
// The answer: 12
// The bill: 1
//
// Search: Name the keywords determining access in classes.
// The answer: private, public, protected
// The bill: 1
Having divided{shared} realizations of a rating, we have ceased to make a decision, what variant of processing to use for the given line with conditions. In this case this choice is done{made} in a code of an example. The problem of a choice of one of realizations of an abstract super class often meets in OOP. Usually this choice is realized with use of a pattern factory.
The structure of our classes now looks as follows:
Despite of all these changes which have occurred with class Marker, we till now did not touch class Question. Class Question works and with object such as Marker, and the type of this object is rigidly registered in definition of the designer. Even in spite of the fact that we have created two new classes, that they pronasledovany from class Marker, has made all these changes transparent for class Question.
Use of special classes which give only the general{common} interfaces, is called as switching of classes or polymorphism. Hiding details of realization from a client code, we reduce interdependence of classes to a minimum that does{makes} a code more modular and suitable to a reuse.
Types of classes and abstract classes in PHP 5 allow to work with objects much more confidently. Abstract classes can be emulated and in PHP 4. It is possible to use different circuits imenovanija or to define{determine} abstract methods, which are comprised by operators die (). For example:
<? php
class Marker {
//..
function handleCondition ($condition_s) {
die (" handleCondition - an abstract method ");
}
function mark ($response_s) {
die (" handleCondition - an abstract method ");
}
}
?>
Defining{Determining} the abstract methods thus, you force affiliated classes to redefine them without fail. A problem of such approach that if we have not redefined such "abstract" method in an affiliated class, we shall not see it until we shall call this method. In PHP 5 in this case will give out a mistake at any attempt of use of a class.
7. We work with types of classes: interfaces
As we already saw, the object can belong more than to one type. The object belonging to any class, at the same time belongs and to all parental classes in hierarchy of inheritance.
The class can be pronasledovan only from one class, accordingly in PHP 4 object can belong only to one family of types. As against him PHP 5 supports interfaces: klassopodobnye the structures, allowing to place object at once in some families of types, guaranteeing thus, that the object supports more than one set of methods.
Interfaces appear by means of a keyword interface. Basically they are similar to abstract classes, but as against them should not comprise realization of methods at all. The interface comprises only attributes and abstract methods.
The interface for a conclusion of the XML-given.
<? php
interface XMLable {
public abstract function toXML ();
}
?>
The class can realize the interface, using a keyword implements together with a name of the sold interface. This line is located in the announcement of a class after a line extends.
class MyClass extends ParentClass implements anInterface {
//..
}
Any class realizing the interface should give realization for all methods determined in the interface, or itself should be declared as abstract. The class can realize set of interfaces, thus names of sold interfaces are simply added by the line implements:
class MyClass implements anInterface, anotherInterface {
//..
}
The added version of class Question realizing interface XMLable:
class Question implements XMLable {
// We shall pass{miss} methods and attributy
function toXML () {
return
<<<XMLFRAGMENT
<question>
<prompt> {$this-> prompt} </prompt>
<response> {$this-> response} </response>
<condition> {$this-> marker-> getCondition ()} </condition>
</question>
XMLFRAGMENT;
}
}
Object Question now belongs and to type Question, and to type XMLable. In realization of a method toXML () we create a XML-line containing the data of object Question. In a reality it was meaningful to use expansion DOM or SimpleXML, but in this example clearness for us is more important than flexibility.
Now imagine a class which is not having the attitudes{relations} to class Question. For example, class User which comprises a login name and it e-mail. Let's sell an example, in which class User (pronasledovannyj from certain class Individual) will realize also interface XMLable.
<? php
class Individual {
public $name = " ";
public $email = 0;
function __ construct ($name_s, $email_s) {
$this-> name = $name_s;
$this-> email = $email_s;
}
}
class User extends Individual implements XMLable {
function getLastLogin () {
//..
}
function toXML () {
return
<<<XMLFRAGMENT
<userdata>
<name> {$this-> name} </name>
<email> {$this-> email} </email>
</userdata>
XMLFRAGMENT;
}
}
?>
Class User has no anything the general{common} with class Question except that both of them realize interface XMLable.
It means, that a certain foreign class can work with objects of classes Question and User as with objects such as XMLable, not paying attention to their basic type. A class which and acts:
class ObjectDump {
private $xmlStr;
private $objects = array ();
function addXMLable (XMLable $object) {
$this-> objects [] = $object;
}
function output () {
$xmlStr = " <odump> n ";
foreach ($this-> objects as $output) {
$xmlStr. = $output-> toXML ();
}
$xmlStr. = " </odump> n ";
return $xmlStr;
}
function __ toString () {
return $this-> output ();
}
}
Class ObjectDump accepts objects such as XMLable, using the instruction{indication} of accepted type at the announcement of a method addXMLable (). Irrespective of, as far as different objects transmitted to him, ObjectDump knows, that all of them contain realization of a method toXML (), and all this, that is necessary for him for normal job. Object ObjectDump uses this knowledge in a method output () which passes on all objects XMLable transferred{handed} to him and unites a conclusion of their methods toXML () in one XML-line. By itself, all our knowledge of these objects are reduced to that each of them has method interesting us. Typification can be applied to arguments of object, but it does not impose restrictions on type of returned values. It is necessary to document well interfaces that on their basis it was possible to create precisely working classes.
As well as abstract classes, interfaces describe characteristics on which the client can lean{base} at job with object. Itself PHP works with interfaces, defining{determining} built - in interfaces Iterator and IteratorAggregate. Realize them, and you will need to realize a set of methods required by the interface iterator on the one hand and a class - collection with another.
When PHP meets object such as IteratorAggregate inside the operator foreach, he uses realized in him and in object Iterator created by him the methods necessary for detour of a collection.
These interfaces are convenient for using for three reasons:
* The predetermined functionality. PHP knows, that he can cause methods valid () and current () (besides other) in any class realizing interface Iterator.
* Interfaces do not prevent normal inheritance. Your class can inherit other class and at the same time realize interface Iterator.
* You define{determine} in the realization how to process a collection. Have wanted to realize a certain filter? Forward, have the full right.
8. The resume
PHP allows to work with types very freely, and both with built - in, and with user. You can lead a variable to to any type, and in most cases you can pass a method or function argument of any type. This flexibility it is possible and it is necessary to use. But often, limiting accepted types of the data with the help of reduction of types, you can make your classes more reliable and steady against failures.
Abstract classes and interfaces are used to separate the interface and his realizations, cleaning{removing} the confusing balls of an interdependent code and grouping the connected functions.
Reduction of types is supplemented with abstract classes and interfaces. Abstract methods with tipizovannymi arguments define{determine} the interface on which it is possible to rely. On the other hand, typification is inaccessible neither to simple types, nor for the values returned by methods. Therefore they need to be supervised manually.
9. About the author
Meht Zandstra (Matt Zandstra) - the writer and the adviser specializing on the server software. With partner Maksom Guljelmino (Max Guglielmino) he operates firm Corrosive, technical agency on trainings and planning in open ON and to open standards and develops Internet - applications.

|