Object-Oriented PHP, Part I
Object-Oriented PHP, Part I
Ellie Strejlau | Senior Developer
May 29, 2019
A few months ago, I wrote about object-oriented programming (OOP) as a concept, and I used ES6 to convey those practices. (If you do not know much about object-oriented programming as a whole, I recommend that you go read that post first!) Because OOP is a design pattern and not a language-specific feature, the idea arose to explore OOP in other languages. However, aside from syntax differences, other languages also have different OOP features that are worth explaining. A special request came in for an explanation of OOPHP and, fortunately, I know a thing or two about PHP, so let's do this!
Before Beginning...
This post assumes that you know:
- what variables and functions/methods are,
- how to read basic PHP syntax, and
- what a fatal error is.
The Example
Let's go with a different scenario from last time. How about we try to create a basic structure for a blog? We'll call our first class BlogEntry
.
class BlogEntry {
public $title;
private $created_time;
public function __construct() {
$this->created_time = new DateTime();
}
}
$todays_entry = new BlogEntry();
$todays_entry->title = 'Back on my Ish: Object-Oriented PHP, Part 1';
If you recall from my last entry, you should think of a class as a blueprint from which a script can make actual objects. This is a pretty basic PHP class, but there's a lot to unpack in this relatively small example alone.
Properties
Properties are variables that exist inside of classes and are usually descriptive of what an object should contain. You can define as many properties as you want inside of a class. There are 2 properties in this example: $title
and $created
. $title
is a property that holds a string value; in this case, it's the title of the entry. $created
holds a new DateTime
object for the time the entry was created. (DateTime
is a built-in PHP class that has some really useful features and ample documentation.)
Methods
A method is a function that's defined inside of a class that usually performs actions on the object created from that class.
class BlogEntry {
public $title;
private $created_time;
public function __construct() {
$this->created_time = new DateTime();
}
public function printEntryPreview() {
print "\"$this->title\" was created on " . $this->created_time->format('F j, Y');
}
}
$todays_entry = new BlogEntry();
$todays_entry->title = 'Back on my Ish: Object-Oriented PHP, Part 1';
$todays_entry->printEntryPreview();
The method I created for this example is the printEntryPreview
method. When that method is called on the last line in this script, it results in the following output: "Back on my Ish: Object-Oriented PHP, Part 1" was created on April 10, 2019
. You've probably noticed that there's another method called __construct
. All you need to know right now is that's what PHP calls a "magic method" and it runs when an object is created. We'll go into magic methods in more detail in part 2.
Access and $this
The syntax to access a method or property in PHP is the right arrow notation (->
) as you can see in the __construct
method above. $this
is a purely contextual variable, like in my ES6 example. We're using $this
to tell PHP that we want the object to access it's own properties or methods. In this example, $this
refers to $todays_entry
. If you were to create another entry, called $tomorrows_entry
, $this
would refer to $tomorrows_entry
.
Property and Method Visibility
Visibility refers to the level of access the rest of your code has to the properties and methods that exist inside of a class. There are 3 visibilities in PHP: Private, Protected, and Public.
Private
Private properties and methods can only be accessed via methods or properties in that same class and they are not inherited by child classes. The BlogEntry
example has a private $created_time
property, so if you attempt to append $todays_entry->created_time = new DateTime()
to the end of the above example, you will be greeted by a fatal error.
Protected
Protected properties and methods are those that can be accessed by the class itself and are inherited by child classes. However, outside of both classes, it acts like a private property and will throw an error if you try to modify it directly. You'll see this in action in our example shortly.
Public
Public properties and methods can be accessed from everywhere and are inherited by child classes. These methods and properties can be accessed by anything in the rest of your code. (Using the public keyword on properties means that you can use the ->
notation for accessing them directly!)
In the BlogEntry
example above, you'll see that I'm modifying the title directly on the second to last line. It doesn't throw an error because it's a public property.
Property-Specific Getters and Setters
One OOP pattern often utilizes public methods for getters and setters, which are methods that exist purely for the purpose of setting and returning values for (usually private) properties. Let's use the entry's publication date/time as an example here.
class BlogEntry {
public $title;
private $created_time;
private $published_time;
function __construct() {
$this->created_time = new DateTime();
}
public function getCreatedTime() {
return $this->created_time;
}
public function setPublishedTime(DateTime $published) {
$this->published_time = $published;
}
public function getPublishedTime() {
return $this->published_time;
}
}
$todays_entry = new BlogEntry();
$todays_entry->title = 'Back on my Ish: Object-Oriented PHP Part 1';
$todays_entry->setPublishedTime(new DateTime('April 30, 2019 12:00 PM'));
In this example, we're using methods to set and get the publication date for the entry, since that might not be set on construct and that property should probably be mutable. The getter, getPublishedTime
, will return the publish date DateTime
object when called. The setter, setPublishedTime
, sets the value of the private property because only methods inside of this class can do that.
There are magic methods that you can use here too, but using getters and setters specific to each property can be used to make sure you're getting the correct data type. For instance, if you try to send anything other than a DateTime
object into the setPublishedTime
function, the script will throw a fatal error. (We'll explore this more in part 2!)
Inheritance
Inheritance is the concept that some classes have child classes and those child classes inherit all of the protected and public properties and methods. This is useful for creating classes that use the same functionality but might need some new or slightly different functionality in child classes. Let's try this with our BlogEntry
example by creating different types of entries.
class BlogEntry {
public $title;
protected $created_time;
protected $published_time;
protected $content;
function __construct() {
$this->created_time = new DateTime();
}
public function getCreatedTime() {
return $this->created_time;
}
public function setPublishedTime(DateTime $published) {
$this->published_time = $published;
}
public function getPublishedTime() {
return $this->published_time;
}
public function setContent($content) {
$this->content = $content;
}
public function getContent($content) {
return $this->content;
}
}
class ReviewEntry extends BlogEntry {
private $rating;
public function setRating(int $rating) {
if ($rating < 1 || $rating > 5) {
throw new Error('Rating value is invalid');
return;
}
$this->rating = $rating;
}
public function getRating() {
return str_repeat('🌟', $this->rating);
}
}
$todays_entry = new BlogEntry();
$todays_entry->title = 'Back on my Ish: Object-Oriented PHP Part 1';
$todays_entry->setPublishedTime(new DateTime('April 30, 2019 12:00 PM'));
$tomorrows_entry = new ReviewEntry();
$tomorrows_entry->title = 'Avengers: Endgame (Spoiler Warning!)';
$tomorrows_entry->setPublishedTime(new DateTime('May 1, 2019 12:00 PM'));
$tomorrows_entry->setRating(5);
There's now a new class called ReviewEntry
that has a new property called $rating
which is restricted to integer values between 1 and 5. (Entering a number outside of that range results in a fatal error: Rating value is invalid
.) I also changed the visibility of the properties on the base BlogEntry
class to protected
so that its inherited classes can use them.
Abstraction and Final Classes
PHP and a few other languages also have the concept of abstract and final classes. An abstract class is a class that can never be used to directly create a new object and is often used as the basis for other classes in order to share functionality between them all. Additionally, properties and methods defined in abstract classes should be either protected or public so that they're inherited by child classes. (There is one other way to share functionality between classes—called Traits—that we'll go over in part 2.) Conversely, a final class is one that isn't allowed to have any child classes. Let's bring up our example one more time for this case.
abstract class BlogEntry {
protected $created_time;
protected $published_time;
protected $content;
function __construct() {
$this->created_time = new DateTime();
}
public function getCreatedTime() {
return $this->created_time;
}
public function setPublishedTime(DateTime $published) {
$this->published_time = $published;
}
public function getPublishedTime() {
return $this->published_time;
}
public function setContent($content) {
$this->content = $content;
}
public function getContent($content) {
return $this->content;
}
}
final class StandardEntry extends BlogEntry {
public $title;
}
final class StatusEntry extends BlogEntry {
public $mood;
}
final class ReviewEntry extends BlogEntry {
public $title;
private $rating;
public function setRating(int $rating) {
if ($rating < 1 || $rating > 5) {
throw new Error('Rating value is invalid');
return;
}
$this->rating = $rating;
}
public function getRating() {
return str_repeat('🌟', $this->rating);
}
}
$todays_entry = new StandardEntry();
$todays_entry->title = 'Back on my Ish: Object-Oriented PHP Part 1';
$todays_entry->setPublishedTime(new DateTime('April 30, 2019 12:00 PM'));
$todays_status = new StatusEntry();
$todays_status->mood = 'happy';
$todays_status->setPublishedTime(new DateTime('April 30, 2019 12:00 PM'));
$todays_status->setContent('I\'m going to see Avengers tonight!');
$tomorrows_entry = new ReviewEntry();
$tomorrows_entry->title = 'Avengers: Endgame';
$tomorrows_entry->setPublishedTime(new DateTime('May 1, 2019 12:00 PM'));
$tomorrows_entry->setRating(5);
$tomorrows_entry->setContent('Spoiler Warning: This movie was great.');
In the above example, the BlogEntry
class is abstract and all of the other classes are final. If you attempt to create an object from the BlogEntry
class or attempt to extend one of the final classes, you'll receive a fatal error.
Abstract Methods
Abstract methods are used in abstract classes to create a requirement that the child class must define that method. Here's a small snippet as an example:
abstract class BlogEntry {
abstract protected function printEntry();
}
Because the printEntry
method in this example is abstract, it must be defined in all child classes. There's another way to make sure that classes are defining certain methods—using interfaces—which we'll review in part 2.
The Full Example
I've created a public repository on Github with my various object-oriented PHP sample code. Here's the full example from this post with some additional printing methods, so you can see some output when it's run.
What do you mean that wasn't everything?!
I know this was a long entry, but there's still a lot that I didn't get to cover. I hope you come back around for the next installment where I will cover even more interesting OOP concepts in PHP.
One note about my last entry
You may recall that I didn't actually review property and method visibility in my previous post, because as far as I've seen they don't exist in JS/ES6 for one reason or another. (However, TypeScript has built-in support for all three visibilities as well as abstract classes. If you like ES6 and strongly-typed languages, give it a try!)