Learning PHP 7 High Performance
上QQ阅读APP看书,第一时间看更新

OOP features

PHP 7 introduced a few new OOP features that will enable developers to write clean and effective code. In this section, we will discuss these features.

Type hints

Prior to PHP 7, there was no need to declare the data type of the arguments passed to a function or class method. Also, there was no need to mention the return data type. Any data type can be passed to and returned from a function or method. This is one of the huge problems in PHP, in which it is not always clear which data types should be passed or received from a function or method. To fix this problem, PHP 7 introduced type hints. As of now, two type hints are introduced: scalar and return type hints. These are discussed in the following sections.

Type hints is a feature in both OOP and procedural PHP because it can be used for both procedural functions and object methods.

Scalar type hints

PHP 7 made it possible to use scalar type hints for integers, floats, strings, and Booleans for both functions and methods. Let's have a look at the following example:

class Person
{
  public function age(int $age)
  {
    return $age;
    }

  public function name(string $name)
  {
    return $name;
    }

  public function isAlive(bool $alive)
  {
    return $alive;
    }

}

$person = new Person();
echo $person->name('Altaf Hussain');
echo $person->age(30);
echo $person->isAlive(TRUE);

In the preceding code, we created a Person class. We have three methods, and each method receives different arguments whose data types are defined with them, as is highlighted in the preceding code. If you run the preceding code, it will work fine as we will pass the desired data types for each method.

Age can be a float, such as 30.5 years; so, if we pass a float number to the age method, it will still work, as follows:

echo $person->age(30.5);

Why is that? It is because, by default, scalar type hints are nonrestrictive. This means that we can pass float numbers to a method that expects an integer number.

To make it more restrictive, the following single-line code can be placed at the top of the file:

declare(strict_types = 1);

Now, if we pass a float number to the age function, we will get an Uncaught Type Error, which is a fatal error that tells us that Person::age must be of the int type given the float. Similar errors will be generated if we pass a string to a method that is not of the string type. Consider the following example:

echo $person->isAlive('true');

The preceding code will generate the fatal error as the string is passed to it.

Return type hints

Another important feature of PHP 7 is the ability to define the return data type for a function or method. It behaves the same way scalar type hints behave. Let's modify our Person class a little to understand return type hints, as follows:

class Person
{
  public function age(float $age) : string
  {
    return 'Age is '.$age;
  }

  public function name(string $name) : string
  {
    return $name;
    }

  public function isAlive(bool $alive) : string
  {
    return ($alive) ? 'Yes' : 'No';
  }

}

The changes in the class are highlighted. The return type is defined using the: data-type syntax. It does not matter if the return type is the same as the scalar type. These can be different as long as they match their respective data types.

Now, let's try an example with the object return type. Consider the previous Person class and add a getAddress method to it. Also, we will add a new class, Address, to the same file, as shown in the following code:

class Address 
{
 public function getAddress()
 {
 return ['street' => 'Street 1', 'country' => 'Pak'];
 }
}

class Person
{
  public function age(float $age) : string
  {
    return 'Age is '.$age;
  }

  public function name(string $name) : string
  {
    return $name;
  }

  public function isAlive(bool $alive) : string
  {
    return ($alive) ? 'Yes' : 'No';
  }

 public function getAddress() : Address
 {
 return new Address();
 }
}

The additional code added to the Person class and the new Address class is highlighted. Now, if we call the getAddress method of the Person class, it will work perfectly and won't throw an error. However, let's suppose that we change the return statement, as follows:

public function getAddress() : Address
{
  return ['street' => 'Street 1', 'country' => 'Pak'];
}

In this case, the preceding method will throw an uncaught exception similar to the following:

Fatal error: Uncaught TypeError: Return value of Person::getAddress() must be an instance of Address, array returned

This is because we return an array instead of an Address object. Now, the question is: why use type hints? The big advantage of using type hints is that it will always avoid accidentally passing or returning wrong and unexpected data to methods or functions.

As can be seen in the preceding examples, this makes the code clear, and by looking at the declarations of the methods, one can exactly know which data types should be passed to each of the methods and what kind of data is returned by looking into the code of each method or comment, if any.

