PHP OOP basics
In this section, we are going to cover a very brief overview of the basic OOP features of PHP 8. I am not aiming to be comprehensive here; if this is new to you, then I suggest you visit the official docs, which do a great job of explaining these features in detail:
PHP: Classes and Objects - Manual
https://www.php.net/manual/en/language.oop5.php
We will be covering further details throughout the book; however, the following should be enough to whet your appetite and let you know whether your knowledge is up to date. We're going to look briefly at classes, interfaces, and namespaces. This should all be quite familiar to you already and if it is not, then a little extra revision of these topics would be useful so that you can get the most out of later chapters.
Classes
The basic building block of PHP OOP is the class. As briefly mentioned earlier in this chapter, classes are the things that get instantiated in the "objects" that OOP talks about.
A simple class looks like this:
src/Part1/Chapter1/Simple/SimpleClass.php
Repo: https://git.io/JRw7C
<?php declare(strict_types=1); namespace Book\Part1\Chapter1\Simple; class SimpleClass { public function __construct(public string $name = 'Simon') { } }
To create an instance of the class, we use the new
keyword, which then gives us an "instance" that we can assign to a variable and generally interact with.
Then, a new instance is created; PHP will check for a method called __construct
and if it exists, it will be called. The parameters passed into this method are those that are passed in when calling the class:
src/Part1/Chapter1/simple.php
Repo: https://git.io/JRw7W
<?php declare(strict_types=1); namespace Book\Part1\Chapter1; use Book\Part1\Chapter1\Simple\SimpleClass; require __DIR__ . '/../../../vendor/autoload.php'; $instance = new SimpleClass(); echo "\n" . $instance->name; // Simon $instance2 = new SimpleClass('Sally'); echo "\n" . $instance2->name; //Sally
Output:
Inside the class, we have a variable called $this
, which is available to every method inside the class and refers to the current instance. It provides access to all the methods and properties of that instance:
src/Part1/Chapter1/Simple/SimpleWithGetter.php
Repo: https://git.io/JRw7l
<?php declare(strict_types=1); namespace Book\Part1\Chapter1\Simple; class SimpleWithGetter { public function __construct(private string $name = 'Simon') { } public function getName(): string { return $this->name; } }
If the parameters are defined in the constructor with a visibility keyword (private
, protected
, or public
), then the parameter will automatically be defined as a property of the class with that visibility level.
If a visibility level is not specified, then in order to assign the parameter value to the class as a property, we have to do something like this:
src/Part1/Chapter1/Simple/SimpleManualAssignment.php
Repo: https://git.io/JRw78
<?php declare(strict_types=1); namespace Book\Part1\Chapter1\Simple; class SimpleManualAssignment { private string $name; public function __construct(string $name = 'Simon') { // take the constructor param and manually assign to class property $this->name = $name; } }
There are three ways that properties can be set in the class:
- In the constructor parameters
- Defining the property in the class body
- Dynamically assigning the property without a definition (don't do this)
Have a look at the following snippet, which demonstrates these three options:
src/Part1/Chapter1/Simple/SimplePropertyAssignment.php
Repo: https://git.io/JRw74
<?php declare(strict_types=1); namespace Book\Part1\Chapter1\Simple; class SimplePropertyAssignment { private string $defined = 'defaultValue'; public function __construct( private string $constructorParam = 'constructorValue' ) { // this is a bad idea, dynamicProperty is untyped and public $this->dynamicProperty = 'dynamicallyAdded'; } }
When a property is declared dynamically, it can only ever be public, and it cannot be typed. This means that we lose a huge amount of safety and open the door to a whole range of bugs:
src/Part1/Chapter1/simple_properties.php
Repo: https://git.io/JRw7B
<?php declare(strict_types=1); namespace Book\Part1\Chapter1; use Book\Part1\Chapter1\Simple\SimplePropertyAssignment; require __DIR__ . '/../../../vendor/autoload.php'; $instance = new SimplePropertyAssignment(); // dynamic property is public echo "\n" . $instance->dynamicProperty; // dynamic property is untyped and can be anything $instance->dynamicProperty = 123; echo "\n" . $instance->dynamicProperty;
Output:
As you hopefully now realise, public properties are generally not something you want, apart from in very particular circumstances. If you ever see dynamically assigned properties, the general expectation is that it is a mistake, and for this reason, most integrated development environments (IDEs) will highlight it as a warning.
Interfaces
Interfaces are pieces of code that never contain any functionality, but what they do define are the… well… interface that classes must provide when they implement the specific interface.
For example:
src/Part1/Chapter1/interface.php
Repo: https://git.io/JRw7R
<?php declare(strict_types=1); namespace Book\Part1\Chapter1; interface GetsSomethingInterface { /** * This interface defines one method. * It must be called "getSomething" and it must return a string */ public function getSomething(): string; } class GetsSomethingClass implements GetsSomethingInterface { public function getSomething(): string { return 'something'; } } echo "\n" . (new GetsSomethingClass())->getSomething();
Output:
By implementing an interface, the class tells the world that it conforms to a set of strict rules. It must implement the methods in the interface as they are defined. That allows other code to not necessarily care exactly what class it is and confidently call methods as we know they will be there, and we know what types they will return.
Read more about interfaces in the official docs:
PHP: Object Interfaces - Manual
https://www.php.net/manual/en/language.oop5.interfaces.php
Namespaces
This is not strictly part of OOP but is highly relevant, so I'm including it here. Namespaces are designed to make naming things a lot, lot simpler. Before we had namespaces, our code needed to worry about the fact that there might be a situation where some other piece of code tries to use the same name as the one you are trying to use for your class, function, or another item. This led to things having very long names, such as Mage_Catalog_Product_CompareController
– all in an effort to ensure that there is no risk of a name clash, because a name clash is a fatal error.
The advent of namespaces meant that we could now define a clean slate for naming things. We need to define a unique namespace and then within that namespace, we can give things lovely, short, meaningful names with no fear of a name clash:
<?php namespace My\Library; class Helper {} function write(string $value){}
There is nothing in PHP itself that defines which namespace any particular file should use; however, modern PHP developers have almost completely adopted something called PSR-4:
PSR-4: Autoloader - PHP-FIG
https://www.php-fig.org/psr/psr-4/
As you can see if you follow the link, PSR-4 lays out rules around exactly how to use namespaces and how they should correspond to the file/folder structure of your project.
What is PSR?
If you haven't heard of PSR before, it might be worth reading into it a bit. PSR stands for PHP Standards Recommendations. It is something that was put together and promoted by a group of PHP developers called the PHP Framework Interop Group – PHP-FIG for short.
What they have done is tried to put together coding standards and basic interfaces to define general components. The goal is that rather than each framework being its own weird and wonderful isolated pond, there are standard approaches to standard problems. The huge advantage of this is that tooling and libraries are less coupled to frameworks and we can have nice things that were primarily built for one framework or application being useable by the rest of the PHP ecosystem. DI containers are actually a great example of this.
This policy defines a folder structure and naming convention that corresponds a class's namespace. Each \
in the namespace name corresponds to a subdirectory in the folder structure, with a defined prefix applied within a defined base directory.
This example is copied from the PSR-4 page:
If you have been paying close attention to the code snippets, you may have spotted that the example code for this book also conforms to PSR-4. The root namespace prefix is Book\
and this corresponds to the src
directory in the code
repo. For code that is designed to be shared publicly, this root namespace is like a unique username or brand and is generally standardized for all packages released by the same person or company. We will explore these kinds of packages later in the book.
For any files in the base directory, the namespace is simply the namespace prefix. If the file is in a subfolder, then the namespace is extended to match the subfolder name.
If you look back through the code snippets in this chapter, you can see that as we are in Part1
and Chapter1
, the code snippets are being stored in src/Part1/Chapter1
, and for each code snippet that is in that folder, the namespace is defined as follows:
namespace Book\Part1\Chapter1;
Any code snippets in a subfolder have the namespace extended to match; for example, the file /src/Part1/Chapter1/Simple/SimplePropertyAssignment.php
has the namespace defined as follows:
namespace Book\Part1\Chapter1\Simple;
PSR-4 is a very sane and sensible system to follow, and I highly encourage you to do so.
Read more about namespaces in the official docs:
PHP: Namespaces - Manual