# 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
```