Namespaces and group use declaration

In a very large codebase, classes are divided into namespaces, which makes them easy to manage and work with. However, if there are too many classes in a namespace and we need to use 10 of them, then we have to type the complete use statement for all these classes.

Note

In PHP, it is not required to divide classes in subfolders according to their namespace, as is the case with other programming languages. Namespaces just provide a logical separation of classes. However, we are not limited to placing our classes in subfolders according to our namespaces.

For example, we have a Publishers/Packt namespace and the classes Book, Ebook, Video, and Presentation. Also, we have a functions.php file, which has our normal functions and is in the same Publishers/Packt namespace. Another file, constants.php, has the constant values required for the application and is in the same namespace. The code for each class and the functions.php and constants.php files is as follows:

//book.php
namespace Publishers\Packt;

class Book 
{
  public function get() : string
  {
    return get_class();
  }
}

Now, the code for the Ebook class is as follows:

//ebook.php
namespace Publishers\Packt;

class Ebook 
{
  public function get() : string
  {
    return get_class();
  }
}

The code for the Video class is as follows:

//presentation.php
namespace Publishers\Packt;

class Video 
{
  public function get() : string
  {
    return get_class();
  }
}

Similarly, the code for the presentation class is as follows:

//presentation.php
namespace Publishers\Packt;

class Presentation 
{
  public function get() : string
  {
    return get_class();
  }
}

All the four classes have the same methods, which return the classes' names using the PHP built-in get_class() function.

Now, add the following two functions to the functions.php file:

//functions.php

namespace Publishers\Packt;

function getBook() : string
{
  return 'PHP 7';
}
function saveBook(string $book) : string
{
  return $book.' is saved';
}

Now, let's add the following code to the constants.php file:

//constants.php
  
namespace Publishers/Packt;

const COUNT = 10;
const KEY = '123DGHtiop09847';
const URL = 'https://www.Packtpub.com/';

The code in both functions.php and constants.php is self-explanatory. Note that each file has a namespace Publishers/Packt line at the top, which makes these classes, functions, and constants belong to this namespace.

Now, there are three ways to use the classes, functions, and constants. Let's consider each one.

Take a look at the following code:

//Instantiate objects for each class in namespace

$book = new Publishers\Packt\Book();
$ebook = new Publishers\Packt\Ebook();
$video = new Publishers\Packt\Video();
$presentation = new Publishers\Packt\Presentation();

//Use functions in namespace

echo Publishers/Packt/getBook();
echo Publishers/Packt/saveBook('PHP 7 High Performance');

//Use constants

echo Publishers\Packt\COUNT;
echo Publishers\Packt\KEY;

In the preceding code, we used namespace names directly while creating objects or using functions and constants. The code looks fine, but it is cluttered. Namespace is everywhere, and if we have lots of namespaces, it will look very ugly, and the readability will be affected.

Note

We did not include class files in the previous code. Either the include statements or PHP's __autoload function can be used to include all the files.

Now, let's rewrite the preceding code to make it more readable, as follows:

use Publishers\Packt\Book;
use Publishers\Packt\Ebook;
use Publishers\Packt\Video;
use Publishers\Packt\Presentation;
use function Publishers\Packt\getBook;
use function Publishers\Packt\saveBook;
use const Publishers\Packt\COUNT;
use const Publishers\Packt\KEY;

