This chapter marks the beginning of Part 2 of the book, PHP 8 Tricks. In this part, you'll discover the dark corners of PHP 8: the place where backward-compatibility breaks exist. This part gives you insight into how to avoid problems before migrating an existing application to PHP 8. You will learn what to look for in your existing code that could cause it to stop working after a PHP 8 upgrade. Once you master the topics presented in this part of the book, you will be well equipped to modify existing code in such a manner that it continues to function normally following a PHP 8 upgrade.
In this chapter, you will be introduced to new PHP 8 features specific to object-oriented programming (OOP). The chapter provides you with plenty of short code examples that clearly illustrate the new features and concepts. This chapter is critical in helping you quickly take advantage of the power of PHP 8 as you adapt the code examples for your own practice. The focus of this chapter is on situations where object-oriented code might break after a PHP 8 migration.
Topics covered in this chapter include the following:
To examine and run the code examples provided in this chapter, the minimum recommended hardware is the following:
In addition, you will need to install the following software:
Please refer to the Technical requirements section of Chapter 1, Introducing New PHP 8 OOP Features, for more information on Docker and Docker Compose installation, as well as how to build the Docker container used to demonstrate code explained in this book. In this book, we refer to the directory in which you restored the sample code for this book as /repo.
The source code for this chapter is located here: https://github.com/PacktPublishing/PHP-8-Programming-Tips-Tricks-and-Best-Practices.
We can now begin our discussion by examining core OOP coding differences.
There are a number of significant changes to how you are able to write OOP code in PHP 8. In this section, we focus on three key areas that might present you with potential backward-compatibility breaks. The areas we address in this section are common bad practices associated with making static method calls, handling object properties, and PHP autoloading.
After reading this section, and working your way through the examples, you are in in a better position to spot OOP bad practices and to learn how PHP 8 has placed restrictions on such usage. In this chapter, you learn good coding practices, which will ultimately make you a better programmer. You will also be able to address changes in PHP autoloading that can potentially cause failure in an application migrated to PHP 8.
Let's first look at how PHP 8 has tightened up on making static calls.
Surprisingly, PHP versions 7 and below allowed developers to make a static call to a class method not declared static. At first glance, any future developer reviewing your code immediately assumes that the method has been defined as static. This can lead to unexpected behavior as the future developer, operating under a false assumption, starts to misuse your code.
In this simple example, we define a Test class with a nonStatic() method. In the procedural code that follows the class definition, we echo the return value of this method, however, in doing so we make a static call:
// /repo/ch05/php8_oop_diff_static.php
class Test {
public function notStatic() {
return __CLASS__ . PHP_EOL;
}
}
echo Test::notStatic();
When we run this code in PHP 7, here is the result:
root@php8_tips_php7 [ /repo/ch05 ]#
php php8_oop_diff_static.php
PHP Deprecated: Non-static method Test::notStatic() should not be called statically in /repo/ch05/php8_oop_diff_static.php on line 11
Test
As you can see from the output, PHP 7 issues a deprecation notice, but allows the call to be made! In PHP 8, however, the result is a fatal Error, as shown here:
root@php8_tips_php8 [ /repo/ch05 ]#
php php8_oop_diff_static.php
PHP Fatal error: Uncaught Error: Non-static method Test::notStatic() cannot be called statically in /repo/ch05/php8_oop_diff_static.php:11
Calling a non-static method using static method call syntax is a bad practice in the sense that well-written code makes the intention of the code developer crystal clear. If you do not define a method as static, but later call it in a static sense, a developer assigned to maintain your code in the future might become confused and could make wrong assumptions about the original intent of the code. The end result will be even more bad code!
In PHP 8, you can no longer call a non-static method using a static method call. Let's now have a look at another bad practice involving treating object properties as keys.
Arrays have been a central feature in PHP all the way back to the earliest versions. OOP, on the other hand, was not introduced until PHP 4. In the early days of OOP, array functions were often expanded to accommodate object properties. This led to a blurring of the distinction between an object and an array, which in turn spawned a number of bad practices.
In order to maintain a clear separation between array handling and object handling, PHP 8 now restricts the array_key_exists() function to only accept an array as an argument. To illustrate this, consider the following example:
// /repo/ch05/php8_oop_diff_array_key_exists.php
$obj = new class () { public $var = 'OK.'; };
// not all code is shown
$default = 'DEFAULT';
echo (isset($obj->var))
? $obj->var : $default;
echo (property_exists($obj,'var'))
? $obj->var : $default;
echo (array_key_exists('var',$obj))
? $obj->var : $default;
When we run this code in PHP 7, all tests succeed, as shown here:
root@php8_tips_php7 [ /repo/ch05 ]#
php php8_oop_diff_array_key_exists.php
OK.OK.OK.
In PHP 8, however, a fatal TypeError occurs, as array_key_exists() now only accepts an array as an argument. The PHP 8 output is shown here:
root@php8_tips_php8 [ /repo/ch05 ]#
php php8_oop_diff_array_key_exists.php
OK.OK.PHP Fatal error: Uncaught TypeError: array_key_exists(): Argument #2 ($array) must be of type array, class@anonymous given in /repo/ch05/php8_oop_diff_array_key_exists.php:10
The best practice is to use either property_exists() or isset(). We now turn our attention to changes in PHP autoloading.
The basic autoloading class mechanism first introduced in PHP 5.1 works the same in PHP 8. The main difference is that the support for the global function __autoload(), deprecated in PHP 7.2, has been completely removed in PHP 8. Starting with PHP 7.2, developers were encouraged to register their autoloading logic using spl_autoload_register(), available for that purpose since PHP 5.1. Another major difference is how spl_autoload_register() reacts if unable to register an autoloader.
An understanding of how the autoloading process works when using spl_autoload_register() is critical to your work as a developer. Failure to grasp how PHP automatically locates and loads classes will limit your ability to grow as a developer and could have a detrimental impact on your career path.
Before getting into spl_autoload_register(), let's first have a look at the __autoload() function.
The __autoload() function was used by many developers as the primary source of autoloading logic. This function behaves much as a magic method does and that's why it's called automatically depending on the context. Circumstances that would trigger an automatic call to the __autoload() function include the moment when a new class instance is created, but where the class definition has not yet been loaded. Further, if the class extends another class, the autoload logic is also invoked in order to load the super class prior to the creation of the subclass that extends it.
The advantage of using the __autoload() function was that it was quite easy to define, and was often defined in a website's initial index.php file. The disadvantages included the following:
To illustrate potential problems, we'll define an OopBreakScan class, discussed in more detail in Chapter 11, Migrating Existing PHP Apps to PHP 8:
namespace Migration;
class OopBreakScan extends Base {
public static function scanMagicAutoloadFunction(
string $contents, array &$message) : bool {
$found = 0;
$found += (stripos($contents,
'function __autoload(') !== FALSE);
$message[] = ($found)
? Base::ERR_MAGIC_AUTOLOAD
: sprintf(Base::OK_PASSED,
__FUNCTION__);
return (bool) $found;
}
// remaining methods not shown
This class extends a MigrationBase class (not shown). This is significant as any autoloading logic needs to find not only the subclass but its super class as well.
// /repo/ch05/php7_autoload_function.php
function __autoLoad($class) {
$fn = __DIR__ . '/../src/'
. str_replace('', '/', $class)
. '.php';
require_once $fn;
}
use MigrationOopBreakScan;
$contents = file_get_contents(__FILE__);
$message = [];
OopBreakScan::
scanMagicAutoloadFunction($contents, $message);
var_dump($message);
Here is the output running in PHP 7:
root@php8_tips_php7 [ /repo/ch05 ]#
php php7_autoload_function.php
/repo/ch05/php7_autoload_function.php:23:
array(1) {
[0] => string(96) "WARNING: the "__autoload()" function is removed in PHP 8: replace with "spl_autoload_register()""
}
As you can see from the output, the MigrationOopBreakScan class was autoloaded. We know this because the scanMagicAutoloadFunction method was invoked, and we have its results. Furthermore, we know that the MigrationBase class was also autoloaded. The reason we know this is because the error message that appears in the output is a constant of the super class.
However, the same code running in PHP 8 produces this result:
root@php8_tips_php8 [ /repo/ch05 ]#
php php7_autoload_function.php
PHP Fatal error: __autoload() is no longer supported, use spl_autoload_register() instead in /repo/ch05/php7_autoload_function.php on line 4
This result is not surprising as support for the magic __autoload() function was removed in PHP 8. In PHP 8, you must use spl_autoload_register() instead. We now turn our attention to spl_autoload_register().
The primary advantage of the spl_autoload_register() function is that it allows you to register more than one autoloader. Although this might seem like overkill, imagine the nightmare scenario where you are using a number of different open source PHP libraries... and where they all have their own autoloaders defined! As long as all such libraries use spl_autoload_register(), having multiple autoloader callbacks poses no problem.
Each autoloader registered using spl_autoload_register() must be callable. Any of the following are considered callable:
Tip
Composer maintains its own autoloader, which in turn relies upon spl_autoload_register(). If you are using Composer to manage your open source PHP packages, you can simply include /path/to/project/vendor/autoload.php at the start of your application code to use the Composer autoloader. To have Composer autoload your application source code files, add one or more entries into the composer.json file under the autoload : psr-4 key. For more information, see https://getcomposer.org/doc/04-schema.md#psr-4.
A quite typical autoloader class might appear as follows. Note that this is the class we use for many of the OOP examples shown in this book:
// /repo/src/Server/Autoload/Loader.php
namespace ServerAutoload;
class Loader {
const DEFAULT_SRC = __DIR__ . '/../..';
public $src_dir = '';
public function __construct($src_dir = NULL) {
$this->src_dir = $src_dir
?? realpath(self::DEFAULT_SRC);
spl_autoload_register([$this, 'autoload']);
}
public function autoload($class) {
$fn = str_replace('', '/', $class);
$fn = $this->src_dir . '/' . $fn . '.php';
$fn = str_replace('//', '/', $fn);
require_once($fn);
}
}
Now that you have an idea of how to use the spl_auto_register() function, we must examine a potential code break when running PHP 8.
The second argument to the spl_auto_register() function is an optional Boolean value that defaults to FALSE. If the second argument is set to TRUE, the spl_auto_register() function throws an Exception in PHP 7 and below versions if an autoloader fails to register. In PHP 8, however, if the data type of the second argument is anything other than callable, a fatal TypeError is thrown instead, regardless of the value of the second argument!
The simple program example shown next illustrates this danger. In this example, we use the spl_auto_register() function to register a PHP function that does not exist. We set the second argument to TRUE:
// /repo/ch05/php7_spl_spl_autoload_register.php
try {
spl_autoload_register('does_not_exist', TRUE);
$data = ['A' => [1,2,3],'B' => [4,5,6],'C' => [7,8,9]];
$response = new ApplicationStrategyJsonResponse($data);
echo $response->render();
} catch (Exception $e) {
echo "A program error has occurred ";
}
If we then run this block of code in PHP 7, here is the result:
root@php8_tips_php7 [ /repo/ch05 ]#
php php7_spl_spl_autoload_register.php
A program error has occurred
As you can determine from the output, an Exception is thrown. The catch block is invoked, and the message A program error has occurred appears. When we run the same program in PHP 8, however, a fatal Error is thrown:
root@php8_tips_php8 [ /repo/ch05 ]#
php php7_spl_spl_autoload_register.php
PHP Fatal error: Uncaught TypeError: spl_autoload_register(): Argument #1 ($callback) must be a valid callback, no array or string given in /repo/ch05/php7_spl_spl_autoload_register.php:12
Obviously, the catch block was bypassed as it was designed to catch an Exception, not an Error. The simple solution is to have the catch block catch Throwable instead of Exception. This allows the same code to run in either PHP 7 or PHP 8.
Here is how the rewritten code might appear. The output is not shown as it's identical to the same example running in PHP 7:
// /repo/ch05/php8_spl_spl_autoload_register.php
try {
spl_autoload_register('does_not_exist', TRUE);
$data = ['A' => [1,2,3],'B' => [4,5,6],'C' => [7,8,9]];
$response = new ApplicationStrategyJsonResponse($data);
echo $response->render();
} catch (Throwable $e) {
echo "A program error has occurred ";
}
You now have a better understanding of PHP 8 autoloading, and how to spot and correct potential autoloading backward-compatibility breaks. Let's now have a look at changes in PHP 8 pertaining to magic methods.
PHP magic methods are predefined hooks that interrupt the normal flow of an OOP application. Each magic method, if defined, alters the behavior of the application from the minute the object instance is created, up until the point where the instance goes out of scope.
Important note
An object instance goes out of scope when it's unset or overwritten. Object instances also go out of scope when defined in a function or class method, and the execution of that function or class method ends. Ultimately, if for no other reason, an object instance goes out of scope when the PHP program ends.
This section will give you a solid understanding of important changes to magic method usage and behavior introduced in PHP 8. Once you understand the situations described in this section, you will be in a position to make the appropriate code modifications to prevent your application code from failing should you migrate to PHP 8.
Let's first have a look at changes to the object construct method.
Ideally, the class constructor is a method that's called automatically when the object instance is created and is used to perform some sort of object initialization. This initialization most typically involves populating object properties with values supplied as arguments to this method. The initialization could also perform any necessary tasks such as opening file handles, establishing a database connection, and so forth.
In PHP 8, a number of changes in how the class constructor is invoked have been made. This means there's a potential for a backwards compatibility break when you migrate your application to PHP 8. The first change we'll examine has to do with deprecated usage of a method with the same name as the class being used as the class constructor.
In the first PHP OOP implementation, introduced in PHP version 4, it was determined that a method with the same name as the class would assume the role of class constructor, and would be automatically called when a new object instance was created.
It's a little known fact that, even in PHP 8, functions, methods, and even class names, are case-insensitive. Thus $a = new ArrayObject(); is equivalent to $b = new arrayobject();. Variable names, on the other hand, are case-sensitive.
Starting with PHP 5, along with a new and much more robust OOP implementation, magic methods were introduced. One of these methods is __construct(), specifically reserved for class construction, designed to replace the older usage. Using a method with the same name as the class as a constructor was supported through the remaining versions of PHP 5, and all the way through all versions of PHP 7 as well.
In PHP 8, support for a class constructor method with the same name as the class itself has been removed. If a __construct() method is also defined, you will have no problem: __construct() takes precedence as a class constructor. If there is no __construct() method, and you detect a method with the same name as the class (), you have the potential for failure. Please bear in mind that both method and class names are case-insensitive!
Have a look at the following example. It works in PHP 7 but not in PHP 8:
// /repo/ch05/php8_oop_bc_break_construct.php
class Text {
public $fh = '';
public const ERROR_FN = 'ERROR: file not found';
public function text(string $fn) {
if (!file_exists($fn))
throw new Exception(self::ERROR_FN);
$this->fh = new SplFileObject($fn, 'r');
}
public function getText() {
return $this->fh->fpassthru();
}
}
$fn = __DIR__ . '/../sample_data/gettysburg.txt';
$text = new Text($fn);
echo $text->getText();
root@php8_tips_php7 [ /repo/ch05 ]#
php php8_bc_break_construct.php
PHP Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; Text has a deprecated constructor in /repo/ch05/php8_bc_break_construct.php on line 4
Fourscore and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty and dedicated to the proposition that all men are created equal. ... <remaining text not shown>
root@php8_tips_php8 [ /repo/ch05 ]# php php8_bc_break_construct.php
PHP Fatal error: Uncaught Error: Call to a member function fpassthru() on string in /repo/ch05/php8_bc_break_construct.php:16
It's important to note that the error shown in PHP 8 does not tell you the real reason why the program failed. Hence, it's extremely important that you scan your PHP applications, especially older applications, to see if there's a method with the same name as the class. Accordingly, the best practice is to simply rename the method having the same name as the class to __construct().
Now let's have a look at how inconsistencies in handling Exception and exit in the class constructor have been addressed in PHP 8.
Another issue addressed in PHP 8 has to do with a situation where the class construct method either throws an Exception, or executes exit(). In PHP versions prior to PHP 8, if an Exception is thrown in the class constructor, the __destruct() method, if defined, is not called. On the other hand, if either exit() or die() (both PHP functions are equivalent to each other) is used in the constructor, the __destruct() method is called. In PHP 8, this inconsistency is addressed. Now, in either case, the __destruct() method is not called.
You may be wondering why this is of concern. The reason why you need to be aware of this important change is that you might have logic residing in the __destruct() method that was called in a situation where you also might call either exit() or die(). In PHP 8, you can no longer rely upon this code, which may cause a backwards compatibility break.
In this example, we have two connection classes. ConnectPdo uses the PDO extension to provide query results, whereas ConnectMysqli uses the MySQLi extension:
// /repo/src/Php7/Connector/ConnectInterface.php
namespace Php7Connector;
interface ConnectInterface {
public function query(string $sql) : array;
}
// /repo/src/Php7/Connector/Base.php
namespace Php7Connector;
abstract class Base implements ConnectInterface {
const CONN_TERMINATED = 'Connection Terminated';
public $conn = NULL;
public function __destruct() {
$message = get_class($this)
. ':' . self::CONN_TERMINATED;
error_log($message);
}
}
// /repo/src/Php7/Connector/ConnectPdo.php
namespace Php7Connector;
use PDO;
class ConnectPdo extends Base {
public function __construct(
string $dsn, string $usr, string $pwd) {
$this->conn = new PDO($dsn, $usr, $pwd);
}
public function query(string $sql) : array {
$stmt = $this->conn->query($sql);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
// /repo/src/Php7/Connector/ConnectMysqli.php
namespace Php7Connector;
class ConnectMysqli extends Base {
public function __construct(
string $db, string $usr, string $pwd) {
$this->conn = mysqli_connect('localhost',
$usr, $pwd, $db)
or die("Unable to Connect ");
}
public function query(string $sql) : array {
$result = mysqli_query($this->conn, $sql);
return mysqli_fetch_all($result, MYSQLI_ASSOC);
}
}
// /repo/ch05/php8_bc_break_destruct.php
include __DIR__ . '/../vendor/autoload.php';
use Php7Connector {ConnectPdo,ConnectMysqli};
$db = 'test';
$usr = 'fake';
$pwd = 'xyz';
$dsn = 'mysql:host=localhost;dbname=' . $db;
$sql = 'SELECT event_name, event_date FROM events';
$ptn = "%2d : %s : %s ";
try {
$conn = new ConnectPdo($dsn, $usr, $pwd);
var_dump($conn->query($sql));
} catch (Throwable $t) {
printf($ptn, __LINE__, get_class($t),
$t->getMessage());
}
$conn = new ConnectMysqli($db, $usr, $pwd);
var_dump($conn->query($sql));
root@php8_tips_php7 [ /repo/ch05 ]#
php php8_bc_break_destruct.php
15 : PDOException : SQLSTATE[28000] [1045] Access denied for user 'fake'@'localhost' (using password: YES)
PHP Warning: mysqli_connect(): (HY000/1045): Access denied for user 'fake'@'localhost' (using password: YES) in /repo/src/Php7/Connector/ConnectMysqli.php on line 8
Unable to Connect
Php7ConnectorConnectMysqli:Connection Terminated
root@php8_tips_php8 [ /repo/ch05 ]#
php php8_bc_break_destruct.php
15 : PDOException : SQLSTATE[28000] [1045] Access denied for user 'fake'@'localhost' (using password: YES)
PHP Warning: mysqli_connect(): (HY000/1045): Access denied for user 'fake'@'localhost' (using password: YES) in /repo/src/Php7/Connector/ConnectMysqli.php on line 8
Unable to Connect
Now that you have an idea how to spot a potential code break with regards to the __destruct() method along with a call to either die() or exit(), let's turn our attention to changes to the __toString() method.
The __toString() magic method is invoked when an object is used as a string. A classic example is when you simply echo an object. The echo command expects a string as an argument. When non-string data is provided, PHP performs type juggling to convert the data to string. As an object cannot be readily converted to string, the PHP engine then looks to see if __toString() is defined, and if so, returns its value.
The major change in this magic method is the introduction of Stringable, a brand new interface. The new interface is defined as follows:
interface Stringable {
public function __toString(): string;
}
Any class running in PHP 8 that defines the __toString() magic method silently implements the Stringable interface. This new behavior doesn't present any serious potential for a code break. However, since the class now implements the Stringable interface, you are no longer allowed to modify the __toString() method signature.
Here is a short example that reveals the new association with the Stringable interface:
// /repo/ch05/php8_bc_break_magic_to_string.php
class Test {
public $fname = 'Fred';
public $lname = 'Flintstone';
public function __toString() : string {
return $this->fname . ' ' . $this->lname;
}
}
$test = new Test;
$reflect = new ReflectionObject($test);
echo $reflect;
The first few lines of output running in PHP 7 (shown here) simply reveal that it's an instance of the Test class:
root@php8_tips_php7 [ /repo/ch05 ]#
php php8_bc_break_magic_to_string.php
Object of class [ <user> class Test ] {
@@ /repo/ch05/php8_bc_break_magic_to_string.php 3-12
Running the same code example in PHP 8, however, reveals the silent association with the Stringable interface:
root@php8_tips_php8 [ /repo/ch05 ]#
php php8_bc_break_magic_to_string.php
Object of class [ <user> class Test implements Stringable ] {
@@ /repo/ch05/php8_bc_break_magic_to_string.php 3-12
The output shows that even though you did not explicitly implement the Stringable interface, the association was created at runtime, and is revealed by the ReflectionObject instance.
Tip
For more information on magic methods, have a look at this documentation page: https://www.php.net/manual/en/language.oop5.magic.php.
Now that you have an understanding of the situations where PHP 8 code involving magic methods could cause a code break, let's have a look at changes in the serialization process.
There are many times when native PHP data needs to be stored in a file, or in a database table. The problem with current technology is that direct storage of complex PHP data such as objects or arrays is simply not possible, with some exceptions.
One way to overcome this limitation is to convert the object or array into a string. JSON (JavaScript Object Notation) is often chosen for this reason. Once the data has been converted into a string, it can easily be stored in any file or database. However, there is a problem with formatting objects with JSON. Although JSON is able to represent object properties well enough, it's incapable of directly restoring the original object's class and methods.
To address this deficiency, the PHP language includes two native functions, serialize() and unserialize(), that can easily convert objects or arrays into a string and restore them back to their original state. As wonderful as this sounds, there are a number of issues associated with native PHP serialization.
Before we can properly discuss the problem with the existing PHP serialization architecture, we need to have a closer look at how native PHP serialization works.
When a PHP object or array needs to be saved to a non-OOP environment such as a flat file or relational database table, serialize() can be used to flatten an object or array into a string, suitable for storage. Conversely, unserialize() restores the original object or array.
Here is a simple example that demonstrates this concept:
// /repo/ch05/php8_serialization.php
class Test {
public $name = 'Doug';
private $key = 12345;
protected $status = ['A','B','C'];
}
$test = new Test();
$str = serialize($test);
echo $str . " ";
O:4:"Test":3:{s:4:"name";s:4:"Doug";s:9:"Testkey"; i:12345;
s:9:"*status";a:3:{i:0;s:1:"A";i:1;s:1:"B";i:2;s:1:"C";}}
As you can see from the serialized string, the letter O designates Object, a is for array, s is for string and i is for integer.
$obj = unserialize($str);
var_dump($test, $obj);
Let's now have a look at the magic methods that supply legacy PHP serialization support: __sleep() and __wakeup().
The purpose of the __sleep() magic method is to provide a filter used to prevent certain properties from appearing in the serialized string. To use a user object as an example, you may wish to exclude sensitive properties such as a national identification number, credit card number, or password from the serialization.
Here is an example using the __sleep() magic method to exclude a password:
// /repo/ch05/php8_serialization_sleep.php
class Test {
public $name = 'Doug';
protected $key = 12345;
protected $password = '$2y$10$ux07vQNSA0ctbzZcZNA'
. 'lxOa8hi6kchJrJZzqWcxpw/XQUjSNqacx.';
public function __sleep() {
return ['name','key'];
}
}
$test = new Test();
$str = serialize($test)
echo $str . " ";
O:4:"Test":2:{s:4:"name";s:4:"Doug";s:6:"*key";i:12345;}
This is important in that, in most cases, the reason you need to serialize an object is you wish to store it somewhere, whether that be in a session file or in a database. If the filesystem or database is subsequently compromised, you have one less security vulnerability to worry about!
There is a potential code break involving the __sleep() magic method. In versions prior to PHP 8, if __sleep() returns an array with non-existent properties, they are still serialized and assigned a value of NULL. The problem with this approach is that when the object is subsequently unserialized, an extra property now appears, one that is not there by design!
In PHP 8, non-existent properties in the __sleep() magic method return are silently ignored. If your legacy code anticipates the old behavior and takes steps to delete the unwanted property, or even worse, if your code assumes the unwanted property exists, you will ultimately have an error. Such assumptions are extremely dangerous as they can lead to unexpected code behavior.
To illustrate the issue, have a look at the following code example:
class Test {
public $name = 'Doug';
public function __sleep() {
return ['name', 'missing'];
}
}
echo "Test instance before serialization: ";
$test = new Test();
var_dump($test);
echo "Test instance after serialization: ";
$stored = serialize($test);
$restored = unserialize($stored);
var_dump($restored);
root@php8_tips_php7 [ /repo/ch05 ]#
php php8_bc_break_sleep.php
Test instance before serialization:
/repo/ch05/php8_bc_break_sleep.php:13:
class Test#1 (1) {
public $name => string(4) "Doug"
}
Test instance after serialization:
PHP Notice: serialize(): "missing" returned as member variable from __sleep() but does not exist in /repo/ch05/php8_bc_break_sleep.php on line 16
class Test#2 (2) {
public $name => string(4) "Doug"
public $missing => NULL
}
root@php8_tips_php8 [ /repo/ch05 ]# php php8_bc_break_sleep.php
Test instance before serialization:
object(Test)#1 (1) {
["name"]=> string(4) "Doug"
}
Test instance after serialization:
PHP Warning: serialize(): "missing" returned as member variable from __sleep() but does not exist in /repo/ch05/php8_bc_break_sleep.php on line 16
object(Test)#2 (1) {
["name"]=> string(4) "Doug"
}
You might also observe that in PHP 7, a Notice is issued, whereas in PHP 8, the same situation produces a Warning. A pre-migration check for a potential code break in this case is difficult because you would need to determine, if the magic method __sleep() is defined, whether or not a non-existent property is being included in the list.
Let's now have a look at the counterpart method, __wakeup().
The purpose of the __wakeup() magic method is mainly to perform additional initialization on the unserialized object. Examples would be to restore a database connection or reinstate a file handle. Here's a very simple example that uses __wakeup() magic to re-open a file handle:
// /repo/ch05/php8_serialization_wakeup.php
class Gettysburg {
public $fn = __DIR__ . '/gettysburg.txt';
public $obj = NULL;
public function __construct() {
$this->obj = new SplFileObject($this->fn, 'r');
}
public function getText() {
$this->obj->rewind();
return $this->obj->fpassthru();
}
}
$old = new Gettysburg();
echo $old->getText();
$str = serialize($old);
PHP Fatal error: Uncaught Exception: Serialization of 'SplFileObject' is not allowed in /repo/ch05/php8_serialization_wakeup.php:19
public function __sleep() {
return ['fn'];
}
$str = serialize($old);
$new = unserialize($str);
echo $new->getText();
PHP Fatal error: Uncaught Error: Call to a member function rewind() on null in /repo/ch05/php8_serialization_wakeup.php:13
The problem, of course, is that the file handle was lost during serialization. When the object was unserialized, the __construct() method was not called.
public function __wakeup() {
self::__construct();
}
Now you have an idea of how PHP native serialization works, and also know a bit about the __sleep() and __wakeup() magic methods, as well as potential code breaks. Let's now have a look at an interface that was designed to facilitate the custom serialization of objects.
In order to facilitate the serialization of objects, the Serializable interface was added to the language beginning with PHP 5.1. The idea behind this interface was to provide a way of identifying objects that had the ability to serialize themselves. In addition, the methods specified by this interface were designed to provide some degree of control over object serialization.
As long as a class implements this interface, developers are assured that two methods are defined: serialize() and unserialize(). Here is the interface definition:
interface Serializable {
public serialize () : string|null
public unserialize (string $serialized) : void
}
Any class that implements this interface has its custom serialize() and unserialize() methods automatically invoked during native serialization or unserialization. To illustrate this technique, consider the following example:
// /repo/ch05/php8_bc_break_serializable.php
class A implements Serializable {
private $a = 'A';
private $b = 'B';
private $u = NULL;
public function serialize() {
$this->u = new DateTime();
return serialize(get_object_vars($this));
}
public function unserialize($payload) {
$vars = unserialize($payload);
foreach ($vars as $key => $val)
$this->$key = $val;
}
}
$a1 = new A();
var_dump($a1);
object(A)#1 (3) {
["a":"A":private]=> string(1) "A"
["b":"A":private]=> string(1) "B"
["u":"A":private]=> NULL
}
$str = serialize($a1);
$a2 = unserialize($str);
var_dump($a2);
object(A)#3 (3) {
["a":"A":private]=> string(1) "A"
["b":"A":private]=> string(1) "B"
["u":"A":private]=> object(DateTime)#4 (3) {
["date"]=> string(26) "2021-02-12 05:35:10.835999"
["timezone_type"]=> int(3)
["timezone"]=> string(3) "UTC"
}
}
Let's now have a look at issues with the serialization process for objects that implement the Serializable interface.
There is an overall problem with the earlier approach to serialization. If a class to be serialized has defined a __wakeup() magic method, it's not invoked immediately upon unserialization. Rather, any defined __wakeup() magic methods are first queued up, the entire chain of objects is unserialized, and only then are methods in the queue executed. This can result in a mismatch between what is seen by an object's unserialize() method compared to what is seen by its queued __wakeup() method.
This architectural flaw can result in inconsistent behavior and ambiguous results when dealing with objects that implement the Serializable interface. Many developers consider the Serializable interface to be severely broken due to the need to create back references when the serialization of nested objects occurs. This need arises in situations where nested serialization calls occur.
Such nested calls might occur, for example, when a class defines a method that in turn calls the PHP serialize() function. The order in which back references are created is preset in PHP serialization prior to PHP 8, potentially causing an avalanche of cascading failures.
The solution is to use two new magic methods to give you complete control over serialization and unserialization sequencing, described next.
A new way of controlling serialization was first introduced in PHP 7.4 and carried over into PHP 8. In order to take advantage of this new technology, all you need to do is to implement two magic methods: __serialize() and __unserialize(). If implemented, PHP turns control over serialization entirely to the __serialize() method. Likewise, unserialization is entirely controlled by the __unserialize() magic method. The __sleep() and __wakeup() methods, if defined, are ignored.
As a further benefit, PHP 8 provides full support for the two new magic methods in the following SPL classes:
Best practice
To gain full control over serialization, implement the new __serialize() and __unserialize() magic methods. You no longer need to implement the Serializable interface, nor do you need to define __sleep() and __wakeup(). For more information on the eventual discontinuation of the Serializable interface, see this RFC: https://wiki.php.net/rfc/phase_out_serializable.
As an example of the new PHP serialization usage, consider the following code example:
// /repo/ch05/php8_bc_break_serialization.php
class Test extends ArrayObject {
protected $id = 12345;
public $name = 'Doug';
private $key = '';
public function __construct() {
$this->key = bin2hex(random_bytes(8));
}
public function getKey() {
return $this->key;
}
public function __serialize() {
return ['id' => $this->id,
'name' => $this->name];
}
public function __unserialize($data) {
$this->id = $data['id'];
$this->name = $data['name'];
$this->__construct();
}
}
$test = new Test();
echo " Old Key: " . $test->getKey() . " ";
Here is how the key might appear:
Old Key: mXq78DhplByDWuPtzk820g==
$str = serialize($test);
echo $str . " ";
Here is how the serialized string might appear:
O:4:"Test":2:{s:2:"id";i:12345;s:4:"name";s:4:"Doug";}
Note from the output that the secret does not appear in the serialized string. This is important because if the storage location of the serialized string is compromised, a security vulnerability might be exposed, giving an attacker a way to break into your system.
$obj = unserialize($str);
echo "New Key: " . $obj->getKey() . " ";
Here is the last bit of output. Notice that a new key has been generated:
New Key: kDgU7FGfJn5qlOKcHEbyqQ==
As you can see, using the new PHP serialization feature is not complicated. Any timing issues are now fully in your control because the new magic methods are executed in the order in which the objects are serialized and unserialized.
Important note
PHP 7.4 and above understands serialized strings from older versions of PHP, however, strings serialized by PHP 7.4 or 8.x might not be properly unserialized by older versions of PHP.
Tip
For a full discussion, please see the RFC on custom serialization:
https://wiki.php.net/rfc/custom_object_serialization
You now have a full understanding of PHP serialization and the improved support provided by the two new magic methods. It's now time to shift gears and examine how PHP 8 expands variance support.
The concept of variance is at the heart of OOP. Variance is an umbrella term that covers how the various subtypes interrelate. Some 20 years ago, a pair of early computer scientists, Wing and Liskov, devised an important theorem that is at the heart of OOP subtypes, now known as the Liskov Substitution Principle.
Without going into the precise mathematics, this principle can be paraphrased as follows:
Class X can be considered a subtype of class Y if you are able to substitute an instance of X in place of an instance of Y, and the application's behavior does not change in any way.
Tip
The actual paper that first described and provided the precise mathematical formulaic definition of the Liskov Substitution Principle can be found here: A behavioral notion of subtyping, ACM Transactions on Programming Languages and Systems, by B. Liskov and J. Wing, November 1994 (https://dl.acm.org/doi/10.1145/197320.197383).
In this section, we examine how PHP 8 provides enhanced variance support in the form of covariant returns and contraviariant parameters. An understanding of covariance and contravariance will increase your ability to write good solid code. Without this understanding, your code might produce inconsistent results and become the source of many bugs.
Let's start by covering covariant returns.
Covariance support in PHP is designed to preserve the ordering of types from the most specific to the most general. A classic example of this is seen in how try / catch blocks are formulated:
try {
$pdo = new PDO($dsn, $usr, $pwd, $opts);
} catch (PDOException $p) {
error_log('Database Error: ' . $p->getMessage());
} catch (Throwable $t) {
error_log('Unknown Error: ' . $t->getMessage());
}
try {
$pdo = new PDO($dsn, $usr, $pwd, $opts);
} catch (Throwable $t) {
error_log('Unknown Error: ' . $t->getMessage());
} catch (PDOException $p) {
error_log('Database Error: ' . $p->getMessage());
}
Let's now have a look at how PHP covariance support applies to object method return data types:
interface FactoryInterface {
public function make(array $arr): ArrayObject;
}
class ArrTest extends ArrayObject {
const DEFAULT_TEST = 'This is a test';
}
class ArrFactory implements FactoryInterface {
protected array $data;
public function make(array $data): ArrTest {
$this->data = $data;
return new ArrTest($this->data);
}
}
$factory = new ArrFactory();
$obj1 = $factory->make([1,2,3]);
$obj2 = $factory->make(['A','B','C']);
var_dump($obj1, $obj2);
root@php8_tips_php7 [ /repo/ch05 ]#
php php8_variance_covariant.php
PHP Fatal error: Declaration of ArrFactory::make(array $data): ArrTest must be compatible with FactoryInterface::make(array $arr): ArrayObject in /repo/ch05/php8_variance_covariant.php on line 9
root@php8_tips_php8 [ /repo/ch05 ]#
php php8_variance_covariant.php
object(ArrTest)#2 (1) {
["storage":"ArrayObject":private]=>
array(3) {
[0]=> int(1)
[1]=> int(2)
[2]=> int(3)
}
}
object(ArrTest)#3 (1) {
["storage":"ArrayObject":private]=>
array(3) {
[0]=> string(1) "A"
[1]=> string(1) "B"
[2]=> string(1) "C"
}
}
ArrTest extends ArrayObject and is a suitable subtype that clearly meets the criteria defined by the Liskov Substitution Principle. As you can see from the last output, PHP 8 more fully embraces true OOP principles than the earlier versions of PHP. The end result is that your code and application architecture can be much more intuitive and logically reasonable when using PHP 8.
Let's now have a look at contravariant parameters.
Whereas covariance concerns the ordering of subtypes from general to specific, contravariance concerns the reverse: from specific to general. In PHP 7 and earlier, full support for contravariance was not available. Accordingly, implementing an interface or extending an abstract class, in PHP 7, parameter type hints are invariant.
In PHP 8, on the other hand, due to support for contravariant parameters, you are free to be specific in top-level super classes and interfaces. As long as the subtype is compatible, you can then modify the type hint in the extending or implementing class to be more general.
This gives you much more freedom in defining an overall architecture where you define interfaces or abstract classes. Developers using your interfaces or super classes are given a great deal more flexibility in PHP 8 when it comes to implementing descendent class logic.
Let's have a look at how PHP 8 support for contravariant parameters works:
// /repo/ch05/php8_variance_contravariant.php
class IterObj extends ArrayIterator {}
abstract class Base {
public abstract function stringify(IterObj $it);
}
class IterTest extends Base {
public function stringify(iterable $it) {
return implode(',',
iterator_to_array($it)) . " ";
}
}
class IterObj extends ArrayIterator {}
$test = new IterTest();
$objIt = new IterObj([1,2,3]);
$arrIt = new ArrayIterator(['A','B','C']);
echo $test->stringify($objIt);
echo $test->stringify($arrIt);
root@php8_tips_php7 [ /repo/ch05 ]#
php php8_variance_contravariant.php
PHP Fatal error: Declaration of IterTest::stringify(iterable $it) must be compatible with Base::stringify(IterObj $it) in /repo/ch05/php8_variance_contravariant.php on line 11
Because PHP 7.1 does not provide support for contravariant parameters, it treats the data type for its parameters as invariant, and simply displays a message indicating that the data type of the child class is incompatible with the data type specified in the parent class.
root@php8_tips_php8 [ /repo/ch05 ]# php php8_variance_contravariant.php
1,2,3
A,B,C
The main advantage you derive from PHP 8 support for covariant returns and contravariant parameters is the ability to override not only method logic but the method signature as well. You will find that although PHP 8 is much stricter in its enforcement of good coding practices, the enhanced variance support gives you greater freedom in designing your inheritance structure. In a certain sense, at least with regards to parameter and return value data types, PHP 8 is, if anything, less restrictive!
Tip
For a full explanation of how variance support is applied in PHP 7.4 and PHP 8, have a look here: https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters.
We'll now have a look at changes to the SPL and how those changes can have an impact on application performance after migrating to PHP 8.
The SPL is an extension that contains key classes that implement basic data structures and enhance OOP functionality. It was first introduced in PHP 5 and is now included by default in all PHP installations. Covering the entire SPL is beyond the scope of this book. Instead, in this section, we discuss where significant changes have occurred in the SPL when running PHP 8. In addition, we give you tips and guidance on SPL changes that have the potential to cause your existing applications to stop working.
We start by examining changes to the SplFileObject class.
SplFileObject is an excellent class that incorporates most of the standalone f*() functions, such as fgets(), fread(), fwrite(), and so forth, into a single class. SplFileObject ::__construct() method arguments mirror the arguments provided to the fopen() function.
The main difference in PHP 8 is that a relatively obscure method, fgetss(), has been removed from the SplFileObject class. The SplFileObject::fgetss() method, available in PHP 7 and below, mirrors the standalone fgetss() function in that it combines fgets() with strip_tags().
For the sake of illustration, let's assume you have created a website that allows users to upload text files. Before displaying content from the text file, you wish to remove any markup tags. Here is an example that uses the fgetss() method to accomplish this:
// /repo/ch05/php7_spl_splfileobject.php
$fn = $_GET['fn'] ?? '';
if (!$fn || !file_exists($fn))
exit('Unable to locate file');
$obj = new SplFileObject($fn, 'r');
$safe = '';
while ($line = $obj->fgetss()) $safe .= $line;
echo '<h1>Contents</h1><hr>' . $safe;
<h1>This File is Infected</h1>
<script>alert('You Been Hacked');</script>
<img src="http://very.bad.site/hacked.php" />
http://localhost:7777/ch05/php7_spl_splfileobject.php? fn=includes/you_been_hacked.html
As you can see from the output shown next, all HTML markup tags have been removed:
To accomplish the same thing in PHP 8, the code shown previously would need to be modified by replacing fgetss() with fgets(). We would also need to use strip_tags() on the line concatenated to $safe. Here is how the modified code might appear:
// /repo/ch05/php8_spl_splfileobject.php
$fn = $_GET['fn'] ?? '';
if (!$fn || !file_exists($fn))
exit('Unable to locate file');
$obj = new SplFileObject($fn, 'r');
$safe = '';
while ($line = $obj->fgets())
$safe .= strip_tags($line);
echo '<h1>Contents</h1><hr>' . $safe;
The output from the modified code is identical to that shown in Figure 5.1. We'll now turn our attention to changes in another SPL class: SplHeap.
SplHeap is a foundational class used to represent data structured as a binary tree. Two additional classes are also available that build upon SplHeap. SplMinHeap organizes the tree with the minimum value at the top. SplMaxHeap does the reverse, placing the maximum value at the top.
A heap structure is especially useful in situations where data arrives out of order. Once inserted into the heap, the item is automatically placed in its proper order. Thus, at any given moment, you can display the heap safe in the knowledge that all items will be in order without having to run one of the PHP sort functions.
The key to maintaining the automatic sort order is to define an abstract method, compare(). As this method is abstract, SplHeap cannot be instantiated directly. Instead, you need to extend the class and implement compare().
There is the potential for a backward-compatible code break when using SplHeap in PHP 8 as the method signature for compare() must be exactly as follows: SplHeap::compare($value1, $value2).
Let's now have a look at a code example that uses SplHeap to build a list of billionaires organized by last name:
Here is how the data produced by the class appears:
array(20) {
[0] => array(1) {
[177000000000] => string(10) "Bezos,Jeff"
}
[1] => array(1) {
[157000000000] => string(9) "Musk,Elon"
}
[2] => array(1) {
[136000000000] => string(10) "Gates,Bill"
}
... remaining data not shown
As you can see, the data is presented in descending order where the key represents net worth. In contrast, in our sample program, we plan to produce data in ascending order by last name.
// /repo/ch05/php7_spl_splheap.php
define('SRC_FILE', __DIR__
. '/../sample_data/billionaires.txt');
require_once __DIR__
. '/../src/Server/Autoload/Loader.php';
$loader = new ServerAutoloadLoader();
use ServicesBillionaireTracker;
$tracker = new BillionaireTracker();
$list = $tracker->extract(SRC_FILE);
$heap = new class () extends SplHeap {
public function compare(
array $arr1, array $arr2) : int {
$cmp1 = array_values($arr2)[0];
$cmp2 = array_values($arr1)[0];
return $cmp1 <=> $cmp2;
}
};
You might also note that the value for $cmp1 is assigned from the second array, and the value for $cmp2 is from the first array. The reason for this switch is because we wish to produce results in ascending order.
foreach ($list as $item)
$heap->insert($item);
$patt = "%20s %32s ";
$line = str_repeat('-', 56) . " ";
echo $tracker->view($heap, $patt, $line);
root@php8_tips_php7 [ /repo/ch05 ]#
php php7_spl_splheap.php
--------------------------------------------------------
Net Worth Name
--------------------------------------------------------
84,000,000,000 Ambani,Mukesh
115,000,000,000 Arnault,Bernard
83,600,000,000 Ballmer,Steve
... some lines were omitted to save space ...
58,200,000,000 Walton,Rob
100,000,000,000 Zuckerberg,Mark
--------------------------------------------------------
1,795,100,000,000
--------------------------------------------------------
You will note, however, that when we attempt to run the same program in PHP 8, an error is thrown. Here is the output of the same program running in PHP 8:
root@php8_tips_php8 [ /repo/ch05 ]# php php7_spl_splheap.php
PHP Fatal error: Declaration of SplHeap@anonymous::compare(array $arr1, array $arr2): int must be compatible with SplHeap::compare(mixed $value1, mixed $value2) in /repo/ch05/php7_spl_splheap.php on line 16
Accordingly, to get this working properly, we must redefine the anonymous class that extends SplHeap. Here is a modified version of that portion of the code:
$heap = new class () extends SplHeap {
public function compare($arr1, $arr2) : int {
$cmp1 = array_values($arr2)[0];
$cmp2 = array_values($arr1)[0];
return $cmp1 <=> $cmp2;
}
};
The only change is in the compare() method signature. When executed, the results (not shown) are identical. The full code for PHP 8 can be viewed at /repo/ch05/php8_spl_splheap.php.
This concludes our discussion of changes to the SplHeap class. Please note that the same change also applies to SplMinHeap and SplMaxHeap. Let's now have a look at a potentially significant change in the SplDoublyLinkedList class.
The SplDoublyLinkedList class is an iterator that's able to display information in either FIFO (First-In, First-Out) or LIFO (Last-In, First-Out) order. It's more common, however, to say that you can iterate through the list in either forward or reverse order.
This is a very powerful addition to any developer's library. To do the same thing with ArrayIterator, for example, would require at least a dozen lines of code! Accordingly, PHP developers like to use this class for situations where they need to navigate a list in either direction at will.
Unfortunately, there is a potential code break due to a difference in the return value of the push() and unshift() methods. The push() method is used to add a value at the end of the list. The unshift() method, on the other hand, adds value to the beginning of the list.
In PHP 7 and below, these methods, if successful, returned Boolean TRUE. If the method failed, it returned Boolean FALSE. In PHP 8, however, neither method returns a value. If you look at the method signature in the current documentation, you will see a return data type of void. The potential code break can arise where you check to return a value of either push() or unshift() before continuing.
Let's have a look at a simple example that populates a doubly linked list with a simple list of five values, and displays them in both FIFO and LIFO order:
// /repo/ch05/php7_spl_spldoublylinkedlist.php
$double = new class() extends SplDoublyLinkedList {
public function show(int $mode) {
$this->setIteratorMode($mode);
$this->rewind();
while ($item = $this->current()) {
echo $item . '. ';
$this->next();
}
}
};
$item = ['Person', 'Woman', 'Man', 'Camera', 'TV'];
foreach ($item as $key => $value)
if (!$double->push($value))
throw new Exception('ERROR');
This is the block of code where the potential code break exists. In PHP 7 and below, push() returns TRUE or FALSE. In PHP 8, there is no return value.
echo "**************** Foward ******************** ";
$forward = SplDoublyLinkedList::IT_MODE_FIFO
| SplDoublyLinkedList::IT_MODE_KEEP;
$double->show($forward);
echo " ************* Reverse ***************** ";
$reverse = SplDoublyLinkedList::IT_MODE_LIFO
| SplDoublyLinkedList::IT_MODE_KEEP;
$double->show($reverse);
Here is the output running in PHP 7.1:
root@php8_tips_php7 [ /repo/ch05 ]#
php php7_spl_spldoublylinkedlist.php
**************** Foward ********************
Person. Woman. Man. Camera. TV.
**************** Reverse ********************
TV. Camera. Man. Woman. Person.
root@php8_tips_php8 [ /home/ch05 ]#
php php7_spl_spldoublylinkedlist.php
PHP Fatal error: Uncaught Exception: ERROR in /home/ch05/php7_spl_spldoublylinkedlist.php:23
If no value is returned by push(), inside the if() statement PHP assumes NULL, which in turn is interpolated as Boolean FALSE! Accordingly, after the first push() command, the if() block causes an Exception to be thrown. Because the Exception is not caught, a fatal Error is generated.
To rewrite this block of code to work in PHP 8, all you need to do is to remove the if() statement, and not throw an Exception. Here's how the rewritten code block (shown in Step 2) might appear:
$item = ['Person', 'Woman', 'Man', 'Camera', 'TV'];
foreach ($item as $key => $value)
$double->push($value);
Now, if we execute the rewritten code, the results are seen here:
root@php8_tips_php7 [ /home/ch05 ]#
php php8_spl_spldoublylinkedlist.php
**************** Foward ********************
Person. Woman. Man. Camera. TV.
**************** Reverse ********************
TV. Camera. Man. Woman. Person.
Now you have an idea of how to use SplDoublyLinkedList, and also know about the potential code break relating to push() or unshift(). You also have an idea about potential code breaks when using various SPL classes and functions in PHP 8. This concludes our discussion for this chapter.
In this chapter, you learned about potential problems in OOP code when migrating to PHP 8. In the first section, you learned how a number of bad practices were allowed in PHP 7 and earlier versions, but now represent a potential code break in PHP 8. With this knowledge, you are a better developer and can deliver high-quality code to benefit your company.
In the next section, you learned good habits when using magic methods. Potential code breaks can occur because PHP 8 now enforces a degree of consistency not seen in earlier versions of PHP. These inconsistencies involve class constructor usage and certain aspects of magic method usage. The following section taught you about PHP serialization and how changes made in PHP 8 can make your code more resilient and less vulnerable to errors or attacks during the serialize and unserialize process.
In this chapter, you also learned about enhanced PHP 8 support for covariant return types and contravariant parameters. Having knowledge of variance, and how support has improved in PHP 8, allows you to be more creative and flexible when developing class inheritance structures in PHP 8. You now know how to write code that was simply not possible in earlier versions of PHP.
The last section covered a number of key classes in the SPL. You learned a great deal about how basic data structures such as heap and linked lists can be implemented in PHP 8. The information in that section was critical in helping you to avoid problems with code involving the SPL.
The next chapter continues the discussion on potential code breaks. The emphasis in the next chapter, however, is on procedural rather than object code.
3.145.119.199