The document discusses object-oriented programming (OOP) concepts in PHP and Drupal, including classes, objects, inheritance, composition, interfaces, and design principles like the single responsibility principle. It provides examples of how procedural code can be rewritten using OOP patterns to make the code more modular, reusable, and testable. Key benefits of OOP like code reuse and extensibility are highlighted.
Artificial intelligence in the post-deep learning era
Coming to Terms with OOP in Drupal
1. Coming to Terms with
OOP in Drupal
Chris Tankersley
php[world] 2016
php[world] 2016 1
2. In the beginning…
function mymodule_modules_enabled($modules) {
// Stuff happens
}
function mymodule_menu() {
return array(
// …
);
}
php[world] 2016 2
3. Procedural Programming
• Code is kept in Procedures (functions)
• Procedures contain steps to be carried out
php[world] 2016 3
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. 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. 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. 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;
}
}
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. 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. 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. 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;
}
}
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
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. 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
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. 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. 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;
}
}
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. 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. 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. 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
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. 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. 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. 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. 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. 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
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. 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
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. 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. 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. 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
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. 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. 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. 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. How to Improve
• Change responsibility to just building the invoice data
• Move the ‘output’ stuff to other classes
php[world] 2016 53
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. Additional Resources
• Clean Code – Robert C. Martin
• PHP Objects, Patterns, and Practice – Matt Zandstra
php[world] 2016 56