Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

195 views

Published on

Drupal 8 has not only brought to the table a much improved admin experience, but has now moved on from its procedural roots into the realm of Object Oriented Programming. While this is a great thing for developers, many Drupal developers have never been introduced to OOP. This talk will explore a very high-level overview of objects, inheritance, composition, and how to architect your code as it relates to Drupal 8.

Published in: Technology
  • Be the first to like this

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

  1. 1. Coming to Terms with OOP in Drupal Chris Tankersley php[world] 2016 php[world] 2016 1
  2. 2. In the beginning… function mymodule_modules_enabled($modules) { // Stuff happens } function mymodule_menu() { return array( // … ); } php[world] 2016 2
  3. 3. Procedural Programming • Code is kept in Procedures (functions) • Procedures contain steps to be carried out php[world] 2016 3
  4. 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 4
  5. 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 5
  6. 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 6
  7. 7. php[world] 2016 7 Class class Employee { protected $name; protected $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; } }
  8. 8. Object <?php $manager= new Employee(); // ^ // | // `--- This is the Object php[world] 2016 8
  9. 9. php[world] 2016 9 Methods and Properties class 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 <<<ENDTEXT Name: {$this->name} Number: {$this->number} ENDTEXT; } }
  10. 10. New Vocabulary • Property – Data inside of an object • Method – Function inside of an object • Both are accessed using the -> notation php[world] 2016 10
  11. 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 11
  12. 12. php[world] 2016 12 Class class 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 <<<ENDTEXT Name: {$this->name} Number: {$this->number} ENDTEXT; } }
  13. 13. Object <?php $manager= new Employee(); $manager->name = ‘Bob’; $manager->number = 1234; // PHP Fatal error: Cannot access protected property Employee::$number php[world] 2016 13
  14. 14. Extending Classes • Inheritance • Composition php[world] 2016 14
  15. 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 15
  16. 16. namespace DrupalblockEntity; class Block { } php[world] 2016 16
  17. 17. $block = new DrupalblockEntityBlock(); php[world] 2016 17
  18. 18. use DrupalblockEntityBlock; use MyNamespaceblockEntityBlock as MyBlock; $block = new Block(); $my_block = new MyBlock(); php[world] 2016 18
  19. 19. Inheritance php[world] 2016 19
  20. 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 20
  21. 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 21
  22. 22. Our Structure php[world] 2016 22 Employee Manager Scientist Laborer
  23. 23. The Employee Class php[world] 2016 23 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; } }
  24. 24. The Manager Class php[world] 2016 24 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; } }
  25. 25. The Scientist Class php[world] 2016 25 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; } }
  26. 26. The Laborer Class php[world] 2016 26 class Laborer extends Employee { }
  27. 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 27
  28. 28. We use it all the time namespace DrupalblockEntity; use DrupalCoreConfigEntityConfigEntityBase; use DrupalblockBlockInterface; use DrupalCoreEntityEntityWithPluginCollectionInterface; class Block extends ConfigEntityBase, implements BlockInterface, EntityWithPluginCollectionInterface { protected $id; protected $settings; // … public function getRegion() { return $this->region; } // … } php[world] 2016 28
  29. 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 29
  30. 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 30
  31. 31. Composition php[world] 2016 31
  32. 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 32
  33. 33. 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); } php[world] 2016 33
  34. 34. 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; } } php[world] 2016 34
  35. 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 35
  36. 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 36
  37. 37. 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; } php[world] 2016 37
  38. 38. Taking Advantage of OOP php[world] 2016 38
  39. 39. Coupling php[world] 2016 39
  40. 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 40
  41. 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 41
  42. 42. Law of Demeter php[world] 2016 42
  43. 43. Dependency Injection php[world] 2016 43
  44. 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 44
  45. 45. 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); } } php[world] 2016 45
  46. 46. 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); } } php[world] 2016 46
  47. 47. 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); } } php[world] 2016 47
  48. 48. Single Responsibility Principle php[world] 2016 48
  49. 49. Single Responsibility Principle • Every class should have a single responsibility, and that responsibility should be encapsulated in that class php[world] 2016 49
  50. 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 50
  51. 51. 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; } php[world] 2016 51 https://github.com/laravel/cashier/blob/master/src/Laravel/Cashier/Invoice.php
  52. 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 52
  53. 53. How to Improve • Change responsibility to just building the invoice data • Move the ‘output’ stuff to other classes php[world] 2016 53
  54. 54. Unit Testing php[world] 2016 54
  55. 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 55
  56. 56. Additional Resources • Clean Code – Robert C. Martin • PHP Objects, Patterns, and Practice – Matt Zandstra php[world] 2016 56
  57. 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 php[world] 2016 57

×