OOP Basics
I’ve been a self-taught “developer” for 8 years now, and I realized that I still don’t have a very good grasp of OOP. I find myself just floundering around OOP frameworks, trying different things until something works. Probably not the best approach. So I decided to take some time to learn the Basics of OOP. These are my notes…
Procedural vs OOP #
The idea behind OOP is to make code that is both easy to understand and maintain. Based on the principal that you should avoid repeating the same code, OOP splits the code up into chunks that can be re-used in multiple parts of an application.
Let’s say you want to create a website, that displays the contacts in a database. With a procedural approach, you might do something like this…
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Untitled Document</title>
<?php
//Define the contacts in the header
//Normally this data would be pulled from a database, but we'll just use an array for this example
$contacts = [
['name' => 'Jim Jones', 'phone' => '123-123-1234', 'email' => 'jim@email.com']
'name' => ''Sally Anderson', 'phone' => '555-555-5555', 'email' => 'sally@email.com'],
'name' => ' 'Bob Hunter', 'phone' => '456-456-4567', 'email' => 'bob@email.com']
];
?>
</head>
<body>
<?php
// And display the contacts in the body...
foreach ($contacts as $contact) {
echo '<li>'.$contact['name'].'</li>';
} ?>
</body>
</html>
Which works just fine. But what happens when we want to display the same contacts on a different page? We would need to create a new page, and copy/paste the database code. Which, of course, would also work fine. But that quickly begins to break down when we need to update our connection to the database. Or update the contacts being queried from the database. In those cases, we would need to update two bits of code.
If your coming from a procedural world, the first reaction may be to create a function, and include that function on the top of each page. For example…
/contacts.php
<?php
function get_contacts() {
return $contacts = [
['name' => 'Jim Jones', 'phone' => '123-123-1234', 'email' => 'jim@email.com']
'name' => ''Sally Anderson', 'phone' => '555-555-5555', 'email' => 'sally@email.com'],
'name' => ' 'Bob Hunter', 'phone' => '456-456-4567', 'email' => 'bob@email.com']
];
}
And then pull that into each page’s header…
/index.php
<?php
include 'contacts.php';
$contacts = get_contacts();
?>
Now that does solve the problem of having the same code in two different places. When we need to update the contacts, we can simply update the array in the contacts.php file. But sooner or later, were going to want to add other functions. Functions to add, update and delete contacts (for example). So for this, it would make sense to organize all of our functions together into a “class.”
Getting Started with Classes #
A class is simply a way of grouping functions (referred to as methods) into a logical unit, making the overall code much easier to read and maintain. You can also think of a class like a “blueprint,” that contains all variables (referred to as properties) that each contact has in common.
Let’s start by creating a class for our contacts, with all of the associated properties.
class Contact
{
public $name = 'Jim Jones';
public $phone = '123-123-1234';
public $email = 'jim@email.com';
}
In this example, “Public” just means that the property is available outside of the class. And we can access these properties by creating a new instance of the class (referred to as an object).
So let’s do that. Let’s create a new contact object, and display the contact’s name….
$contact = new Contact(); //creating a new instance of the contact object
var_dump($contact->name); //dump out the contact's name
Which will return…
string(9) "Jim Jones"
Awesome! But this doesn’t do us much good always returnin the same “Jim Jones” contact. We need a way to create new contacts, all with different names, phones and emails. To do that, let’s take a step backwards and remove Jim’s information from the class.
class Contact
{
public $name;
public $phone;
public $email;
}
And now we can create a new contact object, containing the appropriate information.
$contact = new Contact();
$contact->name = 'Jim Jones';
$contact->phone = '123-123-1234';
$contact->email = 'jim@email.com';
And when we var dump the contents, we see we have saved all of Jim’s information into our new contact object.
class Contact#1 (3) {
public $name =>
string(9) "Jim Jones"
public $phone =>
string(12) "123-123-1234"
public $email =>
string(13) "jim@email.com"
}
We can clean tis up quite a bit, by adding a create
method (remember - “method” is just a fancy word for function, when used within a class) to our Contact class. With a create method in place, we will be able to create new contacts with a single line of code.
$contact->create('Jim Jones','123-123-1234','jim@email.com');
Our create
class would look something like this…
class Contact
{
public $name;
public $phone;
public $email;
public function create($name, $phone, $email) {
$this->name = $name;
$this->phone = $phone;
$this->email = $email;
}
}
Notice how we are using the $this->
- which is simply referring to our $name, $phone, and $email properties which we defined as part of the class. Using $this->
instructs the code to look outside of the current method, to the parent class for the property.
So let’s pull all of this together and put it into action….
class Contact
{
public $name;
public $phone;
public $email;
public function create($name, $phone, $email) {
$this->name = $name;
$this->phone = $phone;
$this->email = $email;
//here we would save the contact to a database.
}
}
$contact = new Contact();
$contact->create('Jim Jones','123-123-1234','jim@email.com');
$contact2 = new Contact();
$contact2->create('Sally Anderson','555-555-5555','sally@email.com');
$contact3 = new Contact();
$contact3->create('Bob Hunter','5456-456-4567','bob@email.com');
var_dump($contact3);
Getters and Setters #
Getters and setters will give us a little more protection and security. To showcase this, let’s assume that we want to give each of our contacts a profile photo. And to do that, we create a new method named setProfilePhoto
, which accepts a $photo
property.
class Contact
{
public $name;
public $phone;
public $email;
public $photo;
public function create($name, $phone, $email) {
$this->name = $name;
$this->phone = $phone;
$this->email = $email;
//here we would normally save the contact to a database.
}
public function setProfilePhoto($photo) {
$this->photo = $photo;
}
}
And we can call this method to define the contact’s photo…
$contact3->setProfilePhoto('http://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50');
The problem is, that we can also define the contact’s photo with…
$contact3->setProfilePhoto('photo.jpg');
We don’t want this. For it to work, we need to ensure that our profile photo to always be a URL. This is where getters and setters come into play. We can use our “setter” to validate that the profile photo being added, is in fact a URL. To do this, we use PHP’s FILTER_VALIDATE_URL filter, and alert to user if it doesn’t pass.
public function setProfilePhoto($photo) {
if (!filter_var($photo, FILTER_VALIDATE_URL)) {
throw new Exception('Not a valid photo');
}
$this->photo = $photo;
}
}
Now, when we try to set a photo that does not contain a URL, we get an error:
Fatal error: Uncaught exception 'Exception' with message 'Not a valid photo'
So that’s setters. Getters will allow us to access specific properties of our contact. Let’s assume that whenever a photo was requested, we would need to wrap it in an HTML image tag. We can do that with a getter like so…
public function getProfilePhoto() {
return "<img src='$this->photo' />";
}
And when we call this method
var_dump($contact3->getProfilePhoto());
We get our properly formatted profile photo:
string(77) "<img src='http://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50' />"
Now this might not seem like a big deal. But as your project begins to expand. And the contact’s information and profile photos being to get spread throughout the app. Having a single location where you get and set these properties, will clean up your code significantly, and make it much easier to manage.
Encapsulation #
The idea behind encapsulation is to hide as much information and behavior as we can. Take, for example, our setProfilePhoto
method. We can very simply over-ride this behavior, by setting the $photo
property directly.
$contact->photo = 'photo.jpg';
But the entire point of having the setProfilePhoto
method, was to ensure that the photo was always to be set to a URL. So we need to protect from being able to define the photo property directly. To do that, we can use encapsulation.
The way we accomplish that, is by making the $photo
property only available to the setProfilePhoto
method. And the only thing we have to do to accomplish that, is to set it as a private
property like so…
private $photo;
Now, if we try to set the photo directly with $contact->photo = 'photo.jpg';
, we will see an error:
Fatal error: Cannot access private property Contact::$photo
Inheritance #
Just as you inherit attributes and behavior from your parents, the same can be true of our code. This can be done by extending our class to sub classes. So if we added an Employee
class under our existing Contact
class, it would look something like so…
class Employee extends Contact
{
}
$employee = new Employee();
$employee->create('Tom Anderson','192-124-1353','tom@employee.com');
var_dump($employee);
Notice by extending the Contact
class, we can utilize all of it’s methods.
Where this can be useful, is if we need to override specific functions of the Contact class. Say, for example, we wanted all photos of employees to include a border. To do that, we can update our getProfilePhoto
method to account for that.
public function getProfilePhoto() {
return "<img src='$this->photo' style="border:1px solid blue" />";
}
But if we try to do that…
$employee->setProfilePhoto('http://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50');
var_dump($employee->getProfilePhoto());
We’ll get an error…
Notice: Undefined property: Employee::$photo
This is because our $photo
property is set to private
. In order to make this property available to sub-classes, we need to set it to protected
.
protected $photo.
Once we do that, we’ll see our updated employee profile photo with a border.
string(107) "<img src='http://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50' style='border:1px solid blue' />"
Autoloading #
In our examples so far, we have two different classes - one for Contact
and Employee
. We’ve just been including both into a single file to test. But in the real world, these should be placed int their own files.
Which begs the question… how do we reference these files? If your coming from a procedural world, then your first instinct would be to require the classes file with…
require 'Contact.php';
require 'Employee.php';
…Which would world. However, once we end up with tens or hundreds of different files, requiring them in this way really gets to really be a pain. And others, more smarter than me, have figured out how to auto-load all of these files, so we don’t have to do that. And the tool of choice is Composer.
Let’s start by breaking up our classes into their own files. We’ll place all of our files into an “App” directory. And our classes in an “App/src” directory. I also added namespace App
just above the class names (which will be used later).
App/src/Contact.php
<?php
namespace App;
class Contact
{
public $name;
public $phone;
public $email;
protected $photo;
public function create($name, $phone, $email)
{
$this->name = $name;
$this->phone = $phone;
$this->email = $email;
//here we would save the contact to a database.
}
public function setProfilePhoto($photo)
{
$this->photo = $photo;
}
public function getProfilePhoto() {
return "<img src='$this->photo' />";
}
}
App/src/Employee.php
namespace App;
<?php
class Employee extends Contact
{
public function getProfilePhoto()
{
return "<img src='$this->photo' style="border:1px solid blue" />";
}
}
And then we’ll have our entry point file, that will attempt to instantiate these classes.
App/index.php
<?php
$contact = new Contact();
$contact->create('Jim Jones','123-123-1234','jim@email.com');
var_dump($contact);
However, this will fail, because this file does not know where the Contact
class is.
The first step to autoloading our Contact and Employee files, is to get composer installed. Once installed, we’ll go back to our project, and create a “composer.json” file, with the following autoload settings.
/composer.json
{
"autoload": {
"psr-4": {
"App\\": "src"
}
}
}
With this, we’re telling composer to use the psr-4
convention to auto load our files. Then, we are telling psr-4, that our application’s name is App
, and that the files we want to autoload are located in the src
directory.
If we run composer install
, it creates a file named “/Vendor/autoload.php.” This is the single file that we want to include in all of our application projects. This single file will be responsible for autoloading all of our project files in one swoop. So we update our index.php file by requiring that file, and also declaring the namespace with App\Contact
.
App/index.php
<?php
require 'vendor/autoload.php';
$contact = new Contact();
$contact->create('Jim Jones','123-123-1234','jim@email.com');
var_dump($contact);
Finally, we run composer dump-autoload
(which we will want to do every time we change the autoload settings in composer.json), and composer adds our auto loaded files to…
/vendor/composer/autoload_psr4.php
…
return array(
‘App\’ => array($baseDir . ‘/src’),
);
And that’s it. If we run the index.php file, it functions just as before. Even better, any classes that we add into the “src” directory in the future, will automatically be loaded and available to our app.
Collections #
http://www.sitepoint.com/collection-classes-in-php/
It might be helpful if we could group all of our contacts together, to reference them all through a single object, or “Collection.” To do that, let’s assume that all of our contacts belong to a user (presumably, the user of our app).
Our collection class should expose methods that allow us to add, retrieve, and delete items. Let’s start by creating User
class, with an addContact
and getContact
methods.
class User {
protected $contacts = [];
public function addContact(Contact $contact)
{
$this->contacts[] = $contact;
}
public function getContacts()
{
return $this->contacts;
}
}
First, I’m created a public $contacts
array. Then, the addContact
method is used to add the contact to the user. Notice by adding Contact $contact
to the hire method, I’m requiring that the passed message, be an instance of Contact. This is called type hinting, and helps protect the method from receiving unwanted information.
$user = new User();
$bob = new Contact();
$bob->create('Bob Hunter','5456-456-4567','bob@email.com');
$sally = new Contact();
$sally->create('Sally Anderson','555-555-5555','sally@email.com');
$user->addContact($bob);
$user->addContact($sally);
var_dump($user->getContacts());
This will then allow us to loop through the company, to get each contact.
foreach($user->getContacts() as $id => $contact) {
echo $id.": ".$contact->name."\r\n";
}
Giving us:
0: Bob Hunter
1: Sally Anderson
It would also be helpful if we could reference a single contact. Say that we only wanted to pull Sally’s information. If we create an getContact
method, we could reference Sally by her ID of 1
.
public function getContact($key)
{
return $this->contacts[$key];
}
Which would look like…
$contact = $user->getContact(1);
… giving us Sally’s info.
Finally, it would be helpful to delete contacts as well. So let’s create a delete method.
public function deleteContact($key)
{
unset($this->contacts[$key]);
}
Which we could then use…
$contact = $user->deleteContact(1);
Statics #
Declaring class properties or methods as static makes them accessible without needing an instantiation of the class.
When methods do nothing more than accepting some input, and returning some output, Statics may be a good choice. Another way to put it - if you have a global functions file, these functions may make a good case to be static. However, if your method depends on other methods, using static is typically not a good choice.
Essentially, making is a global function, which can be called anywhere in an application.
For an example, we will break away from using our Contact class, since all of our contact’s properties are specific to the contact (these values are not being shared), so it would not make sense to make them static.
However, what if we wanted to calculate how complete a contacts profile was? Now that function would be universal across all contacts. This may make for a good static function. Let’s see how that would look…
class Score {
public static function getContactScore(Contact $contact) {
$score = 0;
if($contact->name != '') $score++;
if($contact->phone != '') $score++;
if($contact->email != '') $score++;
if($contact->getProfilePhoto() != '') $score++;
return $score/4;
}
}
This method is giving a point for every property that has a value in the Contact object. Then taking that value, and diving by 4 (the total number of properties avaiable), to get us a percent of the profile completness.
So if we were to create a new contact, but never set the profile photo:
$contact = new Employee();
$contact->create('Tom Anderson','192-124-1353','tom@employee.com');
We would be able to use our new static function calculate the score like using:
echo Score::getContactScore($contact)
Which gives us .75
(since 3 of the 4 items are completed).
Notice that to instantiate our Score method, all we need to do use use Score::getContactScore
. No longer are we accessing an instance of the object. Instead, we can use this to calculate contact’s score from anywhere. It has essentially become a global function.
You can also make properties static. And you would only want to do this, when the properties will be shared.
Learn More… #
- https://laracasts.com/series/object-oriented-bootcamp-in-php/episodes/1
- http://www.killerphp.com/tutorials/php-objects-page-1/
- http://stackoverflow.com/questions/1530868/simple-explanation-php-oop-vs-procedural
- http://stackoverflow.com/questions/716412/why-use-php-oop-over-basic-functions-and-when
- http://www.sitepoint.com/collection-classes-in-php/