When developers are introduced to Object Oriented Programming, one of the first things that happens is that they are taught that nouns turn into objects, verbs into methods, and Dog is a subclass of Animal. OOP is more than just turning things into classes and objects and showing that both Boats and Cars have motors, and that Dogs and Cats both speak(). Let's look at OOP in real world settings and go beyond cars and dogs, and see how to use Object Oriented Programming properly in PHP. Traits, Composition, Inheritance, none of it is off limits!
1. OOP is More Than Cars
and Dogs
Chris Tankersley
Midwest PHP 2017
Midwest PHP 2017 1
2. Quick Vocabulary Lesson
• Class – Definition of code
• Object – Instantiation of a Class
• Member/Property – Variable belonging to a class
• Method – Function belonging to a class
There will be more as we go along
Midwest PHP 2017 2
3. Midwest PHP 2017 3
Class
class Employee {
protected $name; // This is a member
protected $number;
// This is a Method
public function setData($data) {
$this->name = $data['name'];
$this->number = $data['number'];
}
public function viewData() {
echo <<<ENDTEXT
Name: {$this->name}
Number: {$this->number}
ENDTEXT;
}
}
6. Let’s count the reasons
• Because we’re told to, procedural programming leads to spaghetti
code
• We deal with objects every day, so it shouldn’t be too hard
• We want to allow for code re-use
• We want to group like code together
• We want to easily extend our code
• We want to be able to easily test our code
Midwest PHP 2017 6
16. What we’re all taught
• Classes are “things” in the real world
• We should construct class members based on Attributes
• Number of wheels
• Sound it makes
• We should construct class methods based on “Actions”
• Running
• Speaking
• Jumping
Midwest PHP 2017 16
17. New Vocabulary
• Parent Class – Class that is extended
• Child Class – Class that is extending another class
In PHP, a class can be both a Child and a Parent at the same time
Midwest PHP 2017 17
20. The Employee Class
Midwest PHP 2017 20
abstract class Employee {
protected $name; // Employee Name
protected $number; // Employee Number
public function setData($data) {
$this->name = $data['name'];
$this->number = $data['number'];
}
public function viewData() {
echo <<<ENDTEXT
Name: {$this->name}
Number: {$this->number}
ENDTEXT;
}
}
21. The Manager Class
Midwest PHP 2017 21
class Manager extends Employee {
protected $title; // Employee Title
protected $dues; // Golf Dues
public function setData($data) {
parent::setData($data);
$this->title = $data['title'];
$this->dues = $data['dues'];
}
public function viewData() {
parent::viewData();
echo <<<ENDTEXT
Title: {$this->title}
Golf Dues: {$this->dues}
ENDTEXT;
}
}
22. The Scientist Class
Midwest PHP 2017 22
class Scientist extends Employee {
protected $pubs; // Number of Publications
public function setData($data) {
parent::setData($data);
$this->pubs = $data['pubs'];
}
public function viewData() {
parent::viewData();
echo <<<ENDTEXT
Publications: {$this->pubs}
ENDTEXT;
}
}
24. What does this teach us?
• Inheritance
• Makes it easier to group code together and share it amongst classes
• Allows us to extend code as needed
• PHP allows Single inheritance
Midwest PHP 2017 24
25. We use it all the time
namespace ApplicationController;
use ZendMvcControllerAbstractActionController;
use ZendViewModelViewModel;
Class IndexController extends AbstractActionController {
public function indexAction() {
/** @var VendorVendorService $vendor */
$vendor = $this->serviceLocator->get('VendorVendorService');
$view = new ViewModel();
return $view;
}
} Midwest PHP 2017 25
26. Why it Works (Most of the time, Kinda)
• Allows us to extend things we didn’t necessarily create
• Encourages code re-use
• Allows developers to abstract away things
Midwest PHP 2017 26
27. How to use it
• Understand the difference between Public, Protected, and Private
• Public – Anyone can use this, even children
• Protected – Anything internal can use this, even children
• Private – This is mine, hands off
• Abstract vs Concrete Classes
• Abstract classes cannot be instantiated directly, they must be extended
Midwest PHP 2017 27
28. The Employee Class
Midwest PHP 2017 28
abstract class Employee {
protected $name; // Employee Name
protected $number; // Employee Number
public function setData($data) {
$this->name = $data['name'];
$this->number = $data['number'];
}
public function viewData() {
echo <<<ENDTEXT
Name: {$this->name}
Number: {$this->number}
ENDTEXT;
}
}
29. The Manager Class
Midwest PHP 2017 29
class Manager extends Employee {
protected $title; // Employee Title
protected $dues; // Golf Dues
public function setData($data) {
parent::setData($data);
$this->title = $data['title'];
$this->dues = $data['dues'];
}
public function viewData() {
parent::viewData();
echo <<<ENDTEXT
Title: {$this->title}
Golf Dues: {$this->dues}
ENDTEXT;
}
}
30. An Example
// Fatal error: Cannot instantiate abstract class Employee
$employee = new Employee();
// We can do this though!
$manager = new Manager();
// Fatal error: Cannot access protected property Manager::$name
$manager->name = 'Bob McManager’;
// setData is public, so we can use that
$manager->setData(['name' => 'Bob McManager’,'number' => 1]);
// We can also view the data, since it's public
$manager->viewData();
Midwest PHP 2017 30
31. Why can Inheritance Be Bad
• PHP only allows Single Inheritance on an Class
• You can have a series of Inheritance though, for example CEO extends
Manager, Manager extends Employee
• Long inheritance chains can be a code smell
• Private members and methods cannot be used by Child classes
• Single Inheritance can make it hard to ‘bolt on’ new functionality
between disparate classes
Midwest PHP 2017 31
33. The General Idea
• Classes contain other classes to do work and extend that way, instead
of through Inheritance
• Interfaces define “contracts” that objects will adhere to
• Your classes implement interfaces to add needed functionality
Midwest PHP 2017 33
34. Interfaces
interface EmployeeInterface {
protected $name;
protected $number;
public function getName();
public function setName($name);
public function getNumber();
public function setNumber($number);
}
interface ManagerInterface {
protected $golfHandicap;
public function getHandicap();
public function setHandicap($handicap);
}
Midwest PHP 2017 34
35. Interface Implementation
class Employee implements EmployeeInterface {
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
}
class Manager implements EmployeeInterface, ManagerInterface {
// defines the employee getters/setters as well
public function getHandicap() {
return $this->handicap;
}
public function setHandicap($handicap) {
$this->handicap = $handicap;
}
}
Midwest PHP 2017 35
36. This is Good and Bad
• “HAS-A” is tends to be more flexible than “IS-A”
• Somewhat easier to understand, since there isn’t a hierarchy you
have to backtrack
• Each class must provide their own Implementation, so can lead to
code duplication
Midwest PHP 2017 36
37. Traits
• Allows small blocks of code to be defined that can be used by many
classes
• Useful when abstract classes/inheritance would be cumbersome
• My Posts and Pages classes shouldn’t need to extend a Slugger class just to
generate slugs.
Midwest PHP 2017 37
38. Avoid Code-Duplication with Traits
trait EmployeeTrait {
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
}
class Employee implements EmployeeInterface {
use EmployeeTrait;
}
class Manager implements EmployeeInterface, ManagerInterface {
use EmployeeTrait;
use ManagerTrait;
}
Midwest PHP 2017 38
41. What is Coupling?
• Coupling is how dependent your code is on another class
• The more classes you are coupled to, the more changes affect your
class
Midwest PHP 2017 41
42. namespace ApplicationController;
use ZendMvcControllerAbstractActionController;
use ZendViewModelViewModel;
class MapController extends AbstractActionController
{
public function indexAction()
{
// Position is an array with a Latitude and Longitude object
$position = $this->getServiceLocator()->get('MapService’)
->getLatLong('123 Main Street', 'Defiance', 'OH');
echo $position->latitude->getPoint();
}
}
Midwest PHP 2017 42
45. What is Dependency Injection?
• Injecting dependencies into classes, instead of having the class create
it
• Allows for much easier testing
• Allows for a much easier time swapping out code
• Reduces the coupling that happens between classes
Midwest PHP 2017 45
46. Method Injection
class MapService {
public function getLatLong(GoogleMaps $map, $street, $city, $state) {
return $map->getLatLong($street . ' ' . $city . ' ' . $state);
}
public function getAddress(GoogleMaps $map, $lat, $long) {
return $map->getAddress($lat, $long);
}
}
Midwest PHP 2017 46
47. Constructor Injection
class MapService {
protected $map;
public function __construct(GoogleMaps $map) {
$this->map = $map;
}
public function getLatLong($street, $city, $state) {
return $this
->map
->getLatLong($street . ' ' . $city . ' ' . $state);
}
}
Midwest PHP 2017 47
48. Setter Injection
class MapService {
protected $map;
public function setMap(GoogleMaps $map) {
$this->map = $map;
}
public function getMap() {
return $this->map;
}
public function getLatLong($street, $city, $state) {
return $this->getMap()->getLatLong($street . ' ' . $city . ' ' . $state);
}
}
Midwest PHP 2017 48
50. Single Responsibility Principle
• Every class should have a single responsibility, and that responsibility
should be encapsulated in that class
Midwest PHP 2017 50
51. What is a Responsibility?
• Responsibility is a “Reason To Change” – Robert C. Martin
• By having more than one “Reason to Change”, code is harder to
maintain and becomes coupled
• Since the class is coupled to multiple responsibilities, it becomes
harder for the class to adapt to any one responsibility
Midwest PHP 2017 51
52. An Example
/**
* Create a new invoice instance.
*
* @param LaravelCashierContractsBillable $billable
* @param object
* @return void
*/
public function __construct(BillableContract $billable, $invoice)
{
$this->billable = $billable;
$this->files = new Filesystem;
$this->stripeInvoice = $invoice;
}
/**
* Create an invoice download response.
*
* @param array $data
* @param string $storagePath
* @return SymfonyComponentHttpFoundationResponse
*/
public function download(array $data, $storagePath = null)
{
$filename = $this->getDownloadFilename($data['product']);
$document = $this->writeInvoice($data, $storagePath);
$response = new Response($this->files->get($document), 200, [
'Content-Description' => 'File Transfer',
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
'Content-Transfer-Encoding' => 'binary',
'Content-Type' => 'application/pdf',
]);
$this->files->delete($document);
return $response;
}
Midwest PHP 2017 52
https://github.com/laravel/cashier/blob/5.0/src/Laravel/Cashier/Invoice.php
53. Why is this Bad?
• This single class has the following responsibilities:
• Generating totals for the invoice (including discounts/coupons)
• Generating an HTML View of the invoice (Invoice::view())
• Generating a PDF download of the invoice(Invoice::download())
• This is coupled to a shell script as well
• Two different displays handled by the class. Adding more means more
responsibility
• Coupled to a specific HTML template, the filesystem, the Laravel
Views system, and PhantomJS via the shell script
Midwest PHP 2017 53
54. How to Improve
• Change responsibility to just building the invoice data
• Move the ‘output’ stuff to other classes
Midwest PHP 2017 54
56. Test Driven Development
• Write your tests
• Watch them fail
• Write code to make the tests pass
• Feel better! (and refactor)
Midwest PHP 2017 56
57. This is not a testing talk
• Using Interfaces makes it easier to mock objects
• Reducing coupling and following Demeter’s Law makes you have to
mock less objects
• Dependency Injection means you only mock what you need for that
test
• Single Responsibility means your test should be short and sweet
• Easier testing leads to more testing
Midwest PHP 2017 57
59. We can make a dog with wheels!
• Abstract class for Animal
• Class for Dog that extends Animal
• Trait for Wheels
• With the correct methodology, we could even unit test this
As in the real world, we can now represent a crippled dog
Midwest PHP 2017 59