# Object Oriented PHP In this chapter we are going to explore modern object oriented PHP. Whilst there isn't sufficient space in this book to do a deep dive on this topic, we hope that you will be able to use the contents of this chapter as a jumping point for your own further study and experimentation. To be clear, anything you see that you don't understand - you are absolutely encouraged to put the book down and go and start reading the PHP docs so that you can gain a fuller understanding of that particular feature. Some readers may already be very familiar with object oriented programming (OOP) in PHP and for those, this chapter might be a nice refresher and you never know, you might learn something new! If you are not familiar with modern OO PHP then there is likely to be a lot to take in and I seriously hope you don't find this overwhelming. All I can say is, it's worth persevering as OOP is fundamental to modern PHP. You are strongly encouraged to have a thorough read through of the offical documentation on Classes and Objects: https://www.php.net/manual/en/language.oop5.php ## What is OOP In this chapter, we are going to look in general at what we mean by Object Oriented Programming and what it means to you. ### Understanding the Phrase OOP stands for object oriented programming. It sounds like one of those CS phrases that get bandied about quite a bit without people really understanding what they mean. Lets work it from the back... #### Programming Hopefully you already have a good idea what we mean by programming. PHP is a programming language and by that we mean simply that it is a set of coding words, symbols and phrases that you can use to create code that ultimately does something. There is lots of inane debate about whether PHP truly is a programming language but let's not go there. We're here to get things done and that is where PHP excels. #### Oriented Oriented - "showing the direction in which something is aimed, directed towards or interested in something". So we're programming in a way that is directed towards or interested in something. The something we are interested in is Objects. In OOP we use classes and objects for everything. #### Objects What is an object? Well in PHP, an object is an "instance" of a "class". Here is the simplest class in PHP: ```php <?php class Foo {} ``` Simply enough it is defined with the word `class` and it has a name and then a body which is delineated by the curly braces. On its own this is pretty useless of course. It gets more interesting as we add code to the body of the class and meta information to the class definition. To create an object, we use the word `new` and can then optionally assign the result of this call to a variable. We now have an `object` ```php <?php $foo = new Foo(); ``` When we are talking about OOP. What we really mean is that we will compose our program entirely using objects. There must always be some code that resides at the global scope, but in an OOP program this code is very limited indeed and is simply used to bootstrap and initialise the OOP code. For example, this is the index.php file that all web requests are directed to when looking for a page in a symfony project: ```php <?php # https://github.com/symfony/demo/tree/main/public/index.php use App\Kernel; use Symfony\Component\Dotenv\Dotenv; use Symfony\Component\ErrorHandler\Debug; use Symfony\Component\HttpFoundation\Request; require dirname(__DIR__).'/vendor/autoload.php'; (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); if ($_SERVER['APP_DEBUG']) { umask(0000); Debug::enable(); } if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) { Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST); } if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) { Request::setTrustedHosts([$trustedHosts]); } $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response); ``` ### OOP Alternatives It might be easier to understand what OOP is when we compare it with other programming styles. #### Procedural This is the classic PHP style. If you work with Wordpress then you are still coding PHP like we did in the noughties. Procedural code works like a long list of instructions. You start at the top and continue reading till you hit the bottom. Of course all code works like this really, but the point is that in procedural code, there is much less use of scoping. A lot of work is done in the global scope though there will generally be lots of calls to global functions and maybe even a few objects floating around. The downsides to Procedural code are that it can be quite difficult to reuse chunks of code, there can be a large number of variables in the global scope which can make debugging things somewhat challenging, and finally the ability to test distinct parts of the code is somewhat diminished - you can only really test the system as a whole with end to end tests. ```php <?php # product_admin.php // bring in an application top file that contains 1000 lines of bootstrapping and including things require __DIR__.'/../../application_top.php'; if(isset($_GET['products_id'])){ // 500 lines of product related stuff if(isset($_POST['action'])){ switch($_POST['action']){ // 1000 lines of various post actions that might be happening } } } ``` In procedural code, it is normal for each page type to have its own file - this means that public traffic can be loading lots of different PHP files. Your public htdocs folder will generally include a number of php files that are publicly accessible and executable, possibly in sub folders as well for different system areas. #### Functional Functional programming, as its name implies, predominantly uses functions as the building block of code. In functional programming, we try to use "pure functions"- functions who for a given set of inputs will always return the same output, and will never cause any side effects. PHP has a powerful functional model and can be a great language for functional styles. I will admit I have never seen a codebase in PHP that you would regard as purely written in the functional programming style, but you do often see functional programming styles utilised in OOP and Procedural code. Here is a contrived example of how a functional programming index.php file might look: ```php <?php declare(strict_types=1); require __DIR__.'/../functions.php'; (static function(string $responseTxt, array $headers):void{ array_map( static function(string $header){ header($header); }, $headers ); echo $responseTxt; })(...(static function(array $request):array{ $headers=[]; $responseTxt=''; // some code to actually generate the page and headers return ['headers'=>$headers, 'responseTxt'=>$responseTxt]; })($_REQUEST)); ``` Whilst I have never actually seen a proper Functional Programming based project in the real world, you can and do see functional programming styles applied to OOP and Procedural projects. The benefits of using pure functions and avoiding shared state are applicable to all forms of programming. ### Learning Object Oriented PHP The first resource you should hit when learning PHP is the official documentation (https://www.php.net/manual/en /language.oop5.php) which is good, though I do suggest you avoid the comments as many are very old and not relevant at all. Probably the easiest way to get your head around OOP in PHP is to work on an existing open source project or framework that is written in the OOP style. There are many to choose from though the quality can vary. Here is a quick introduction to some popular projects: #### Frameworks The two most popular PHP frameworks are Laravel and Symfony. All modern PHP frameworks that I am aware of adopt the Model, View, Controller (MVC) structure. In a nutshell, this means that all requests are directed to a single "front controller" which then handles deciding exactly what the request is asking for and then routing that to the correct Controller. What that means is that there is a single publicly accessible index.php file as the only public PHP file in the htdocs folder. The Controllers job is to utilise the Model to retrieve any data required and perform any required processing before finally utilising the View to return a string of text to be rendered as HTML. It's worth reading more on MVC is this is not something you are familiar with. ##### Symfony Symfony is has been with PHP for a long time (since 2005) and has matured with the language over time. It is a great choice for "Enterprise" projects that require stability and a clear support lifetime. The general wisdom is that Symfony might be a better choice for long term projects that are likely to grow in complexity over time. The way that modern Symfony is constructed is highly modular, it allows you to keep the scale of your application as lean as possible, only bringing in functionality that you need. Symfony is built upon the concept of "Components" that are actually framework agnostic decoupled libraries that perform a certain function. This means that the codebase as a whole is kept very clean and you can work in a way that is less about the framework. https://github.com/symfony/symfony ##### Laravel Laravel is by far the most popular PHP framework at the moment. Laravel uses a lot of Symfony components under the hood which underlines how good the Symfony stuff is. Where Laravel and Symfony differ is primarily in their approach and philosophy. The general wisdom is that Laravel is easier and faster to work with for more simple projects, but if you are expecting to build the kind of project that might grow very complex over time then Symfony might be a better choice. https://github.com/laravel/laravel ##### Slim If you are looking to build something simple such as a plain API then you might enjoy working with more of a micro framework such as Slim. https://github.com/slimphp/Slim ##### Roll your own! Not something that I would recommend for a real production site, but for a personal project you can't really beat the experience of creating your own bespoke MVC framework. If you want to avoid reinventing every single wheel, you could take the same tack as many successful projects and utilise Symfony components for your basic building blocks, such as HttpKernel https://github.com/symfony/http-kernel #### Applications Learning frameworks is all well and good, but if you need to solve a fairly standard project such as building an online shop or some form of blog or brochure site, then you might be better to choose a more complete package that already includes all the basic features you need. I have pulled together a few suggestions for you. These are all generally framework based and so can give you some good ideas on how to use a framework in a full application. ##### E-Commerce An area where PHP really shines in terms of open source packages available is E-Commerce. The main problem facing the modern e-commerce PHP developer is that there is so much choice! ###### Sylius Sylius is Symfony based and has been built using modern practices such as TDD and BDD (we will get into these later in the book). It doesn't really claim to be the "all bells and whistles included" platform that perhaps Magento does , but instead it offers itself as a solid platform for a more bespoke E-Commerce solution. https://github.com/Sylius/Sylius ###### Magento Magento is the big daddy in open source PHP E-Commerce. Magento is based on quite an old fashioned framework at it's core and is unusual in that it is not using Symfony or Laravel as it's framework. Magento 2 brings in some interesting strategies such as "Aspect Oriented Programming" and extensive use of code generation. https://github.com/magento/magento2 ###### Shopware The latest version of Shopware is Symfony based and aims to provide Magento levels of functionality with the development best practice approach of Sylius. It's an interesting proposition and definitely worth a look. https://github.com/shopware/platform ##### CMS Content Management Systems are designed to manage generic content driven sites such as blogs and brochure sites. ###### Drupal Drupal recently relaunched built upon Symfony components. It is a large scale CMS platform suitable for bigger content projects. It is not a Symfony framework project but instead has it's own framework and custom Kernel https://git.drupalcode.org/ ###### OctoberCMS October CMS is a much smaller project than Drupal but worth a look if you prefer something Laravel based. https://github.com/octobercms/october ## Inheritance and Composition, Encapsulation and Visibility, Interfaces and Concretions A foundational feature of OOP in PHP and something you must understand well if you are to successfully grasp OO PHP. ### Encapsulation and Visibility PHP classes "encapsulate data and functionality". The word encapsulate simply means "enclose in a capsule". What this means is that the things inside the capsule, in our case the class, are hidden away. Of course, we can't hide everything away, or the class has no real utility, and so we need to enable the outside world to access class property and methods but we want to keep tight control on this. Here is the offical docs page that you should have a thorough read of: https://www.php.net/manual/en/language.oop5.visibility.php #### Private, Protected, Public By default, class properties and methods are `public`. This means that they are freely accessible for reading/writing/calling from outside the class. This is very, _very_ rarely the way you want it. Instead we want to pull the curtains drawn and only expose the things that we really want to share. By setting class properties and methods to `private` we prevent any outside access at all. I would suggest this should be your default accessibility with all class members. Make everything private. If you want to allow people to read your properties, you can either define `getter` methods, or you can use some PHP magic to allow readonly by implementing a `__get` method or `__call` method for properties and methods respectively. If you have a child class, it will also not be able to read the properties or methods of your class. Once you get into inheritance then you might find it easier to mark properties and methods as `protected`. This level of visibility is the same as private, but also allows child classes to have access. The final, most open and therefore should be the least used, is `public` which allows full access to all comers. The door is wide open and everyone is invited. Generally you should be doing this as infrequently as possible. ### Inheritance To get a full understanding of the PHP inheritance model your first port of call should be the official documentation: https://www.php.net/manual/en/language.oop5.inheritance.php Inheritance in OOP is referring to the fact that a class can inherit properties and methods from a parent class. ```php <?php declare(strict_types=1); class MyParentClass { protected int $foo = 1; } class MyChildClass extends MyParentClass { public function getFoo(): int { return $this->foo; } } ``` The inheritance model in PHP is based on single inheritance. This means that a class or interface can only have a single parent. Some other languages support multiple inheritance, where a single class can have multiple parents. This can be more powerful but that comes at the cost of significant extra complexity. There can be multiple levels of parent->child relations, though generally it is advisable to keep the number of levels as low as possible to avoid excessive complexity and coupling. ```php <?php declare(strict_types=1); class MyGrandParentClass { protected int $foo = 1; } class MyParentClass extends MyGrandParentClass { protected int $bar = 2; } class MyChildClass extends MyParentClass { public function getFoo(): int { return $this->foo; } public function getBar(): int { return $this->bar; } } ``` Have a look through the following code, discussion to follow: [Code Snippet](./../../../php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/force_inheritance.php) ```php <?php declare(strict_types=1); /** * A trait to allow public read but not public right for all properties in a class. * Suggest usage is combined with the `@property-read` annotation */ trait PublicRead { final public function __get(string $name): mixed { $this->assertPropertyExists($name); return $this->$name; } private function assertPropertyExists(string $name): void { if ($this->__isset($name)) { return; } throw new \InvalidArgumentException( 'Property ' . $name . ' does not exist or is not accessible in ' . static::class ); } final public function __set(string $name, mixed $value): void { $this->assertPropertyExists($name); throw new \RuntimeException('Property ' . $name . ' is read only'); } final public function __isset( $name ): bool { return property_exists($this, $name); } } # Class CAN be instantiated and inherited from /** * @property-read $name */ class Person { /** Using the PublicRead trait to allow read only access to properties */ use PublicRead; public function __construct( protected string $name ) { } } # Class CANNOT be instantiated, CAN be inherited from /** * @property-read $id */ abstract class AbstractUser extends Person { public function __construct( protected int $id, protected string $name ) { parent::__construct($name); } # Abstract function - must be defined in child classes abstract public function __toString(); } # Class CAN be instantiated, CANNOT be inherited from /** * @property-read $id * @property-read $name * @property-read $recentlyViewedPages */ final class FrontEndUser extends AbstractUser { /** @var string[] */ private array $recentlyViewedPages; public function __construct( protected int $id, protected string $name, string ...$recentlyViewedPages ) { parent::__construct($id, $name); $this->recentlyViewedPages = $recentlyViewedPages; } public function __toString(): string { return "front end user $this->name ($this->id) has recently viewed: " . print_r($this->recentlyViewedPages, true); } } # Class CAN be instantiated, CANNOT be inherited from /** * @property-read $permName * @property-read $can */ final class AdminPermission { public const CAN_EDIT = 'canEdit'; public const CAN_VIEW = 'canView'; public const PERMS = [ self::CAN_EDIT, self::CAN_VIEW, ]; /** Using the PublicRead trait to allow read only access to properties */ use PublicRead; public function __construct( private string $permName, private bool $can, ) { $this->assertValidName(); } private function assertValidName(): void { if (in_array($this->permName, self::PERMS, true)) { return; } throw new \InvalidArgumentException( 'Invalid permName ' . $this->permName . ', must be one of ' . print_r(self::PERMS, true) ); } } # Class CAN be instantiated, CANNOT be inherited from /** * @property-read $id * @property-read $name * @property-read $permissions */ final class AdminUser extends AbstractUser { /** @var array<string,AdminPermission> */ private array $permissions; public function __construct( protected int $id, protected string $name, AdminPermission ...$permissions ) { parent::__construct($id, $name); array_map( function (AdminPermission $perm) { $this->permissions[$perm->permName] = $perm; }, $permissions ); } public function __toString(): string { return "\n\nadmin user $this->name ($this->id) has these permissions: \n" . implode("\n", array_map( static function (AdminPermission $perm) { return $perm->permName . ': ' . ($perm->can ? 'true' : 'false'); }, $this->permissions ) ) . "\n"; } } $frontEndUser = new FrontEndUser( 2, 'Steve', 'http://php.com', 'http://something.com' ); echo $frontEndUser; $adminUser = new AdminUser( 1, 'Joseph', new AdminPermission(permName: AdminPermission::CAN_VIEW, can: true), new AdminPermission(permName: AdminPermission::CAN_EDIT, can: true) ); echo $adminUser; ?> OUTPUT: front end user Steve (2) has recently viewed: Array ( [0] => http://php.com [1] => http://something.com ) admin user Joseph (1) has these permissions: canView: true canEdit: true ``` So in the above code there are multiple things going on, some may not be familiar and that's fine - we've still got lots of ground to cover. In particular, we're focussing on the inheritance related items. You can see that there is a normal class, `Person`. The other classes are prefixed with either `abstract` or `final` and these modifiers affect inheritance. We can force the use of inheritance by declaring a class `abstract` and perhaps including `abstract` methods that must be implemented by a child class. You can see that in the `AbstractUser` user class where we decide that our child classes must implement to the magic `__toString` method. We can ensure that a class can not have a child class by declaring it `final`. Some modern PHP developers adopt a policy of declaring all classes `final` by default. This has the benefit of ensuring that heritability is something that has to be consciously enabled and personally I think this is a good idea if you can make it work for you. #### Traits Traits are an alternative approach to sharing code between classes. They can be quite tricky to understand but when you need them they can be very useful indeed. Generally the only time you should use traits is when you need `$this` to be the current class and you need to ensure you have access to all `private` properties and methods. I have implemented the `PublicRead` functionality as a trait so that it can be shared amongst classes, and can interrogate class properties without any issues of visibility. https://www.php.net/manual/en/language.oop5.traits.php #### Constructor Promotion PHP 8 brings in a shorthand approach to defining class properties that are set at construction time. Properties defined with a visbility modifier in the `__construct` argument list will automatically be set as properties with the specified access level. This saves a bit of boilerplate and is something you should definitely use: ```php class OldBoiler { private string $foo; private int $bar; public function __construct(string $foo, int $bar){ $this->foo=$foo; $this->bar=$bar; } } class ShinyNew { public function __construct( private string $foo, private int $bar ){} } ``` https://www.php.net/manual/en/language.oop5.decon.php #### Class Constants In the previous code you might have noted the use of class constants, as defined with the `const` keyword. ```php public const CAN_EDIT = 'canEdit'; public const CAN_VIEW = 'canView'; public const PERMS = [ self::CAN_EDIT, self::CAN_VIEW, ]; ``` Class constants have the same PPP access rules as everything else, though they are by definition read only, and so generally it is perfectly safe to have them marked as `public`. I strongly encourage the use of class constants to be the mechanism for storing your "magic strings" - those important strings that are meaningful and important and are often reused across your code base. The general guidance is to avoid using magic string directly and instead refer to the class constant. ```php new AdminPermission(permName: AdminPermission::CAN_VIEW, can: true); ``` https://www.php.net/manual/en/language.oop5.constants.php #### Function/Method Arguments Another thing you might have seen the above examples are some different approaches to function arguments, namely the splat operator `...` and named arguments `AdminPermission(permName: AdminPermission::CAN_VIEW, can: true)`. Have a read of the offical docs here https://www.php.net/manual/en/functions.arguments.php The splat operator implies a typed array in many ways. It allows a function/method to be called with a variable number of arguments and inside the function body, the arguments are presented as an array. The splat operator also lets us pass an array to a method and unpack it into a separate arguments. For example: ```php <?php declare(strict_types=1); (function(string ...$numbers)){ echo implode("\n", $numbers); })(...['one','two','three']); ``` Named arguments allow us to pass function/method arguments in an arbitrary order by specifying them by name. My hope is that this can put to bed the inane moaning about PHP argument order. It can also generally improve readability which can only be a good thing. Array filter is callback second by default, we can go with that but still use named params to improve readability. ```php <?php declare(strict_types=1); # array_filter ( array $array , callable|null $callback = null , int $mode = 0 ) : array $filtered=array_filter(input: [true,false,true], callback: function($item){return $item==true}); ``` Array map is callable first by default. If we want to be consistent, we can decide to stick with input first ```php <?php declare(strict_types=1); # array_map ( callable|null $callback , array $array , array ...$arrays ) : array $mapped=array_map(input: [true,false,true], callback: function($item){return $item=!$item}); ``` ### Composition So inheritance seems great doesn't it. You can keep your code really DRY by defining commonly used functionality in a parent or abstract class and then share that across a huge number of children. That's what a lot of people thought a few years ago, and then they worked on Magento... > #### What does DRY mean? > D.R.Y stands for "don't repeat yourself" and with regards to coding, it means avoid copy pasting and duplicate > code and instead structure things so that any code that needs to be reused is packaged up in a way that > facilitates that. It could be as simple as a global function or constant or can be a full-blown class that > encapsulates the bundle of functionality that you want to share. If we went with the hugely overused analogy of animals when discussing inheritance, then we could define a whole taxonomy with an `AbstractOrganism` at the top and then child classes all the way down... `AbstractVertebrate` would have a huge number of child classes, and right at the bottom we might have the actual plants and animals defined as `final` classes. The challenge with this kind of structure is that it can be very complicated to understand and very difficult and brittle to work on. A change somewhere high up the chain of inheritance could have unexpected consequences. If you have seen Jurassic park then you no doubt remember the discussion where the mathematician is trying to explain chaos. “It simply deals with unpredictability in complex systems,” he says. “The shorthand is 'the butterfly effect. ' A butterfly can flap its wings in Peking, and in Central Park, you get rain instead of sunshine.” You make a minor change in a middle tier class and cause weird bugs in far-flung areas of your project. So how can we avoid this kind of complexity? By using composition instead of inheritance. This simply means that we build our classes by composing them from other classes. We remove inheritance as a way of sharing functionality and instead we put that piece of functionality in a class and share that class everywhere the functionality is required. The goal of this approach is to reduce complexity and to increase testability. Have a look at the following code, do you prefer it? [Code Snippet](./../../../php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/composition_over_inheritance.php) ```php <?php declare(strict_types=1); final class Person { public function __construct( private string $name ) { } public function getName(): string { return $this->name; } } final class UserData { public function __construct( private int $id, private Person $person ) { } public function getId(): int { return $this->id; } public function getName(): string { return $this->person->getName(); } } final class UrlCollection { /** * @var string[] */ private array $urls; public function __construct(string ...$urls) { $this->urls = $urls; } /** * @return string[] */ public function getUrls(): array { return $this->urls; } } interface User { public function __toString(); } final class FrontEndUser implements User { public function __construct( private UserData $userData, private UrlCollection $recentlyViewedPages ) { } public function __toString(): string { return "front end user {$this->userData->getName()} ({$this->userData->getId()}) has recently viewed: " . print_r($this->recentlyViewedPages->getUrls(), true); } } interface AdminPermissionInterface { public const CAN_EDIT = 'canEdit'; public const CAN_VIEW = 'canView'; public const PERMS = [ self::CAN_EDIT, self::CAN_VIEW, ]; public function getPermName(): string; public function allowed(): bool; } final class CanEditPermission implements AdminPermissionInterface { public function __construct( private bool $allowed ) { } public function getPermName(): string { return self::CAN_EDIT; } public function allowed(): bool { return $this->allowed; } } final class CanViewPermission implements AdminPermissionInterface { public function __construct( private bool $allowed ) { } public function getPermName(): string { return self::CAN_VIEW; } public function allowed(): bool { return $this->allowed; } } final class AdminUser implements User { /** @var array<string,AdminPermission> */ private array $permissions; public function __construct( private UserData $userData, AdminPermissionInterface ...$permissions ) { array_map( function (AdminPermissionInterface $perm) { $this->permissions[$perm->getPermName()] = $perm; }, $permissions ); } public function __toString(): string { return "\n\nadmin user {$this->userData->getName()} ({$this->userData->getId()}) has these permissions: \n" . implode("\n", array_map( static function (AdminPermissionInterface $perm) { return $perm->getPermName() . ': ' . ($perm->allowed() ? 'true' : 'false'); }, $this->permissions ) ) . "\n"; } } $frontEndUser = new FrontEndUser( new UserData(id: 2, person: new Person(name: 'Steve')), new UrlCollection('http://php.com', 'http://something.com') ); echo $frontEndUser; $adminUser = new AdminUser( new UserData(id: 1, person: new Person(name: 'Joseph')), new CanEditPermission(allowed: true), new CanViewPermission(allowed: true) ); echo $adminUser; ?> OUTPUT: front end user Steve (2) has recently viewed: Array ( [0] => http://php.com [1] => http://something.com ) admin user Joseph (1) has these permissions: canEdit: true canView: true ``` What you will notice is that there is firstly no inheritance at all, all classes are marked as final. What we do have is objects containing (being composed of) other objects by injection in the `__construct` method. We also have use of interfaces which are the way to achieve some of the benefits of inheritance without actually using inheritance. #### Interfaces You might have noticed that in this code that we have replaced the concept of `abstract` method with an `interface` instead. An interface is a bit like an abstract class, but it contains no functionality at all, instead it simply acts as a blueprint of what the class should do. This is defined by the methodNames, the arguments to those methods and the return types. You can also specify public constants on interfaces which can be very useful. Interfaces can extend other interfaces and a class can implement one or more interfaces. When we are type hinting in methods and properties, we can hint for the interface rather than the class. This allows us to build really flexible library code that can be easily reused across different projects as we don't couple our code to specific external code. Due to the fact that interfaces can only hint methods and not properties, then we are forced to adopt the style of creating "getters" for all the private or protected properties that we want to expose for public reading. A getter is simply a method that returns the property and by convention is called `getPropertyName`.Thankfully modern IDEs generally make this very simple with a built in function to automatically generate getters so this is no particular hardship. ## SPL - the Standard PHP Library PHP Ships with something called the Standard PHP Library. Generally this includes a set of built in classes, interfaces and functions which provide foundational features and functionality that you can then use in your own code. The SPL is designed to offer solutions to common problems and is guaranteed to be present in any PHP installation. Probably the single most utilised SPL feature is the `spl_autoload_register` which we will learn more about later in the book. As with anything PHP, your first point of reference should be to read the official documentation which for SPL is located at https://www.php.net/manual/en/book.spl.php ### Data Structures The first items listed in the docs are the SPL data structures. https://www.php.net/manual/en/spl.datastructures.php These are designed to offer more advanced features than the simple array. Unfortunately these are not very commonly used. It doesn't take a lot of searching to find articles that can explain why the SPL data structures are generally avoided. PHP 7 brought big performance improvements to the PHP array which removed a lot of the pain that might have tempted you to use the SPL data structures. Generally the only time you would look at using these is if you are juggling huge amounts of data and need to find some ways of improving performance and reducing resource usage. If you do have an requirement for performance and big data then you could take a look at "ext-ds" which is an extension you can add to PHP which brings some performance optimised data structures to PHP. Though it does not have the advantage of being built in by default, it generally performs a lot better than SPL and standard arrays. https://github.com/php-ds/ext-ds ### Iterators More commonly used are the iterators. These are like super powers for the humble `foreach` loop and allow us to iterate over a number of different things, though the most I have seen it being used is when iterating over the filesystem in some form. Unfortunately the iterators are not particularly well documented and so you are likely to have to do a bit of Stack Overflow and your own personal experimentation in order to get what you need, but as always you should start with the official docs: https://www.php.net/manual/en/spl.iterators.php Here is a bit of fun with iterators [Code Snippet](./../../../php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/iterator.php) ```php <?php declare(strict_types=1); const BASE_DIR = '/tmp/iterator-fun/'; echo " First we have a recursive function that will remove a nested directory that contains files. This is used to clean up from any previous run "; function recursiveRemoveDir(string $path): void { $traversable = new \FilesystemIterator( $path, flags: \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS ); $items = iterator_to_array($traversable, false); $items = array_reverse($items); foreach ($items as $item) { if (is_dir($item)) { recursiveRemoveDir($item); rmdir($item); } if (is_file($item)) { unlink($item); } } } recursiveRemoveDir(BASE_DIR); mkdir(directory: BASE_DIR . 'foo/bar/baz', permissions: 0777, recursive: true); mkdir(directory: BASE_DIR . 'boo/far/faz', permissions: 0777, recursive: true); $directoryIterator = new RecursiveDirectoryIterator(directory: BASE_DIR); $iteratorIterator = new RecursiveIteratorIterator( $directoryIterator, mode: RecursiveIteratorIterator::SELF_FIRST ); /** @var SplFileInfo $i */ $toggle = false; $visited = []; foreach ($iteratorIterator as $i) { $path = $i->getRealPath(); if (isset($visited[$path])) { continue; } $visited[$path] = true; if (false === str_starts_with(haystack: $path, needle: BASE_DIR)) { continue; } if (false === $i->isDir()) { continue; } echo "$path\n"; $prefix = ($toggle = !$toggle) ? 'blue_' : 'green_'; tempnam($path, $prefix); } echo " Tree after first recursive pass through and creating temp files: "; passthru(sprintf("tree %s", BASE_DIR)); echo " Now we're going to loop over the directory again using a filter to pull out blue only Notice the use of an anonymous class - https://www.php.net/manual/en/language.oop5.anonymous.php "; $filterIterator = new class( new RecursiveIteratorIterator($directoryIterator) ) extends FilterIterator { public function accept(): bool { $current = $this->getCurrent(); if ($current->isDir()) { return false; } return $this->isBlue(filename: $current->getBasename()); } private function isBlue(string $filename) { return str_contains(haystack: $filename, needle: 'blue'); } private function getCurrent(): SplFileInfo { $current = $this->current(); if ($current instanceof SplFileInfo) { return $current; } throw new \RuntimeException('unexpected current value ' . var_export($current, true)); } }; foreach ($filterIterator as $i) { echo "\n " . $i->getRealPath(); } echo " And so ends the whistle stop tour of iterators, I hope you had fun :) "; ?> OUTPUT: First we have a recursive function that will remove a nested directory that contains files. This is used to clean up from any previous run /tmp/iterator-fun/foo /tmp/iterator-fun/foo/bar /tmp/iterator-fun/foo/bar/baz /tmp/iterator-fun/boo /tmp/iterator-fun/boo/far /tmp/iterator-fun/boo/far/faz Tree after first recursive pass through and creating temp files: /tmp/iterator-fun/ ├── boo │   ├── far │   │   ├── blue_MyUlY3 │   │   └── faz │   │   └── green_v7UQdP │   └── green_BfNRIi └── foo ├── bar │   ├── baz │   │   └── blue_g1yotx │   └── green_3gMWdM └── blue_yVRvY0 6 directories, 6 files Now we're going to loop over the directory again using a filter to pull out blue only Notice the use of an anonymous class - https://www.php.net/manual/en/language.oop5.anonymous.php /tmp/iterator-fun/foo/blue_yVRvY0 /tmp/iterator-fun/foo/bar/baz/blue_g1yotx /tmp/iterator-fun/boo/far/blue_MyUlY3 And so ends the whistle stop tour of iterators, I hope you had fun :) ``` ## Exceptions and Error Handling An Exception in modern PHP is an object that represents the data for a particular error condition. Exceptions are not generally created with `create` but instead they are thrown - as in they are created and unleashed with one magic keyword - `throw`. If you don't know about exceptions, it's time to hit the docs: https://www.php.net/manual/en/language.exceptions.php Once an exception is thrown then it must be caught or it will become a `Fatal Error`. I always imagine it a bit like setting off the timer on a bomb. The bomb gets passed from handler to handler until it either hits someone who has the right tools to disable it, or we run out of layers that can possibly handle it and it goes off.. boom. We catch exceptions with the `catch` keyword. The catch keyword is always used with a type hint for what kind of exceptions it will catch. This can be quite specific, or can be totally general by hinting for `Throwable` which is the interface implemented by all PHP Exceptions and built in Error classes. If you want to see the full tree of built in classes and interfaces for multiple PHP versions, this code snippet is well worth a look: https://3v4l.org/sDMsv The mechanism for catching exceptions is to use a `try`/`catch` block, optionally with a `finally` on the end [Code Snippet](./../../../php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/exception_simple.php) ```php <?php declare(strict_types=1); function dumpExceptionDetails(Throwable $throwable): string { $class = $throwable::class; return " Caught $class with message: {$throwable->getMessage()} Stack Trace: {$throwable->getTraceAsString()} "; } class FooException extends Exception { } class BarException extends Exception { } try { throw new BarException('something went wrong'); } catch (FooException | BarException $superCustomException) { echo " This block is going to be executed, because we have caught one of the specific exception types we are catching "; echo dumpExceptionDetails($superCustomException); } catch (Exception $exception) { echo "This block will not be executed, because the exception has already been caught"; echo dumpExceptionDetails($exception); } catch (Throwable $throwable) { echo "This block will not be executed, because the exception has already been caught"; echo dumpExceptionDetails($throwable); } finally { echo " The finally block always happens.. "; } ?> OUTPUT: This block is going to be executed, because we have caught one of the specific exception types we are catching Caught BarException with message: something went wrong Stack Trace: #0 {main} The finally block always happens.. ``` Note that we can define multiple classes of Exception to be caught in a single catch block using the ` | ` symbol to separate them. ### Exception and Error Handling Best Practices Once you start to use exceptions, you will realise that they are amazing and will want to ensure that all your errors are exceptions. Due to PHP's long life and incumbent legacy, that is not the case by default. If you want to ensure that all errors become exceptions that you can handle, then you need to implement some boilerplate. Every single OOP framework or project I have seen does this for you, but it is worth knowing how to do it and what is going on. [Code Snippet](./../../../php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/custom_error_handler.php) ```php <?php declare(strict_types=1); const DEBUG_MODE = true; /** * This bit of magic boilerplate will turn any old fashioned PHP error into an ErrorException which you can then catch * in your code. */ set_error_handler(static function (int $severity, string $message, string $file, int $line) { if (error_reporting() & $severity) { return; } throw new ErrorException($message, 0, $severity, $file, $line); }); /** * This bit of magic boilerplate becomes your ultimate fallback should any exceptions bubble past all the catch blocks * in your code. */ set_exception_handler(static function (Throwable $throwable) { if (false === DEBUG_MODE) { echo " An error has occurred, please look at a happy picture whilst our engineers fix this for you :) "; return; } echo " You are clearly a developer, please see a load of useful debug info: " . var_export($throwable, true); }); echo " And now to do something silly "; substr(string: new stdClass(), offset: 'cheese'); ?> OUTPUT: And now to do something silly You are clearly a developer, please see a load of useful debug info: TypeError::__set_state(array( 'message' => 'substr(): Argument #1 ($string) must be of type string, stdClass given', 'string' => '', 'code' => 0, 'file' => '/home/book_ops/php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/custom_error_handler.php', 'line' => 46, 'trace' => array ( 0 => array ( 'file' => '/home/book_ops/php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/custom_error_handler.php', 'line' => 46, 'function' => 'substr', 'args' => array ( 0 => (object) array( ), 1 => 'cheese', ), ), ), 'previous' => NULL, )) ``` Whilst the above is educational, I strongly suggest you don't reinvent the wheel and instead have a look at an existing implementation. Have a look at: * https://github.com/symfony/error-handler * https://github.com/filp/whoops ## Meta Programming with Reflection and Attributes Metaprogramming is a technique where your code and runtime becomes data that you can work with. It can be a bit mind bending but also awesomely powerful. PHP comes with something called reflection which allows you to examine your code and objects at run time. https://www.php.net/manual/en/book.reflection.php This is most useful in library code and is used extensively in projects such as Doctrine ORM (which you really should have a look at if you want to work with databases). https://github.com/doctrine/orm [Code Snippet](../../../php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/reflection.php) ```php <?php declare(strict_types=1); echo " Reflection can be really useful "; class Kid { public function __construct( private string $name, private int $age ) { } private function nameChange(string $newName): void { $this->name = $newName; } public function getName(): string { return $this->name; } public function getAge(): int { return $this->age; } } $instance = new Kid('Anna', 9); $reflection = new ReflectionObject($instance); echo " You can get information about a class/object "; var_dump($reflection->getMethods()); echo " And you can do things you aren't really supposed to be able to do... "; echo " Her name is " . $instance->getName(); $method = $reflection->getMethod('nameChange'); $method->setAccessible(true); $method->invoke($instance, 'Gwenn'); echo " And now her name is " . $instance->getName(); ?> OUTPUT: Reflection can be really useful You can get information about a class/object array(4) { [0]=> object(ReflectionMethod)#3 (2) { ["name"]=> string(11) "__construct" ["class"]=> string(3) "Kid" } [1]=> object(ReflectionMethod)#4 (2) { ["name"]=> string(10) "nameChange" ["class"]=> string(3) "Kid" } [2]=> object(ReflectionMethod)#5 (2) { ["name"]=> string(7) "getName" ["class"]=> string(3) "Kid" } [3]=> object(ReflectionMethod)#6 (2) { ["name"]=> string(6) "getAge" ["class"]=> string(3) "Kid" } } And you can do things you aren't really supposed to be able to do... Her name is Anna And now her name is Gwenn ``` If you want to take your meta programming to the next level, then it is time to take a look at Attributes, a new feature that has come in with PHP8, though is very similar to something that has been achieved for years in PHP by the use of specially formatted comments and some magic with reflection (I told you reflection was powerful). https://www.php.net/manual/en/language.attributes.overview.php Attributes allow you to attach real PHP code to your code to create dynamic meta data and empower all kinds of interesting things. [Code Snippet](./../../../php-book-code/src/Part1-Modern-PHP-and-MySQL/Chapter1-Object-Oriented-PHP/attributes.php) ```php <?php declare(strict_types=1); /** * This class is the attribute itself. It has the magical `#[Attribute]` attribute which marks it as such */ #[Attribute] class WrittenBy { public function __construct( private string $name ) { } public function getName(): string { return $this->name; } } /** * Now we have three classes that implement this attribute: */ #[WrittenBy('Joseph')] class Foo { } #[WrittenBy('Jane')] class Bar { } #[WrittenBy('Steve')] class Baz { } /** * Now we can loop over the classes and dynamically pull out the attribute, get an instance and call methods on it */ foreach ([Foo::class, Bar::class, Baz::class] as $class) { echo "\nClass " . $class . " was written by " . (new ReflectionClass($class))->getAttributes(WrittenBy::class)[0]->newInstance()->getName(); } ?> OUTPUT: Class Foo was written by Joseph Class Bar was written by Jane Class Baz was written by Steve ```