Object-oriented programming
The content here is under the Attribution 4.0 International (CC BY 4.0) license
As PHP evolved, the importance given to Object Oriented Programming (OOP) grew. I would venture to say that, starting with version 5.4 of the language, we saw many gains focused on OOP.
We will go through creating classes, using magic methods, access modifiers, the difference between the reserved words self
and static
, and much more. A shorter version of this page is available as well, it is a shallow intro in comparison with
Creating a class
When we are working with Object Oriented Programming, the first thing that comes to mind logically are objects, in PHP. We use the reserved word class
to define the design of our object.
See in our example below the class Cup
which, despite having nothing inside, is a valid class.
class Cup {
}
Now that we have the structure of the object, we need to add its properties, since making a comparison with the real world, they are the characteristics of a real glass. And what characteristics can a glass have? Size, color and weight are some of them.
For our class, we will only use the first two: size and color.
class Cup {
public $size;
public $color;
}
Now, our object already has some defined properties that it can use. See below how we actually create our object in PHP, and note that to access and set values in our objects, we use a right arrow with the characters -
and >
.
$cup = new Cup();
$cup->size = 'Large';
$cup->color = 'transparent';
In PHP, we cannot force any types, such as arrays, classes or interfaces, into object properties as we can in functions.
The first thing to notice is the use of the reserved word new
to instantiate our object. The second thing is its use in the variable $cup
. Next, we see our object in detail:
Object Cup
(
[size] => Large
[color] => transparent
)
Very interesting! However, so far, our object cannot do anything, it only has some properties (characteristics), but it does not have any behavior or action. Next, let’s add a method that will bring our object to life.
Methods are like functions: their signatures are exactly the same, but in Object Oriented Programming, methods have some additional features that we will see next, such as access modifiers (
public
,private
,protected
). But remember that within classes, functions are called methods.
class Cup {
public $size;
public $color;
function addDrink($drink) {
print 'Chosen drink' . $drink;
}
}
We created our first method that actually doesn’t do much other than display the chosen drink. But as you can see, methods are exactly like functions, but within a class. And to access them, we use the same characters to access properties.
$cup = new Cup();
$cup->adddrink('water'); // Drink chosen: water
To inspect the methods that objects have, we can use the get_class_methods
function, which will return an array containing all the class’s methods:
Array
(
[0] => addDrink
)
Constants can also be defined for our objects with the reserved word const
. We will create a constant in our Cup
class called LIMIT
,
which will define the maximum limit that our object can receive drinks. We will also modify the addDrink
method so that, in addition to the drink
chosen, receive as a parameter how much of it will be added:
class Cup {
const LIMIT = 100;
public $size;
public $color;
function addDrink($drink, $quantity) {
if ($quantity > self::LIMIT) {
print 'The quantity exceeds the limit supported by the cup';
exit();
}
print 'Chosen drink' . $drink;
}
}
By convention, we define constants using only uppercase letters, but we can also use lowercase letters. However, remember that it is not advisable due to the code standards that PHP follows (see more at http://www.php-fig.org/). Therefore, always follow the convention. Below is an example just to illustrate:
class Cup {
const limit = 100;
}
Unlike attributes and methods, for constants we use ::
to access their values. We can access them in two ways: one is through the instantiated object, as we see:
$cup = new Cup();
print $cup::LIMIT; //100
And the other is using the full class name, without its instance:
$cup = new Cup();
$cup::LIMIT; //100
Inheritance
Inheritance is a well-known technique in OOP, in which we can inherit all the behavior of a class, eliminating code duplication and
facilitating its maintenance. In PHP, we can inherit the behavior of a class using the reserved word extends
.
The first thing we will do is create a class called Casa
to represent the base class, and then we will create the class CasaReformada
, which will extend the Casa
class and, in addition to the inherited behavior, will have its specific method .
class Home {
public $color;
public $quantityOfRooms;
function openRoomDoor()
{
print 'Open room door';
}
}
Let’s suppose that our house, represented by the House
class, has been renovated and now we can, in addition to opening the living room door, also open the bedroom window. But, instead of changing the Casa
class, let’s just create another class and apply what changed in the reform.
class CasaReformada extends Casa {
function openJanelaDoQuarto()
{
print 'Bedroom window open';
}
}
When using inheritance, it is not necessary to rewrite the entire class. Instead, we can just inherit what the Casa
class has, and in the CasaReformada
class, we can just put what we want.
In PHP, there is no multiple inheritance, and it is not possible to inherit from multiple classes at the same time.
Now, in addition to having the methods and properties of the CasaReformada
class, we also have those of the Casa
class.
$casaReformada = new CasaReformada();
$casaReformada->cor = 'Blue';
$casaReformada->abrirPortaDaSala(); // Room door open
$casaReformada->openBedroomWindow() // Bedroom window open
Abstract class
Now that we understand inheritance, we can use abstract classes with PHP. Many times, we want to create a hierarchy, but without the need to create a concrete class (that can be instantiated). In these cases, we use abstract classes that provide us with a template for this.
Its syntax is very simple, just put the reserved word abstract
before the reserved word class
:
abstract class MyAbstractClass {}
We could use an abstract class to represent a web service, for example. We know that we need at least two actions: one to handle incoming requests, and another to handle responses to be sent to the client.
See how we can create a template for this web service:
abstract class WebService {
abstract function Handleequest();
abstract function handleResponse();
}
After that, we can specialize a different behavior for each type of web service, to handle requests and responses. Our first specialization will be made to handle requests/responses made with JSON:
class WebServiceJson {
public function HandleRequest()
{
// handles data sent as JSON type
}
public function handleResponse()
{
// send JSON response
}
}
Now imagine that we must manipulate such data, but in XML, supporting both JSON and XML. Thanks to our WebService
template, we just need to create one more specialization to handle XML.
class WebServiceXml {
public function HandleRequest()
{
// manipulate data sent as XML type
}
public function handleResponse()
{
// send XML type response
}
}
Through this type of processing, we have the possibility of manipulating both types of data. See below how simple the code is without the need for if
to identify which type of data we should use. The most important thing is that, if in the future there is a need to create another type of web service data manipulation, it will only be necessary to create a new class with the specific treatment.
// Manage JSON type requests
$json = new WebServiceJson();
$json->handleRequest();
// Manage XML type requests
$xml = new WebServiceXml();
$xml->handleRequest();
Unlike “normal” classes, abstract classes cannot be instantiated and, if you try to instantiate it, a FATAL ERROR
is displayed, as shown in the example:
$abstract = new WebService(); // attempt to instantiate
PHP Fatal error: Cannot instantiate abstract class WebService in /my_dir/oop/classe_abstrata.php on line 5
PHP stack trace:
PHP 1. {main}() /my_dir/oop/classe_abstrata.php:0
Fatal error: Cannot instantiate abstract class WebService in /my_dir/oop/classe_abstrata.php on line 5
Call Stack:
0.0001 230544 1. {main}() /my_dir/oop/classe_abstrata.php:0
We use abstract classes to create a generalization and then specialize specific behaviors in other classes. Let’s look at a new example using the types of people:
abstract class Person {
public abstract function floor();
}
class Adult extends Person {
public function walk() {
print 'Quick';
}
}
class Child extends Person {
public function walk() {
print 'Slowly';
}
}
As in our example, we have an abstract class (contract) called Person
, and we also have two classes that inherit from Person
, which are the Adult
and Child
classes.
The first thing we can notice is that both the Person
and Child
classes override the walk
method of our abstract class, and this is no coincidence. The second thing we should note is the use of the reserved word abstract
in the declaration of the andar()
method.
With abstract classes, we can declare methods without a body, using the abstract
reserved word. This means that the inheriting class is forced to implement this method. In our example, we use this type of behavior so that classes that inherit from Person
define how they walk, as adults and children walk differently, even if they are both the same type.
If an abstract method exists and it is not overridden in the child class, PHP will display a FATAL ERROR
. Let’s try to create an Elder
class, and just extend the Person
class:
class Elderly extends Person {
}
$older = new Elderly();
See that we only extended the Pessoa
class and did not override the andar
method, which results in the fatal error, warning that we are forced to implement the andar
method.
PHP Fatal error: Class Idoso contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Pessoa::andar) in /my_dir/oop/abstract.php on line 11
We can also define complete methods to be used by child classes. Let’s modify our abstract class from the previous example and add the eat
method, but this time this method will be concrete and will not need to be overridden by the child classes (which inherit from the Person
class).
abstract class Person {
public abstract function floor();
public function eat()
{
print 'Coming method invoked on object ' . get_class($this);
}
}
The first interesting thing about our modification is the power that the abstract class gives us. We can force the child classes to implement the methods we want (in our example, we force the implementation of the andar
method) and, in addition, we can create our own methods in the abstract class.
Another point to note is the use of the word $this
within the comer
method. As we cannot create instances of the Person
class, the $this
context will always refer to the child class that was instantiated. See below that the response when using the eat
method changes according to the class we are using:
$adult = new Adult();
$adult->eat(); // Eat method invoked on the Adult object
$child = new Child();
$child->eat(); // Eat method invoked on the Child object
Trait
Starting with PHP version 5.4, a new concept was added to the language to overcome the lack of multiple inheritance. With that, trait
came to life. Just like an abstract class, a trait
cannot be instantiated, and we must use it together with a class.
A common task in which we can use trait
s is the famous logs. This way, it is possible to know what happens in different parts of the system, without having to duplicate code or create separate classes for this task. Logs are useful in production environments where the end customer has access and where we, programmers, cannot display data directly on the screen.
trait Log {}
A trait
can have methods and properties, just like a normal class:
trait Log {
public function record($message)
{
return file_put_contents('log.txt', $message);
}
}
To use a trait
, we must first import it into our class with the reserved word use
. Note that, unlike classes that we import right after declaring the namespace
, trait
s must be imported within the scope of the class.
class LogManager {
use Log;
}
This way, we have access to all trait
properties and methods within the LogGerenciador
class.
$LogManager = new LogManager();
$logger->record('log message'); // method existing in the trait
We must pay attention to inheritance precedence when using trait
s. because elements of the class we import it into override existing methods in it.
class LogManager {
use Log;
public function record($message)
{
print 'This method overrides the existing method in the trait';
}
}
When we try to access the write
method now, instead of saving the content to the file with the file_put_contents
function, the method will just display the message: "This method overwrites the existing method in the trait"
.
$LogManager = new LogManager();
// This method overrides the existing method in the trait
$logger->record('log message');
A very interesting behavior of trait
is that we can use them with abstract methods, as well as in abstract classes. This allows us to create a generalization and force anyone who uses trait
to implement the methods we want.
To illustrate, let’s use a Facebook post as an example. We will create a general trait
in which the use of the message
method will be mandatory. In other words, for each class that uses our trait
, there must be a method that returns a message. After all, it is not possible to create a post without text.
trait FacebookPost {
// All posts must contain a message
abstract public function message();
}
Now that we have our base trait
for the posts we are going to make, we can use it. To do this, we will create a class called PostSimples
, which will only post text and nothing else.
trait PostSimple {
public function message()
{
return 'Post containing text only';
}
}
Now imagine that, in addition to the text message, we also need to create a post that contains images. How could we do this? With trait
s, solving this problem is very easy. Let’s create a new class called ImagemPost
, which will be responsible for managing the image and message for our post.
As the FacebookPost
trait has an abstract method, we are forced to implement it ensuring that the image post will also have a text message.
class PostImage {
use FacebookPost;
public function message()
{
return 'Message from ImagePost class';
}
public function image()
{
return 'facebook.png';
}
}
If we try to use a trait
that has an abstract method and do not override it, a FATAL ERROR
will be displayed by PHP.
class PostImage {
use FacebookPost;
}
PHP Fatal error: Class ImagemPost contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (ImagemPost::mensagem) in /my_dir/oop/trait.php on line 9
PHP stack trace:
PHP 1. {main}() /my_dir/oop/trait.php:0
In addition to the use shown here of traits, there are some interesting small nuances, such as, for example, modifying the access of each method in the class in which the trait is used and method overriding. See the official PHP documentation, at http://php.net/manual/language.oop5.traits.php, which has this additional information.
##interface
Interfaces are a little further beyond generalization than abstract classes. Imagine that interfaces are like contracts, in which whoever implements them must follow the rules. However, it doesn’t matter how you get the result and, unlike abstract classes, interfaces do not have a body in their methods.
interface Car {}
Defining an interface is very simple, as we can see in this example. However, our interface does not have any methods. Unlike abstract classes, which can have complete method definitions, interfaces can only have their definitions.
interface Car {
public function accelerate();
public function stop();
}
To implement our interface, we must use the reserved word implements
.
class EconomyCar implements Car {}
The script generates a FATAL ERROR
, because, when we use interfaces, we are forced to implement all the methods defined in it. Interfaces are like contracts and, as in a contract, we must follow everything that is written, and implement all existing methods in the interface.
PHP Fatal error: Class CarroEconomico contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Carro::acelerar, Carro::parar) in /my_dir/oop/interface.php on line 10
PHP stack trace:
PHP 1. {main}() /my_dir/oop/interface.php:0
Let’s correct our class so that it works with our interface:
class EconomyCar implements Car {
public function accelerate() {}
public function stop() {}
}
Final
Just as we can inherit properties and methods from classes, we can also prevent this behavior. In other words, it is possible to block inheritance of the class we created using the final
reserved word.
final class Television {
public $channel = 99;
}
See that we have the Television
object, which has a channel
property. Let’s now try to define a new channel by inheriting this class:
class NovaTelevisao extends Televisao {
public $channel = 99;
}
When executing this script, we get a FATAL ERROR
, as we cannot inherit from any class that is final
.
PHP Fatal error: Class NovaTelevisao may not inherit from final class (Televisao) in /my_dir/oop/final.php on line 9
Fatal error: Class NovaTelevisao may not inherit from final class (Televisao) in /my_dir/oop/final.php on line 9
Therefore, we chose to block the entire class from being inherited, but we can also define only certain methods so that they are not inherited. Let’s then apply this to our Television
class.
class Television {
private $channel = 99;
final public function changeCanal($canal)
{
$this->channel = $channel;
}
}
Now, we can effectively extend the class, but we cannot inherit the mudarCanal($canal)
method.
class NovaTelevisao extends Televisao {}
Now we will try to override the mudarCanal($canal)
method.
class NovaTelevisao extends Televisao {
public function changeChannel($channel) {}
}
We get a FATAL ERROR
as expected, as this method is final
.
PHP Fatal error: Cannot override final method Televisao::mudarCanal() in /my_dir/oop/final.php on line 18
Fatal error: Cannot override final method Televisao::mudarCanal() in /my_dir/oop/final.php on line 18
In class properties, it is not possible to use the reserved word
final
. For this, in PHP, we use the reserved wordconst
.
Access modifiers
In PHP we have three types of access modifiers and in this section we will explore how to use each of them in detail, see below the list of possible access modifiers:
- public (
public
) - protected (
protected
) - private (
private
)
public
The public, as we have already seen, is where we can access our properties and methods freely. Public mode is assumed by PHP by default if we do not declare any type of access, that is, if we do not use the reserved word public
.
For class properties, declaration is mandatory, but for methods, it is not.
class Car {
//restricted word public, protected or private mandatory
public $mark;
// public word omitted
function callMotor()
{
print 'Engine on';
}
}
But it is also possible to make this explicit in the code whenever we want:
class Car {
public $brand;
public function callMotor()
{
print 'Engine on';
}
}
Without restrictions, we can easily use our object as follows:
$myCar = new Car();
$myCar->brand = 'Ford';
$myCar->startEngine();
protected
Now, we start to restrict the access we want using the word protected
. Let’s start with the $brand
property. Let’s change it to be protected, not public anymore.
class Car {
protected $marca = 'GM';
public function callMotor()
{
print 'Engine on';
}
}
Look at this code and, based on what you’ve seen so far, try to guess what the result will be when you run the following code:
$myCar = new Car();
$myCar->brand = 'Ford';
$myCar->startEngine();
When we execute the code, PHP returns the following FATAL ERROR
:
PHP Fatal error: Cannot access protected property Carro::$marca in /my_dir/oop/visibility/protegido.php on line 14
PHP stack trace:
PHP 1. {main}() /my_dir/oop/visibility/protegado.php:0
Fatal error: Cannot access protected property Carro::$marca in /my_dir/oop/visibility/protegido.php on line 14
Call Stack:
0.0002 233888 1. {main}() /my_dir/oop/visibility/protetido.php:0
Unlike the public
modifier, properties and methods that use protected
can only be accessed from within the class itself or from classes that inherit from that class. Let’s use our Car
class as a template for our Truck
class.
class Truck extends Car {
public function displayMarca()
{
print $this->mark;
}
}
As we can no longer directly access the $marca
property, we are forced to create a method to manipulate it.
$caminhao = new Caminhao();
$truck->exhibirMarca(); //GM
###private
We arrive at the most restricted way that a property or method can have, within a class. Using private
, only the class itself has access to the attribute or method.
In our example below, we have the attribute $numberOfRodas
that belongs only to the Car
class. See the modified Car
class:
class Car {
protected $brand = 'GM';
private $numberOfWheels = 4;
public function displayNumeroOfRodas()
{
print $this->numberOfWheels;
}
}
$carro = new Carro();
$car->exhibirNumeroOfRodas();
When we run this script, we have the following result:
4
But, what if we try to extend this class to access the $wheelnumber
attribute? Look at the following class and try to figure out what the output generated by the script will be, before moving on to the answer.
class Van extends Car {
public function __construct()
{
print $this->numberOfWheels;
}
}
$van = new Van();
When we instantiate the Van
class, there is no property in the class called $wheelnumber
, which causes PHP to display the Undefined property error.
PHP Notice: Undefined property: Van::$numeroDeRodas in /my_dir/oop/private.php on line 15
PHP stack trace:
PHP 1. {main}() /my_dir/oop/visibility/private.php:0
PHP 2. PenetaPreta->__construct() /my_dir/oop/private.php:19
This occurs because the property only belongs to the Car
class, and only it can manipulate this property. No class that extends it or any external access attempt can modify it
$this
In our previous examples, to access class properties and methods, we used a special variable called $this
. It is reserved for PHP and cannot be used as any variable, which makes the following example invalid code:
$this = 'ZCPE';
When running this script, we get the following error:
PHP Fatal error: Cannot re-assign $this in /my_dir/oop/this.php on line 3
Fatal error: Cannot re-assign $this in /my_dir/oop/this.php on line 3
This shows us that we cannot use $this
as any variable. $this
refers to the current class instance. We need to use $this
to access any method or property within a class; otherwise, PHP will try to look for a variable within the scope of the method. See the following example:
class File {
private $file = 'file.txt';
public function displayName()
{
print $file;
}
}
$file = new File();
$file->displayName();
What do you think will happen to this code when it runs?
PHP Notice: Undefined variable: file in /my_dir/oop/this.php on line 8
PHP stack trace:
PHP 1. {main}() /my_dir/oop/this.php:0
PHP 2. File->displayName() /my_dir/oop/this.php:13
Notice: Undefined variable: file in /my_dir/oop/this.php on line 8
Call Stack:
0.0001 233264 1. {main}() /mnt/c/wamp/www/github/my_dir/oop/this.php:0
0.0013 233480 2. File->displayName() /mnt/c/wamp/www/github/my_dir/oop/this.php:13
We get the error Undefined variable: archive
, which tells us that the variable $file
does not exist. To fix this error, we need to use $this
to refer to the $file
class property.
...
public function displayName()
{
print $this->file;
}
After this adjustment, we were able to execute the code perfectly and have the file name displayed as a result.
file.txt
All items covered so far about access modifiers are covered in the official PHP documentation, which you can check at http://php.net/manual/language.oop5.visibility.php. In the official documentation, they call visibility instead of access modifiers.
Magic methods
As PHP evolved, the language provided many features for object-oriented programming. One of them is magical methods, which provide us with enormous flexibility and also advantages when using them. All magic methods begin with the characters __
(two underscores) followed by their name. Below is a list of existing methods to date:
Method | Description |
---|---|
__construct | Object constructor. |
__destruct | Object destructor. |
__call | Invoked when a method does not exist on the object. |
__callStatic | Invoked when a static method does not exist on the object. |
__get | Invoked when a non-existing property of the object is used. |
__set | When a property invoked to set its value does not exist, this method is executed. |
__isset | When a member is not found, __isset is invoked using the isset() or empty() functions to perform this check. |
__unset | When declared, this magic method is responsible for receiving the call when the unset() function is used and for non-accessible members. |
__sleep | When you want to confirm some data that is pending for the class to function, to clean properties already set, or deal with very large objects, __sleep is the method you will use for this purpose. |
__wakeup | When we use the method to serialize (in this case, __sleep ), in some contexts we may end up losing a connection to the database. If it is necessary to do the opposite (in this case, deserialize), we use __wakeup and, in addition, we could also reestablish the connection with the database. |
__toString | It is invoked, for example, when you try to execute echo on the object, PHP will convert it to strings and, if your class contains the magic method __toString , the contents of the method will be displayed. |
__invoke | It is invoked when you try to call an object as a function, not to be confused with functions you create or methods. |
__clone | Method invoked when cloning the object with the reserved word clone
|
These are the magic methods that are covered in the PHP 5.5 certification, but there is one more magic method added to version 5.6 of the language. It will not be covered here, as our focus is PHP 5.5. For more information about __debugInfo
, access the official documentation at http://php.net/manual/language.oop5.magic.php#object.debuginfo.
__construct
The __construct
method is well used, as it is what we use to pass arguments in the construction of our object. Previously, in version 4 of PHP, we used the same class name to do this type of work (just as it is done in Java), however, after the implementation of the magic method __construct
, it fell into disuse.
class Book {
public function __construct($author)
{
print $author;
}
}
As we can see, we are using __construct
to make it mandatory to pass the $author
parameter.
$casaDoCodigo = new Livro('Martin Fowler');
When we run this method, the result we get is:
Martin Fowler
__destruct
Just as we have a method to use as soon as we instantiate an object, we also have one that is executed as soon as the object is destroyed from memory. However, notice that this does not receive any arguments:
class Book {
public function __construct()
{
print 'Book Object created';
}
public function __destruct()
{
print 'Destroyed Book Object';
}
}
$book = new Book();
sleep(2);
The sleep
function will help us better visualize when the object was destroyed. When we run this script, we have the following result:
Book object created
Destroyed Book object // will appear after 2 seconds
We can also simulate the destruction of this object using the unset
function. This time, let’s destroy the object before the script finishes executing:
$book = new Book(); Book object created
sleep(1);
unset($book); // Destroyed Book Object
sleep(2);
Through this example, we were able to define exactly when our object will be destroyed. After instantiating the Book
class, the message Book Object created
is displayed and the script execution will be interrupted for 1 second. Right after this 1 second, we destroy the object with the unset
function and the message Book Object Destroyed
is displayed to us.
And again we use the sleep
function to stop the script from executing for 2 seconds. After this time, the script execution ends.
Although we use
__destruct
in conjunction with thesleep
function to destroy objects, we must be careful, as we cannot determine at what time exactly the object will be cleaned from memory through thecarbage collector
. However, the__destruct
method is an excellent place to perform closing operations, such as connecting to databases and data or streams.
__call
Using the __call
method, we can access methods without declaring them within our class. With each call of a method that does not exist in the class or is not accessible (private or protected methods), PHP will look for the __call
method and execute it.
class Cellphone {
public function __call($method, array $args) {
print 'Method ' . $method . 'invoked';
}
}
We define a class with just the magic method __call
, which means that it doesn’t matter which method we call, because that method will always be invoked. See that, in our example, we tried to invoke the call
method. Since it does not exist in our Cellular
class, the __call
method is invoked instead.
$motorola = new Cellular();
$motorola->connect();
The result we get when running the script is the message:
Call method invoked
Also see that we have a second argument in the __call
method. It serves us to receive all arguments sent to the invoked method. All parameters are placed in an array, and we can iterate over this array to get each of these arguments. Next, we modify our Cellular
class so that, in addition to displaying the method that was invoked, it also displays all arguments passed through foreach
.
class Cellphone {
public function __call($method, array $arguments) {
print 'Method ' . $method . ' invoked with arguments' . PHP_EOL;
foreach($arguments as $argument) {
print $argument . PHP_EOL;
}
}
}
Let’s also change the use of the class to define the number of the Cellular
object, invoking the definirNumero
method.
$motorola = new Cellular();
$motorola->setNumber('(11) 1234-1234');
Now we can display the invoked method and also the passed parameters:
defineNumber method invoked with arguments
(11) 1234-1234
The __call
method has some details regarding access modifiers that we will see below. Imagine that we have a method in the Cellular
class called ligarTela
, but this method is protected (protected
), as shown in the example:
class Cellphone {
protected function callScreen($seconds)
{
print 'Turning on cell phone screen by ' . $seconds . 'seconds';
}
public function __call($method, array $arguments) {
print 'Method ' . $method . ' invoked with arguments' . PHP_EOL;
foreach($arguments as $argument) {
print $argument . PHP_EOL;
}
}
}
Now let’s invoke the ligarTela
method, which we just created:
$motorola = new Cellular();
$motorola->connectScreen(2);
What output will we get when running this script?
CallScreen method invoked with arguments
two
The __call
method is invoked. This occurs because the ligarTela
method is not accessible through the instance of the Cellular
class, and the same occurs with private methods. Always remember that if the class method does not exist or is not accessible, the __call
method will be invoked.
In the same way that we used the __call
method, it is possible to use __callStatic
. The differences between these methods are simple: the __callStatic
method is called in the static context, and its signature must also be static.
class Car {
public function __callStatic($method, $arguments)
{
print 'Statically invoked method:' . $method;
}
}
Car::call();
When we run this script, we get the following result:
Statically invoked method :call
__get
As the name suggests, the __get
method is used to return the value of a non-accessible or non-existent property of a class.
class Fan {
public function __get($name) {
print 'Attempt to access property ' . $name;
}
}
As we can see, the __get
method must have the parameter, as it is through this that we will know which property they tried to access from that object.
$fan = new Fan();
$fan->brand;
The result we get when running the script is:
Attempt to access the brand property
__set
As we have just seen, we have a method exclusively to be invoked when a non-existing property is called. We also have a method specifically to be called if they try to set a value to a property that is not accessible or does not exist. This gives us a lot of flexibility in working with objects to set or return values.
In our example below, we created a Fan
class, and only defined the __set
method so that it is possible to define any property within the object, without it actually existing.
class Fan {
public function __set($name, $value) {
print 'Attempt to set the property value ' . $name . ' with the value ' . $value;
}
}
$fan = new Fan();
$fan->price = 90.00;
And the result we get is:
Attempt to set the value of the price property to the value 90
__isset
When a property is not found using the isset
or empty
functions, the __isset
magic method is invoked. See our example below, in which we use the Collection
class simply to store the properties and values within the $dados
array:
class Collection
{
private $data = [];
public function __set($name, $value)
{
echo "Assigning the index '$nome' with the value '$valor'";
$this->data[$name] = $value;
}
}
$obj = new Collection;
$obj->a = 1;
echo $obj->a; // 1
Now that we have our class working, let’s implement the __isset
method. Therefore, every time we use the isset
or empty
function, the __isset
method will be invoked, thus allowing us to check whether the accessed property exists in our $dados
array
class Collection
{
private $data = [];
public function __set($name, $value)
{
echo "Assigning the index '$nome' with the value '$valor'";
$this->data[$name] = $value;
}
public function __isset($name)
{
echo "Check if '$name' was set?";
return array_key_exists($name, $this->data);
}
}
$obj = new Collection();
$obj->a = 1;
$propertyA = isset($obj->a);
var_dump($propertyA);
When we run the script, we have the following result:
Assigning the index 'a' with the value '1'
Check if 'a' was set? bool(true)
Let’s go in parts to make it easier to understand. The first thing we do here is create a Collection
object and assign the value 1
to the a
property. As the property a
does not exist, the __set
method is invoked and, in this way, we store the name of the invoked property (a
) and its value (1
) in the $dados
array.
After that, we use the isset
function to check if the property really exists. By doing this, the __isset
method is invoked and we check whether the property a
exists in our array $data
by the array_key_exists
function. As it was previously defined with the value 1
, true is returned (true
), and we assign this value to the variable $propiedadeA
.
Lastly, we just display the value of the $propertyA
property with the var_dump
function.
We used the
isset
function in our example, but if we change it to theempty
function, we get the same result.
__unset
We can use the __unset
magic method to remove properties defined with the __set
magic method. To make it easier to understand, we will continue with our Collection
class that we used in the previous section.
We’re just going to make a small modification to it to use the __unset
method, and we’re going to remove the __isset
method. See how our Collection
class will look:
class Collection
{
private $data = [];
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __unset($name)
{
// Remove the property only if it exists in the $data array
if (array_key_exists($name, $this->data)) {
unset($this->data[$name]);
}
}
}
The first thing we are going to do to understand the __unset
method is to define some properties. Let’s create two of them: property b
and property c
, both with the value 1
.
$obj = new Collection;
$obj->b = 1;
$obj->c = 1;
print_r($obj);
When executing this script, we have the following result:
Object Collection (
[data:Collection:private] => Array
(
[b] => 1
[c] => 1
)
)
With our properties defined, we can now remove them using the unset
function. When using this function, we automatically invoke the __unset
method within the Colecao
class.
unset($obj->b);
print_r($obj);
Removing the b
property left us with only c
, and we proved this by displaying the entire Colecao
object with the print_r
function.
Removing property b
Object Collection
(
[data:Collection:private] => Array
(
[c] => 1
)
)
__sleep and __wakeup
__sleep
and __wakeup
are methods especially for working with serialization of your object. When PHP’s serialize
function is called on the object, the __sleep
method is invoked if it exists in the class. The same occurs with the __wakeup
method when the unserialize
function is invoked.
class Serialize {
public function __sleep()
{
print 'Method invoked when using the serialize function';
return [];
}
}
serialize(new Serialize());
The first thing we must do is create our __sleep
method in the class, and we must return an array-type value; otherwise, the following NOTICE
will be displayed:
Notice: serialize(): __sleep should return an array only containing the names of instance-variables to serialize in /my_dir/oop/__sleep.php on line 13
PHP stack trace:
PHP 1. {main}() /my_dir/oop/__sleep.php:0
PHP 2. serialize() /my_dir/oop/__sleep.php:13
Executing this code will display the following message:
Method invoked when using the serialize function
Once serialized, we can also return the state of the object using the unserialize
function. Let’s change our class that we used as an example for this:
class Serialize {
public function __wakeup()
{
print 'method invoked when using the unserialize function';
}
}
$serialized object = serialize(new Serialize());
unserialize($objectSerialized);
Unlike the __sleep
method, we do not need to return any value when implementing the __wakeup
method. The only thing we need to make sure of before using the unserialize
function is to invoke the serialize
method first; otherwise, the following WARNING
will be displayed:
PHP Warning: unserialize() expects parameter 1 to be string, object given in /my_dir/oop/__serialize.php on line 13
PHP stack trace:
PHP 1. {main}() /my_dir/oop/__serialize.php:0
PHP 2. unserialize() /my_dir/oop/__serialize.php:13
If you ran this code, you saw that we used the serialize
function before invoking the unserialize
function. Therefore, the following result will be displayed:
method invoked when using the unserialize function
__toString
To understand the __toString
method, let’s first imagine the following scenario: you have a Book
class with some public properties, such as $name
and $author
.
class Book {
public $name;
public $author;
}
And you want to print the existing values and properties using echo
or print
.
$zcpe = new Livro();
print $zcpe;
However, when we run the code, we get a FATAL ERROR
, saying that we cannot convert an object to a string:
PHP Catchable fatal error: Object of class Livro could not be converted to string in /my_dir/oop/to_string.php on line 6
PHP stack trace:
PHP 1. {main}() /my_dir/oop/to_string.php:0
Catchable fatal error: Object of class Livro could not be converted to string in /my_dir/oop/to_string.php on line 6
Call Stack:
0.0001 230688 1. {main}() /my_dir/oop/to_string.php:0
For this to work, we must implement the magic __toString
method in our class, which gives us the possibility of displaying whatever we want from the object through a string.
class Book {
public $name;
public $author;
public function __toString()
{
return 'name: ' . $this->name . ' author: ' . $this->author;
}
}
Note that, when implementing the method, we can define how the object will be displayed. In our case, we are just displaying the instance values.
__invoke
Through the __invoke
method, we can treat objects as functions.
class Computer {
public function __invoke()
{
print '__invoke method executed';
}
}
By implementing this method, it is possible to use our object with the same syntax as a function:
$computer = new Computer();
$computer(); // invoking the object with a function
When executing the code, we have the following result:
__invoke method executed
We can also pass arguments, in addition to executing our object as a function. Let’s modify our class. To do this, we will use func_get_args
, which returns all the parameters passed to a function, and we will go parameter by parameter through foreach
to display them.
class Computer {
public function __invoke()
{
print '__invoke method executed';
foreach (func_get_args() as $parameter) {
print 'parameter: ' . $parameter;
}
}
}
After this small modification, we can now retrieve any passed parameter:
$computer = new Computer();
$computer(1, 2, 'third parameter');
When running this script, we get the following result:
__invoke method executed
parameter: 1
parameter: 2
parameter: third parameter
If you prefer, you can also fix the exact number of arguments you want. Next, we will change our Computer
class to accept only two arguments: $name
and $brand
.
class Computer {
public function __invoke($name, $brand)
{
print '__invoke method executed';
print 'name : ' . $name . ' brand: ' . $brand;
}
}
Choosing this type of syntax makes it mandatory to pass parameters, just like in a method or a normal function.
$computer = new Computer();
$computer('Computer1', 'Asus');
__clone
The magic method __clone
is only used when trying to clone an object with the reserved word clone
. If the __clone
method exists within the class, it will be executed; otherwise it will just be ignored.
class Home {
public $number;
}
$casa1 = new Casa();
$casa2 = clone $casa1;
In this example, we are not using any magical method and, even so, we managed to clone our object, which is perfectly valid. We have two identical objects so far.
var_dump($casa1 == $casa2); // bool(true)
The objects are the same as we do not change any properties within them. Let’s then change the number of the object $casa1
and $casa2
.
$casa1->number = 123;
$casa2->number = 456;
var_dump($casa1 == $casa2); // bool(false)
Now, we get false
when trying to compare the objects $casa1
and $casa2
, as these objects have different values.
However, let’s imagine that we are going to use the Address
class together with the Casa
class to make our code more elegant.
class Address {
public $street;
public $number;
}
We will make a small modification to our Casa
class to use the Address
class. Let’s instantiate a new Address
object in the constructor method of the Casa
class.
class Home {
public $color;
public $address;
public function __construct()
{
$this->address = new Address();
}
}
Now that we have both objects, let’s clone them:
$casa1 = new Casa();
$casa2 = clone $casa1;
var_dump($casa1 == $casa2); // bool (true)
Nothing new, right? We just clone the Casa
class and, comparing the two objects, we get the answer true. In the next example, try to analyze more carefully:
$casa1 = new Casa();
$casa1->address->street = 'Av. São Paulo';
$casa2 = clone $casa1;
$casa2->address->street = 'Av. Brazil';
var_dump($casa1 == $casa2); // bool (true)
Can you understand why even changing the $street
property of the Address
object we still get true as a response?
This occurs because in PHP we have the “raza” type of cloning, in which objects inside other objects are not cloned.
What happens is that the object that performs the cloning (in our case, the variable $casa2
) continues to point to the object Address
in the variable $casa1
.
Now that we understand the problem, we need to understand how we can solve it. In our case, we will force the cloning of the internal objects of our class through the magic method __clone
.
class Home {
public $color;
public $address;
public function __construct()
{
$this->address = new Address();
}
public function __clone()
{
$this->address = clone $this->address;
}
}
With the addition of the __clone
magic method in our Casa
class, we force the cloning of the Address
object in the $address
property.
$casa1 = new Casa();
$casa1->address->street = 'Av. São Paulo';
$casa2 = clone $casa1;
$casa2->address->street = 'Av. Brazil';
var_dump($casa1 == $casa2); // bool (false)
Now we have the expected result when comparing the $casa1
object with the $casa2
object. It is very important to remember this concept, as it is a reason for prank questions on the certification test. Just try to note that PHP, by default, does a “clear” cloning (shallow) and, to change this behavior, we must use the magic __clone
method.
It is important to note that implementing magic methods within a class is not mandatory. All will be executed according to the scenario they propose and only if they are implemented; otherwise, PHP will just ignore these methods and continue with its normal execution.
Try/catch exceptions
In programming, there are cases where, depending on our verification, we decide not to return a value, for example, a boolean.
Sometimes we just want to divert the execution of our script if something doesn’t go the way we’re expecting, like a logical check (1 == 2
).
Note that, in our flow, process 3 will only be executed if an exception is thrown; otherwise, execution will proceed normally through flow 1 and 2.
Fortunately, PHP has exception throwing, which allows us to divert the flow of our script’s execution however we want.
To create your first exception, you must use the reserved word throw
in your code.
function largestNumber($firstNumber = 0, $secondNumber = 0) {
if ($firstNumber > $secondNumber) {
throw new Exception('The first number is greater than the second');
}
return true;
}
numberLargest(2, 1);
When we run the script, we have our exception thrown:
PHP Fatal error: Uncaught exception 'Exception' with message 'The first number is greater than the second' in /my_dir/excecao.php:5
Stacktrace:
#0 /my_dir/excecao.php(11): numberLargest(2, 1)
#1 {main}
thrown in /my_dir/excecao.php on line 5
Fatal error: Uncaught exception 'Exception' with message 'The first number is greater than the second' in /my_dir/excecao.php on line 5
Exception: The first number is greater than the second in /my_dir/excecao.php on line 5
Call Stack:
0.0002 232432 1. {main}() /my_dir/excecao.php:0
0.0002 232560 2. numeroMaiores() /my_dir/excecao.php:11
This is PHP’s default behavior: when faced with an exception that is not handled, a fatal error is displayed, and the script ends. In many cases, including ours, we do not want a FATAL ERROR
to be displayed to the user. To do this, we need to use exception handling through the try
and catch
blocks.
Through this handling, we were able to “catch” this exception thrown by the script and handle it in an elegant way for our user, without ending the script with a fatal error.
try {
} catch (Exception $excecao) {
}
See the syntax used to use the try
and catch
blocks. Inside the try
block, this is where we should place our main flow of the script, and inside the catch
block, this is where we should handle the exception, if it is thrown.
Let’s look at a new example using the same function we used previously:
try {
numberLargest(2, 1);
} catch (Exception $excecao) {
print $excecao->getMessage();
}
Now, running our script again, we have a much more elegant output without fatal errors displayed to the user:
The first number is greater than the second
Note that, inside the catch
block, we are expecting an object of type Exception
. And it is through this object that we can access different methods to find out which exception was thrown, what the message code was, etc.
See the Exception class in detail in the PHP documentation, at http://php.net/manual/language.exceptions.extending.php
In addition to handling our exceptions in the catch
block, we can specify which exception we want to handle. Let’s modify our script so that it throws two different exceptions:
function largestNumber($firstNumber = 0, $secondNumber = 0) {
if ($firstNumber > $secondNumber) {
throw new Exception(
'The first number is greater than the second'
);
}
if ($firstNumber === $secondNumber) {
throw new InvalidArgumentException(
'The numbers must not be the same'
);
}
return true;
}
We now have a function that throws two different types of exceptions:
try {
numberLargest(1, 1);
} catch (Exception $excecao) {
print $excecao->getMessage();
}
Note that we have a generic exception class, which is Exception
. In other words, all exception classes inherit from the Exception
class, which makes exception handling very easy, as we saw in the previous example. Note that the type of exception thrown is an invalid argument InvalidArgumentException
, however in the catch
block we use the generic class Exception
to handle any exception thrown within the try
block.
With this, we have the following result after running the script:
The numbers must not be the same
Now let’s make things a little more difficult. With exceptions, we saw that we can throw several of any type and handle them through the catch
block with the Exception
class. But what if we want to apply different treatment depending on the exception thrown? With PHP, it is possible to nest several catch
blocks.
try {
numberLargest(1, 1);
} catch (InvalidArgumentException $excecao) {
print 'Invalid argument ' . $excecao->getMessage();
} catch (Exception $excecao) {
print 'Generic treatment: ' . $excecao->getMessage();
}
As the example shows, we can specify different types of treatments for certain exceptions. Furthermore, it is possible to define generic handling with the Exception
class if no handling is applied to a specific exception. When we run the script, we have the following result:
Invalid argument The first number is greater than the second
But, in addition to specific or generic treatment, we have a very important rule about exceptions, which is the order in which they are handled:
The
catch
block will be executed by the first class that satisfies the exception that was thrown.
Let’s take as a basis the previous example that executes the catch
block and expects an exception of type InvalidArgumentException
. Pay attention to the following part:
} catch (InvalidArgumentException $excecao) {
print 'Invalid argument ' . $excecao->getMessage();
} catch (Exception $excecao) {
print 'Generic treatment: ' . $excecao->getMessage();
}
What if we invert the catch
blocks?
} catch (Exception $excecao) {
print 'Generic treatment: ' . $excecao->getMessage();
} catch (InvalidArgumentException $excecao) {
print 'Invalid argument ' . $excecao->getMessage();
}
What would be the output when we run the code again?
This time, we have a different result:
Generic treatment: The numbers must not be the same
This occurs because PHP does not have cascading exception handling, that is, the first catch
that satisfies its condition will be executed.
##finally
In PHP 5.5, we introduced a new reserved word for working with exceptions, called finally
. With this new functionality, we can guarantee the execution of the code within the finally
block, regardless of whether an exception is thrown or not.
try {
// Normal flow
} catch (Exception $excecao) {
// Exception handling
} finally {
// Mandatory execute this block
}
Let’s go to our first example, which will be forcing an exception to be thrown:
try {
throw new Exception('Exception thrown');
} catch (Exception $excecao) {
print $excecao->getMessage();
} finally {
print 'Block finally executed';
}
When we run this script, we have the following result:
Exception thrown
Finally block executed
The finally
block is executed even if an exception is not thrown:
try {
print 'Normal execution';
} catch (Exception $excecao) {
print $excecao->getMessage();
} finally {
print 'Block finally executed';
}
Which gives us the following result:
Normal execution
Finally block executed
Creating your exception
So far, we have only used exceptions that PHP provides us by default, but we can also create our own exception to use. Below, we have an image of what the PHP exception hierarchy looks like.
The base class for all exceptions is Exception
. To create our own exception, just extend it.
class Invalid Number extends Exception {}
With that, we already have our own exception to use:
function throwExcecao($number)
{
if (!is_numeric($numeric)) {
throw new InvalidNumber(
'Variable $number is not of numeric type'
);
}
}
Like the other exceptions, we can handle our exception normally:
try {
throwExcecao('string');
} catch (InvalidNumber $excecao) {
print $excecao->getMessage();
}
And if we run the script, we have the following result:
Variable $number is not of numeric type
PHP already provides us with some exception classes to use in our code. You can see the classes that exist at the time of writing this book in the table below. Probably, as the language evolves, new classes will be added.
-
BadFunctionCallException
– Must be used when a call is made to an undefined function/argument. -
BadMethodCallException
– Must be used when a call is made to an undefined method. -
DomainException
– Must be used when the expected domain value is not met, for example, when you expect an image of type.jpeg
and receive one of type.png
, or when you expect an email from domain@casadocodigo
and receives a@gmail
. -
InvalidArgumentException
– Must be used when the expected argument value is not correct. -
LengthException
– Should be used when the length received is larger than expected, for example, a person’s name. You are expecting a name with a maximum of 30 characters, but one with 40 is sent. -
LogicException
– Should be used when the necessary logic is not satisfied -
OutOfBoundsException
– Used when errors cannot be detected at compile time, such as a very large integer. -
OutOfRangeException
– Should be used when the desired range is not satisfied, such as trying to access a key that does not exist in an array. -
OverflowException
– Should be used when it is no longer possible to add items to a full container. If you have a list that only accepts 30 items and they try to add one more, this exception should be used. -
RangeException
– Must be used when the expected range is not reached. -
RuntimeException
– Should be used when errors are found during program execution. -
UnderflowException
– Should be used when trying to manipulate an empty container (the opposite of theOverflowException
exception). -
UnexpectedValueException
– Instead of the argument being invalid like theInvalidArgumentException
exception, this exception should be used when the expected value is not provided.
Late static binding and self
Before we discover what Late static binding is, we must first understand what the reserved word self
is and what its limitations are.
We use the reserved word self
to refer to the static context, just as $this
is used to refer to the instance. Let’s look at an example to make it clearer:
class A
{
public static function who()
{
print __CLASS__;
}
public static function test()
{
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
This example shows us two classes: class A
and class B
, which extends class A
, and two static methods in class A
. In class B
, we have a single method that overrides the method in class A
.
Try analyzing the code for a few minutes, and then proceed to the explanation.
Normally, we would think that the response to this code when executed would be to display the name of class B
, but what is displayed is the name of class A
(I recommend that you try running this code on your computer). But, after all, why does this happen?
The answer is very simple and practical. In PHP, we have the self
context which refers to the context of the class in which the method or property was defined, not the class that is invoking it. In other words, in our example, the person invoking the method is class B
, but the method test
is defined in class A
which, in turn, invokes the method of the person using the reserved word self
to refer to its own class (i.e. class A
).
Now that we understand static behavior, we can figure out what this late static binding thing is. This fancy name is nothing more than referring to the class that is invoking the property or method rather than where they were defined.
class A
{
public static function who()
{
print __CLASS__;
}
public static function test()
{
static::who(); // Using late static binding
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
By changing our script from self
to static
, we managed to get the expected result. When executing the script, the class name B
is displayed, and no longer A
.
In the language’s official documentation, you will find a topic just about late static binding, which can be seen at http://php.net/manual/language.oop5.late-static-bindings.php.
Summary
This is the point where, generally, for those starting out with Object Oriented Programming, they really feel like a real programmer! But, in addition to being a subject that attracts a lot of attention, it is also a real step forward for us, as PHP was not born with all the baggage that we have today, with OOP.
Before we finish, another issue to highlight is the use of the standard classes provided by PHP, the famous SPL. You may have noticed that throughout the chapters we have used some in our examples, and this will continue in the remaining chapters. However, we will not dedicate a chapter just to this, as SPL in itself would already be a book.
However, we advise you to see the SPL page in the official PHP documentation, at http://php.net/manual/book.spl.php. There are questions related to this topic. We are unable to specify which class you may fall into on the day of your test, but take a look at as many classes as you can. If you want a tip, pay attention to these classes:
ArrayAccess
ArrayObject
ArrayIterator
IteratorIterator
RecursiveIteratorIterator
InvalidArgumentException
SplFileObject
SplObserver
SplSubject
Resources
Table of contents
Got a question?
If you have question or feedback, don't think twice and click here to leave a comment. Just want to support me? Buy me a coffee!