1. Introduction
Implementing mandatory object types in PHP can sometimes be very important. If it's missing, either due to a lack of knowledge - based on incorrect programming assumptions, or simply laziness - then you'll see results in your particular web application that you don't expect. Especially when programming in PHP 4, it is very easy to use the "is_a()" function (although there are other methods) to verify the type of the object you are working with. Of course, forcing object types can also be used to filter input objects that need to be passed as parameters to other PHP classes in the same application.
However, PHP 4 does not expose some weaknesses about its object model - it may occasionally require writing additional code in order to implement certain features found in mature object-oriented languages. This fact has been known to the PHP community for a long time. However, with the release of PHP 5, many of these extremely valuable features were added as part of the improved object model. They will help implement object-based code development more closely - allowing you to use specific object characteristics.
In the above case, special care should be taken when it comes to object type coercion. In fact, PHP 5 provides developers with at least two ways to check object types during the execution of a web application - they are the "instanceof" operator and the "type hint" feature. Now turning to the main topic of this article, I will introduce the use of the "instanceof" operator in PHP 5; you will soon find that it can be very convenient for determining whether the object you are working with belongs to a specific type.
This article will help you understand how to implement mandatory object types in PHP 5 through some object-oriented examples.
2. What You Should Not Do
To show how to implement object type coercion in PHP 5, I will use the (X)HTML widget class, and a simple page builder class, with simple modifications to suit PHP 5 development. environment.
My first example lists some (X)HTML widget classes that derive from an abstract base class "HTMLElement", which skips checking the type of their input objects. Please look at the following class first:
//Define the abstract class 'HTMLElement'
abstract class HTMLElement{
protected $attributes;
protected function __construct($attributes){
if(!is_array($attributes)){
throw new Exception('Invalid attribute type');
}
$this->attributes=$attributes;
}
// Abstract 'getHTML()' method abstract protected function getHTML();
}
//Define the specific class 'Div'-extends HTMLElement
class Div extends HTMLElement{
private $output='<div ';
private $data;
public function __construct($attributes=array(),$data){
parent::__construct($attributes);
$this->data=$data;
}
//The specific implementation of the 'getHTML()' method public function getHTML(){
foreach($this->attributes as $attribute=>$value){
$this->output.=$attribute.'="'.$value.'" ';
}
$this->output=substr_replace($this->output,'>',-1);
$this->output.=$this->data.'</div>';
return $this->output;
}
}
//Define the concrete class 'Header1' - extends HTMLElement
class Header1 extends HTMLElement{
private $output='<h1 ';
private $data;
public function __construct($attributes=array(),$data){
parent::__construct($attributes);
$this->data=$data;
}
//The specific implementation of the 'getHTML()' method public function getHTML(){
foreach($this->attributes as $attribute=>$value){
$this->output.=$attribute.'="'.$value.'" ';
}
$this->output=substr_replace($this->output,'>',-1);
$this->output.=$this->data.'</h1>';
return $this->output;
}
}
//Define concrete class 'Paragraph' - extends HTMLElement
class Paragraph extends HTMLElement{
private $output='<p ';
private $data;
public function __construct($attributes=array(),$data){
parent::__construct($attributes);
$this->data=$data;
}
//The specific implementation of the 'getHTML()' method public function getHTML(){
foreach($this->attributes as $attribute=>$value){
$this->output.=$attribute.'="'.$value.'" ';
}
$this->output=substr_replace($this->output,'>',-1);
$this->output.=$this->data.'</p>';
return $this->output;
}
}
//Define the concrete class 'UnorderedList' - extends HTMLElement
class UnorderedList extends HTMLElement{
private $output='<ul ';
private $items=array();
public function __construct($attributes=array(), $items=array()){
parent::__construct($attributes);
if(!is_array($items)){
throw new Exception('Invalid parameter for list items');
}
$this->items=$items;
}
//The specific implementation of the 'getHTML()' method public function getHTML(){
foreach($this->attributes as $attribute=>$value){
$this->output.=$attribute.'="'.$value.'" ';
}
$this->output=substr_replace($this->output,'>',-1);
foreach($this->items as $item){
$this->output.='<li>'.$item.'</li>';
}
$this->output.='</ul>';
return $this->output;
}
}
As you can see, the (X)HTML widget classes above are very useful for generating specific elements in a web, but I intentionally wrote the code for each class so that they cannot validate the input parameters. effectiveness. As you might have imagined, input parameters are passed directly into the class constructor and assigned as properties. The question arises: is there anything wrong with doing this? Yes, there is. Now, I'm going to define my simplest page builder class and feed it with widgets like this so you can see how the input to this class is mixed with incorrect objects. Here is the signature of the page generator class:
class PageGenerator{
private $output='';
private $title;
public function __construct($title='Default Page'){
$this->title=$title;
}
public function doHeader(){
$this->output='<html><head><title>'.$this-
>title.'</title></head><body>';
}
public function addHTMLElement($htmlElement){
$this->output.=$htmlElement->getHTML();
}
public function doFooter(){
$this->output.='</body></html>';
}
public function fetchHTML(){
return $this->output;
}
}
Now, we start instantiating some (X)HTML widget objects and passing them to the corresponding generator classes, as shown in the following example:
try{
//Generate some HTML elements $h1=new Header1(array('name'=>'header1', 'class'=>'headerclass'), 'Content for H1
element goes here');
$div=new Div(array('name'=>'div1','class'=>'divclass'),'Content for Div element
goes here');
$par=new Paragraph(array('name'=>'par1','class'=>'parclass'),'Content for Paragraph
element goes here');
$ul=new UnorderedList(array ('name'=>'list1', 'class'=>'listclass'), array
('item1'=>'value1', 'item2'=>'value2', 'item3'=>'value3'));
//Instantiate the page generator class $pageGen=new Page Generator();
$pageGen->doHeader();
//Add 'HTMLElement' object $pageGen->addHTMLElement($h1);
$pageGen->addHTMLElement($div);
$pageGen->addHTMLElement($par);
$pageGen->addHTMLElement($ul);
$pageGen->doFooter();
//Display web page echo $pageGen->fetchHTML();
}
catch(Exception $e){
echo $e->getMessage();
exit();
}
After running the above PHP code, the result you get is a simple web page - it contains some (X)HTML objects created earlier. In this case, it's easy to understand what will happen if for some reason the page builder class receives an incorrect object and calls its "addHTML()" method. Here, I've reworked the conflict condition - by using a non-existent (X)HTML widget object. Please look at the following code again:
try{
//Generate some HTML elements $h1=new Header1(array('name'=>'header1', 'class'=>'headerclass'), 'Content for H1
element goes here');
$div=new Div(array('name'=>'div1','class'=>'divclass'),'Content for Div element
goes here');
$par=new Paragraph(array('name'=>'par1','class'=>'parclass'),'Content for Paragraph
element goes here');
$ul=new UnorderedList(array ('name'=>'list1', 'class'=>'listclass'), array
('item1'=>'value1', 'item2'=>'value2', 'item3'=>'value3'));
//Instantiate the page generator class $pageGen=new Page Generator();
$pageGen->doHeader();
//Add 'HTMLElement' object $pageGen->addHTMLElement($fakeobj) //Pass non-existent object to this method $pageGen->addHTMLElement($div);
$pageGen->addHTMLElement($par);
$pageGen->addHTMLElement($ul);
$pageGen->doFooter();
// Display the page echo $pageGen->fetchHTML();
}
catch(Exception $e){
echo $e->getMessage();
exit();
}
In this case, as shown in the following line:
$pageGen->addHTMLElement($fakeobj)//Pass non-existent object to this method
A non-existent (X)HTML widget object is passed to the page generator class, this will result in a fatal error:
Fatal error: Call to a member function on a non-object in
What about
path/to/file
?This is a direct penalty for not checking the type of the object passed to the generator class! So be sure to keep this in mind when writing your scripts. Fortunately, there is a simple solution to these problems, and this is where the power of the "instanceof" operator comes in. If you want to see how this operator is used, read on.
3. Use the "instanceof" operator.
As you can see, the use of the "instanceof" operator is very simple. It uses two parameters to complete its function. The first parameter is the object you want to check, and the second parameter is the class name (actually an interface name) used to determine whether the object is an instance of the corresponding class. Of course, I used the terminology above intentionally so that you can see how intuitive this operator is to use. Its basic syntax is as follows:
if (object instanceof class name){
//Do something useful
}
Now that you understand how this operator is used in PHP 5, let's define the corresponding web page builder class in order to verify the type of object passed to its "addHTMLElement()" method. Here is the new signature of this class, which I mentioned earlier uses the "instanceof" operator:
class PageGenerator{
private $output='';
private $title;
public function __construct($title='Default Page'){
$this->title=$title;
}
public function doHeader(){
$this->output='<html><head><title>'.$this->title.'</title></head><body>';
}
public function addHTMLElement($htmlElement){
if(!$htmlElement instanceof HTMLElement){
throw new Exception('Invalid (X)HTML element');
}
$this->output.=$htmlElement->getHTML();
}
public function doFooter(){
$this->output.='</body></html>';
}
public function fetchHTML(){
return $this->output;
}
}
Note, in the above class, how the "instanceof" operator is included in the "addHTMLElement()" method in order to ensure that all passed objects are instances of the "HTMLElement" class defined earlier. Now, it is possible to reconstruct the web page you saw earlier, in which case make sure that all input objects passed to the web page builder class are real (X)HTML widget objects. Here is the corresponding example:
try{
//Generate some HTML elements $h1=new Header1(array('name'=>'header1', 'class'=>'headerclass'), 'Content for H1 element goes here');
$div=new Div(array('name'=>'div1', 'class'=>'divclass'), 'Content for Div element goes here');
$par=new Paragraph(array('name'=>'par1','class'=>'parclass'),'Content for Paragraph element goes here');
$teststr='This is not a HTML element';
//Instantiate the page generator class $pageGen=new Page Generator();
$pageGen->doHeader();
//Add 'HTMLElement' object $pageGen->addHTMLElement($teststr) //Pass a simple string to this method $pageGen->addHTMLElement($h1);
$pageGen->addHTMLElement($div);
$pageGen->addHTMLElement($par);
$pageGen->doFooter();
//Display web page echo $pageGen->fetchHTML();
}
catch(Exception $e){
echo $e->getMessage();
exit();
}
As you have seen in the example above, I am passing a simple test string (not an "HTMLElement" object) into the page builder class, which will throw an Exception - caught by a specific "catch" block, as shown below:
Invalid (X)HTML element
At this time, in order to determine the validity of the input object, I used the "instanceof" operator, so that the above web page can be I hope you can really appreciate the importance of filtering the input to your class's methods to avoid extraneous errors by using this operator. Data input.
After showing the correct implementation of the "instanceof" operator inside the web page generator class, there is more to do similar to what I wrote for PHP 4 in the previous article (X). For HTML widget classes, I would like to include this operator as part of their "getHTML()" method, thus allowing the creation of web pages that generate nested (X)HTML elements. Let's discuss how this is accomplished.
4. Extend the use of the "instanceof" operator: Nested (X)HTML widgets
are good. You have seen that the "instanceof" operator performs well in type checking on input objects that are directly injected into the page builder class. functionality. Now, I will go one step further and add a check routine to the constructor and "getHTML()" method of the (X)HTML widget class so that they can accept other widgets as input parameters. Please check the improvements below. Class:
class Div extends HTMLElement{
private $output='<div ';
private $data;
public function __construct($attributes=array(),$data){
if(!$data instanceof HTMLElement&&!is_string($data)){
throw new Exception('Invalid parameter type');
}
parent::__construct($attributes);
$this->data=$data;
}
//The specific implementation of the 'getHTML()' method public function getHTML(){
foreach($this->attributes as $attribute=>$value){
$this->output.=$attribute.'="'.$value.'" ';
}
$this->output=substr_replace($this->output,'>',-1);
$this->output.=($this->data instanceof HTMLElement)?
$this->data->getHTML():$this->data;
$this->output.='</div>';
return $this->output;
}
}
class Header1 extends HTMLElement{
private $output='<h1 ';
private $data;
public function __construct($attributes=array(),$data){
if(!$data instanceof HTMLElement&&!is_string($data)){
throw new Exception('Invalid parameter type');
}
parent::__construct($attributes);
$this->data=$data;
}
//The specific implementation of the 'getHTML()' method public function getHTML(){
foreach($this->attributes as $attribute=>$value){
$this->output.=$attribute.'="'.$value.'" ';
}
$this->output=substr_replace($this->output,'>',-1);
$this->output.=($this->data instanceof HTMLElement)?
$this->data->getHTML():$this->data;
$this->output.='</h1>';
return $this->output;
}
}
class Paragraph extends HTMLElement{
private $output='<p ';
private $data;
public function __construct($attributes=array(),$data){
if(!$data instanceof HTMLElement&&!is_string($data)){
throw new Exception('Invalid parameter type');
}
parent::__construct($attributes);
$this->data=$data;
}
//The specific implementation of the 'getHTML()' method public function getHTML(){
foreach($this->attributes as $attribute=>$value){
$this->output.=$attribute.'="'.$value.'" ';
}
$this->output=substr_replace($this->output,'>',-1);
$this->output.=($this->data instanceof HTMLElement)?
$this->data->getHTML():$this->data;
$this->output.='</p>';
return $this->output;
}
}
class UnorderedList extends HTMLElement{
private $output='<ul ';
private $items=array();
public function __construct($attributes=array(), $items=array()){
parent::__construct($attributes);
if(!is_array($items)){
throw new Exception('Invalid parameter for list items');
}
$this->items=$items;
}
//The specific implementation of the 'getHTML()' method
public function getHTML(){
foreach($this->attributes as $attribute=>$value){
$this->output.=$attribute.'="'.$value.'" ';
}
$this->output=substr_replace($this->output,'>',-1);
foreach($this->items as $item){
$this->output.=($item instanceof
HTMLElement)?'<li>'.$item->getHTML().'</li>':'<li>'.$item.'</li>';
}
$this->output.='</ul>';
return $this->output;
}
}
As shown in the above classes, in order to allow nested (X)HTML elements to be implemented when generating the corresponding web pages, I refactored their constructors and "getHTML()" methods respectively. Note that I included the following conditional block in the constructor of each class:
if(!$data instanceof HTMLElement&&!is_string($data)){
throw new Exception('Invalid parameter type');
}
At this point, what I actually do is make sure that only string data and objects of type "HTMLElement" are allowed as input parameters for each class. Otherwise, an exception will be thrown by the respective method and may cause the application to stop execution. So, this is the process of checking the input data. Now, let's look at the new signature of the "getHTML()" method, which also uses the "instanceof" operator:
$this->output.=($this->data instanceof HTMLElement)?$this->data-
>getHTML():$this->data;
As you can see, in this case, the this operator is very useful for taking advantage of the polymorphic features of the (X)HTML widget class. If the $data attribute is also a widget, then its "getHTML()" method will be called correctly, which will cause the nested web element to be displayed. On the other hand, if it is just a string, then it is added directly to all outputs of the current class.
At this point, you may have understood the usage of the "instanceof" operator in PHP 5 in order to ensure that certain objects belong to a specific type. As you can see in this article, coercing object types in PHP 5 is actually a fairly straightforward task. For now, you'd better develop an example of using this method to filter objects in your PHP application to deepen your understanding.
5. Summary
In this article, you learned how to use the "instanceof" operator in PHP 5 to check the type of your input object; however, the method I showed you is not the only one. In a later article, I'll explain to you how to implement the nice "type hinting" feature in PHP 5, which is another way to enforce object typing.
Author: Zhu Xianzhong Compiler Source: Tianji Development