Coming to Terms with OOP In Drupal - php[world] 2016
-
Upload
chris-tankersley -
Category
Technology
-
view
143 -
download
0
Transcript of Coming to Terms with OOP In Drupal - php[world] 2016
php[world] 2016 1
Coming to Terms with OOP in DrupalChris Tankersleyphp[world] 2016
php[world] 2016 2
In the beginning…function mymodule_modules_enabled($modules) { // Stuff happens}
function mymodule_menu() { return array( // … );}
php[world] 2016 3
Procedural Programming• Code is kept in Procedures (functions)• Procedures contain steps to be carried out
php[world] 2016 4
Why don’t people like it?• Can be hard to test• Can be hard to isolate• Can be hard to name functions succinctly• Can be hard to organize• Can be hard to share• Nearly impossible to modify original, 3rd party code
php[world] 2016 5
Why do we want OOP?• Better ability to test• Better architecture through code encapsulation• With Namespaces, better naming• The ability to share code that can be extended
php[world] 2016 6
Vocabulary• Class – Textual representation of how an object is made up• Object – Variable built from a class that holds data and performs
actions
php[world] 2016 7
Classclass Employee { protected $name; protected $number;
public function setData($data) { $this->name = $data['name']; $this->number = $data['number']; }
public function viewData() { echo <<<ENDTEXTName: {$this->name}Number: {$this->number}ENDTEXT; }}
php[world] 2016 8
Object<?php
$manager= new Employee();// ^// |// `--- This is the Object
php[world] 2016 9
Methods and Propertiesclass Employee { protected $name; // This is a property protected $number;
// This is a Method public function setData($data) { $this->name = $data['name']; $this->number = $data['number']; }
public function viewData() { echo <<<ENDTEXTName: {$this->name}Number: {$this->number}ENDTEXT; }}
php[world] 2016 10
New Vocabulary• Property – Data inside of an object• Method – Function inside of an object
• Both are accessed using the -> notation
php[world] 2016 11
Visibility• Public – Anyone can access the property/method• Protected – Only the class, or child classes, can access• Private – Only the class itself can access
php[world] 2016 12
Classclass Employee { public $name; // Anyone can access this protected $number; // Only itself, and children can access private $ssn; // Only itself can access this
// Anyone can call this method public function setData($data) { $this->name = $data['name']; $this->number = $data['number']; }
public function viewData() { echo <<<ENDTEXTName: {$this->name}Number: {$this->number}ENDTEXT; }}
php[world] 2016 13
Object<?php
$manager= new Employee();$manager->name = ‘Bob’;$manager->number = 1234;
// PHP Fatal error: Cannot access protected property Employee::$number
php[world] 2016 14
Extending Classes• Inheritance• Composition
php[world] 2016 15
Namespaces• Sets up “packages” of code for organization• Is a “path” separated by \• Allows us to have classes with simple, clear names and avoid naming
collisions
php[world] 2016 16
namespace Drupal\block\Entity;
class Block {}
php[world] 2016 17
$block = new \Drupal\block\Entity\Block();
php[world] 2016 18
use \Drupal\block\Entity\Block;use \MyNamespace\block\Entity\Block as MyBlock;
$block = new Block();$my_block = new MyBlock();
php[world] 2016 19
Inheritance
php[world] 2016 20
The first thing most people learn• Classes are “things” in the real world• We should construct class properties based on Attributes• Number of wheels• Sound it makes
• We should construct class methods based on “Actions”• Running• Speaking• Jumping
php[world] 2016 21
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
php[world] 2016 22
Our Structure
Employee
Manager Scientist Laborer
php[world] 2016 23
The Employee Classabstract 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 <<<ENDTEXTName: {$this->name}Number: {$this->number}ENDTEXT; }}
php[world] 2016 24
The Manager Classclass 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 <<<ENDTEXTTitle: {$this->title}Golf Dues: {$this->dues}ENDTEXT; }}
php[world] 2016 25
The Scientist Classclass 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 <<<ENDTEXTPublications: {$this->pubs}ENDTEXT; }}
php[world] 2016 26
The Laborer Class
class Laborer extends Employee { }
php[world] 2016 27
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
php[world] 2016 28
We use it all the timenamespace Drupal\block\Entity;use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\block\BlockInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
class Block extends ConfigEntityBase, implements BlockInterface, EntityWithPluginCollectionInterface { protected $id; protected $settings; // … public function getRegion() { return $this->region; } // …}
php[world] 2016 29
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
php[world] 2016 30
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
php[world] 2016 31
Composition
php[world] 2016 32
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
php[world] 2016 33
Interfacesinterface 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);}
php[world] 2016 34
Interface Implementationclass 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; }}
php[world] 2016 35
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
php[world] 2016 36
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.
php[world] 2016 37
Avoid Code-Duplication with Traitstrait 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;}
php[world] 2016 38
Taking Advantage of OOP
php[world] 2016 39
Coupling
php[world] 2016 40
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
php[world] 2016 41
class Block extends ConfigEntityBase, implements BlockInterface, EntityWithPluginCollectionInterface { public function getPlugin() { return $this->getPluginCollection()->get($this->plugin); }
protected function getPluginCollection() { if (!$this->pluginCollection) { $this->pluginCollection = new BlockPluginCollection( \Drupal::service('plugin.manager.block'), $this->plugin, $this->get('settings'), $this->id()); } return $this->pluginCollection; }}
php[world] 2016 42
Law of Demeter
php[world] 2016 43
Dependency Injection
php[world] 2016 44
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
php[world] 2016 45
Method Injectionclass 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); }}
php[world] 2016 46
Constructor Injectionclass MapService { protected $map; public function __construct(GoogleMaps $map) { $this->map = $map; } public function getLatLong($street, $city, $state) { return $this ->map ->getLatLong($street . ' ' . $city . ' ' . $state); }}
php[world] 2016 47
Setter Injectionclass 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); }}
php[world] 2016 48
Single Responsibility Principle
php[world] 2016 49
Single Responsibility Principle• Every class should have a single responsibility, and that responsibility
should be encapsulated in that class
php[world] 2016 50
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
php[world] 2016 51
An Example/** * Create a new invoice instance. * * @param \Laravel\Cashier\Contracts\Billable $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 \Symfony\Component\HttpFoundation\Response */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;}
https://github.com/laravel/cashier/blob/master/src/Laravel/Cashier/Invoice.php
php[world] 2016 52
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
php[world] 2016 53
How to Improve• Change responsibility to just building the invoice data• Move the ‘output’ stuff to other classes
php[world] 2016 54
Unit Testing
php[world] 2016 55
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
php[world] 2016 56
Additional Resources• Clean Code – Robert C. Martin• PHP Objects, Patterns, and Practice – Matt Zandstra
php[world] 2016 57
Thank You!• Software Engineer, InQuest• Co-Host of “Jerks Talk Games”• http://jerkstalkgames.com
• Author of “Docker for Developers”• https://leanpub.com/dockerfordevs
• http://ctankersley.com• [email protected]• @dragonmantank