Coming to Terms with OOP In Drupal - php[world] 2016

Post on 16-Apr-2017

129 views 0 download

transcript

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 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• chris@ctankersley.com• @dragonmantank