$book = new Book();
$ebook = new Ebook(();
$video = new Video();
$pres = new Presentation();

echo getBook();
echo saveBook('PHP 7 High Performance');

echo COUNT; 
echo KEY;

In the preceding code, at the top, we used PHP statements for specific classes, functions, and constants in a namespace. However, we still wrote duplicate lines of code for each class, function, and/or constant. This may lead to us have lots of use statements at the top of the file, and the overall verbosity would not be good.

To fix this problem, PHP 7 introduced group use declaration. There are three types of group use declarations:

  • Non mixed use declarations
  • Mixed use declarations
  • Compound use declarations

Non mixed group use declarations

Consider that we have different types of features in a namespace, as we have classes, functions, and contacts in a namespace. In non mixed group use declarations, we declare them separately using a use statement. To better understand it, take a look at the following code:

use Publishers\Packt\{ Book, Ebook, Video, Presentation };
use function Publishers\Packt\{ getBook, saveBook };
use const Publishers\Packt\{ COUNT, KEY };

We have three types of features in a namespace: class, functions, and constants. So, we have used separate group use declaration statements to use them. The code is now looking more cleaner, organized, and readable and doesn't require too much duplicate typing.

Mixed group use declarations

In this declaration, we combine all types into a single use statement. Take a look at the following code:

use Publishers\Packt\{ 
  Book,
  Ebook,
  Video,
  Presentation,
  function getBook,
  function saveBook,
  const COUNT,
  const KEY
};

The compound namespace declaration

To understand the compound namespace declaration, we will consider the following criteria.

Let's say we have a Book class in the Publishers\Packt\Paper namespace. Also, we have an Ebook class in the Publishers\Packt\Electronic namespace. The Video and Presentation classes are in the Publishers\Packt\Media namespace. So, to use these classes, we will use the code, as follows:

use Publishers\Packt\Paper\Book;
use Publishers\Packt\Electronic\Ebook;
use Publishers\Packt\Media\{Video,Presentation};

In the compound namespace declaration, we can use the preceding namespaces as follows:

use Publishers\Packt\{
  Paper\Book,
  Electronic\Ebook,
  Media\Video,
  Media\Presentation
};

It is more elegant and clear, and it doesn't require extra typing if the namespace names are long.

The anonymous classes

An anonymous class is a class that is declared and instantiated at the same time. It does not have a name and can have the full features of a normal class. These classes are useful when a single one-time small task is required to be performed and there is no need to write a full-blown class for it.

Note

While creating an anonymous class, it is not named, but it is named internally in PHP with a unique reference based on its address in the memory block. For example, the internal name of an anonymous class may be class@0x4f6a8d124.

The syntax of this class is the same as that of the named classes, but only the name of the class is missing, as shown in the following syntax:

new class(argument) { definition };

Let's look at a basic and very simple example of an anonymous class, as follows:

$name = new class() {
  public function __construct()
  {
    echo 'Altaf Hussain';
  }
};

The preceding code will just display the output as Altaf Hussain.

Arguments can also be passed to the anonymous class constructor, as shown in the following code:

$name = new class('Altaf Hussain') {
  public function __construct(string $name)
  {
    echo $name;
  }
};

This will give us the same output as the first example.

Anonymous classes can extend other classes and have the same parent-child classes functioning as normal named classes. Let's have another example; take a look at the following:

class Packt
{
  protected $number;
  
  public function __construct()
  {
    echo 'I am parent constructor';
  }

  public function getNumber() : float
  {
    return $this->number;
  }
}

$number = new class(5) extends packt
{
  public function __construct(float $number)
  {
    parent::__construct();
    $this->number = $number;
  }
};

echo $number->getNumber();

The preceding code will display I am parent constructor and 5. As can be seen, we extended the Packt class the way we extend named classes. Also, we can access the public and protected properties and methods within the anonymous class and public properties and methods using anonymous class objects.

Anonymous classes can implement interfaces too, the same as named classes. Let's create an interface first. Run the following:

interface Publishers
{
  public function __construct(string $name, string $address);
  public function getName();
  public function getAddress();
}

Now, let's modify our Packt class as follows. We added the highlighted code:

class Packt
{
  protected $number;
  protected $name;
  protected $address;
  public function …
}

The rest of the code is same as the first Packt class. Now, let's create our anonymous class, which will implement the Publishers interface created in the previous code and extend the new Packt class, as follows:

$info = new class('Altaf Hussain', 'Islamabad, Pakistan')extends packt implements Publishers
{
  public function __construct(string $name, string $address)
  {
    $this->name = $name;
    $this->address = $address;
  }

  public function getName() : string
  {
  return $this->name;
  }

  public function getAddress() : string
  {
  return $this->address;
  }
}

echo $info->getName(). ' '.$info->getAddress();

The preceding code is self-explanatory and will output Altaf Hussain along with the address.

It is possible to use anonymous classes within another class, as shown here:

class Math
{
  public $first_number = 10;
  public $second_number = 20;

  public function add() : float
  {
    return $this->first_number + $this->second_number;
  }

  public function multiply_sum()
  {
    return new class() extends Math
    {
      public function multiply(float $third_number) : float
      {
        return $this->add() * $third_number;
      }
    };
  }
}

$math = new Math();
echo $math->multiply_sum()->multiply(2);

The preceding code will return 60. How does this happen? The Math class has a multiply_sum method that returns the object of an anonymous class. This anonymous class is extended from the Math class and has a multiply method. So, our echo statement can be divided into two parts: the first is $math->multiply_sum(), which returns the object of the anonymous class, and the second is ->multiply(2), in which we chained this object to call the anonymous class's multiply method along with an argument of the value 2.

In the preceding case, the Math class can be called the outer class, and the anonymous class can be called the inner class. However, remember that it is not required for the inner class to extend the outer class. In the preceding example, we extended it just to ensure that the inner classes could have access to the outer classes' properties and methods by extending the outer classes.

Old-style constructor deprecation

Back in PHP 4, the constructor of a class has the same name method as that of the class. It is still used and is valid until PHP's 5.6 version. However, now, in PHP 7, it is deprecated. Let's have an example, as shown here:

class Packt
{
  public function packt()
  {
    echo 'I am an old style constructor';
  }
}

$packt = new Packt();

The preceding code will display the output I am an old style constructor with a deprecated message, as follows:

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; Packt has a deprecated constructor in…

However, the old style constructor is still called. Now, let's add the PHP __construct method to our class, as follows:

class Packt
{
  public function __construct()
  {
    echo 'I am default constructor';
  }

  public function packt()
  {
    echo 'I am just a normal class method';
  }
}

$packt = new Packt();
$packt->packt();

In the preceding code, when we instantiated the object of the class, the normal __construct constructor was called. The packt()method isn't considered a normal class method.

Note

Old-style constructors are deprecated, which means that they will still work in PHP 7 and a deprecated message will be displayed, but it will be removed in the upcoming versions. It is best practice to not use them.

The throwable interface

PHP 7 introduced a base interface that can be base for every object that can use the throw statement. In PHP, exceptions and errors can occur. Previously, exceptions could be handled, but it was not possible to handle errors, and thus, any fatal error caused the complete application or a part of the application to halt. To make errors (the most fatal errors) catchable as well, PHP 7 introduced the throwable interface, which is implemented by both the exception and error.

Note

The PHP classes we created can't implement the throwable interface. If required, these classes must extend an exception.

We all know exceptions, so in this topic, we will only discuss errors, which can handle the ugly, fatal errors.

Error

Almost all fatal errors can now throw an error instance, and similarly to exceptions, error instances can be caught using the try/catch block. Let's have a simple example:

function iHaveError($object)
{
  return $object->iDontExist();
  {

//Call the function
iHaveError(null);
echo "I am still running";

If the preceding code is executed, a fatal error will be displayed, the application will be halted, and the echo statement won't be executed in the end.

Now, let's place the function call in the try/catch block, as follows:

try 
{
  iHaveError(null);
} catch(Error $e)
{
  //Either display the error message or log the error message
  echo $e->getMessage();
}

echo 'I am still running';

Now, if the preceding code is executed, the catch body will be executed, and after this, the rest of the application will continue running. In the preceding case, the echo statement will be executed.

In most cases, the error instance will be thrown for the most fatal errors, but for some errors, a subinstance of error will be thrown, such as TypeError, DivisionByZeroError, ParseError, and so on.

Now, let's take a look at a DivisionByZeroError exception in the following example:

try
{
  $a = 20;
  $division = $a / 20;
} catch(DivisionByZeroError $e) 
{
  echo $e->getMessage();
}

Before PHP 7, the preceding code would have issued a warning about the division by zero. However, now in PHP 7, it will throw a DivisionByZeroError, which can be handled.