$30 off During Our Annual Pro Sale. View Details »

PHP For Grown Ups

PHP For Grown Ups

My first User Group talk filled to the brim with tips on writing maintainable, reliable and (re)usable PHP applications.

Andreas Lindeboom

December 23, 2014
Tweet

Other Decks in Programming

Transcript

  1. PHP
    FOR
    GROWN-UPS

    View Slide

  2. WHY
    TALK ABOUT THIS?

    View Slide

  3. PHP
    HAS A BAD RAP...
    (WHICH IT PARTLY DESERVES)

    View Slide

  4. BUT
    YOU CAN USE IT TO BUILD PRETTY SOLID
    STUFF
    (IF YOU KNOW HOW)

    View Slide

  5. SO
    LET’S TALK ABOUT THE HOW

    View Slide

  6. (BUT FIRST, A COUPLE SMALL DISCLAIMERS...)

    View Slide

  7. NONE OF THESE IDEAS ARE NEW
    (IN FACT, MOST ARE PRETTY OLD)

    View Slide

  8. ALL OF THIS CAN BE FOUND IN BOOKS
    (NONE OF WHICH SPECIFIC TO PHP)

    View Slide

  9. GOOD CODE

    View Slide

  10. MAINTAINABLE
    RELIABLE
    (RE)USABLE

    View Slide

  11. MAINTAINABLE CODE
    (ALMOST) ALL CODE CAN AND WILL CHANGE

    View Slide

  12. UNDERSTANDABLE
    DECOUPLED
    CONSISTENTLY ABSTRACTED

    View Slide

  13. “The ratio of time spent reading code versus writing is well over 10 to 1”
    -- Uncle Bob

    View Slide

  14. NAMING
    (CLASSES AND VARIABLES)

    View Slide

  15. DESCRIPTIVE
    AND
    EXPRESSIVE

    View Slide

  16. $light->setStatus(true);

    View Slide

  17. $light->turnOn();

    View Slide

  18. NAMESPACES

    View Slide

  19. EXTRACT METHOD
    // before:
    if (is_numeric($amount) && $amount > 0)
    // after:
    if ($this->isValidAmount($amount))

    View Slide

  20. private function isValidAmount($amount)
    {
    return is_numeric($amount) && $amount > 0;
    }

    View Slide

  21. NAMES WILL SOMETIMES LIE TO YOU

    View Slide

  22. // neither of these is actually a locale
    $this->locale = $locale->locale;

    View Slide

  23. COMMENT ALL THE THINGS
    (WELL, NOT ALL THE THINGS...)

    View Slide

  24. CODE SAYS WHAT
    COMMENTS SAY WHY

    View Slide

  25. // set the patient’s clinic
    $patient->setClinic($clinic);
    // ORLY?

    View Slide

  26. // to register a patient, set the patient’s clinic
    $patient->setClinic($clinic);

    View Slide

  27. GOOD NAMING AND GOOD
    ABSTRACTIONS BEAT
    GOOD COMMENTS

    View Slide

  28. $clinic->registerPatient($patient);

    View Slide

  29. PRINCIPLE OF LEAST ASTONISHMENT
    (MINIMIZE THE NUMBER OF WTFS PER MINUTE)

    View Slide

  30. PHPDOC ALL THE THINGS
    CLASSES, VARIABLES, METHODS, ARGUMENTS

    View Slide

  31. /**
    * If what this method does can not be made obvious from
    * its name, explain it here.
    *
    * @param string $firstname
    * @return void
    */
    public function changeFirstName($firstName)
    {
    // code
    }

    View Slide

  32. WHY BOTHER IF PHP DOES
    NOT ENFORCE THIS?
    COMMUNICATES INTENT
    WORKS GREAT WITH IDES

    View Slide

  33. “Shy code - modules that don’t reveal anything unnecessary to other
    modules and that don’t rely on other modules’ implementations”
    - The Pragmatic Programmer

    View Slide

  34. WHY DECOUPLING?
    ISOLATES CHANGES
    MAKES YOUR CODE MORE FLEXIBLE
    ENCOURAGES ABSTRACTION

    View Slide

  35. ”Modules that don’t reveal anything unnecessary to other modules”
    ENCAPSULATION

    View Slide

  36. $totalPrice = 0;
    foreach ($order->getItems() as $item) {
    /** @var OrderItem $item */
    $amount = $item->getAmount();
    $price = $item->getPrice();
    $totalPrice += $amount * $price;
    }
    $totalPrice += $order->getShippingCosts();

    View Slide

  37. $totalPrice = $order->getTotalPrice();

    View Slide

  38. ”Tell, don’t ask”

    View Slide

  39. $discountedItems = array();
    foreach ($order->getItems() as $item) {
    /** @var OrderItem $item */
    $oldPrice = $item->getPrice();
    $newPrice = $oldPrice - ($oldPrice * ($discountPercentage / 100))
    $item->setPrice($discountPercentage);
    $discountedItems[] = $item;
    }
    $order->setItems($items);

    View Slide

  40. $order->applyDiscount($discountPercentage);

    View Slide

  41. “[...] modules that don’t rely on other modules’ implementations”

    View Slide

  42. FLAVORS OF COUPLING
    INTERNAL DEPENDENCIES
    EXTERNAL PACKAGES
    DATABASES/ORMS
    FRAMEWORKS

    View Slide

  43. EVEN
    PROGRAMMING LANGUAGES

    View Slide

  44. DEPENDENCY INJECTION

    View Slide

  45. Before:
    class EmployeeService
    {
    /**
    * @param integer $employeeId
    * @return void
    */
    public function delete($id)
    {
    $employeeStore = new EmployeeStore();
    $employeeStore->deleteById($id);
    }
    }

    View Slide

  46. After:
    class EmployeeService
    {
    /*
    * @var EmployeeStore
    */
    private $employeeStore
    /**
    * @param mixed $employeeStore
    * @return void
    */
    public function __construct($employeeStore)
    {
    $this->employeeStore = $employeeStore;
    }
    // rest of class here...
    }

    View Slide

  47. /**
    * @param integer $id
    * @return void
    */
    public function deleteById($id)
    {
    $this->employeeStore->deleteById($id);
    }

    View Slide

  48. public function __construct($employeeStore)

    View Slide

  49. public function __construct(EmployeeStore $employeeStore)

    View Slide

  50. interface EmployeeStoreInterface
    {
    /**
    * @param integer $id
    */
    public function deleteById($id);
    }

    View Slide

  51. class PdoEmployeeStore implements EmployeeStoreInterface
    {
    /**
    * @param integer $id
    */
    public function deleteById($id) {
    // PDO implementation here
    }
    }

    View Slide

  52. public function __construct(EmployeeStoreInterface $employeeStore)

    View Slide

  53. TREAT INTERFACES AS THE REAL THING

    View Slide

  54. “That seems like an hell of a lot of work...”

    View Slide

  55. DI CONTAINER
    ”IF I AM ASKING FOR AN EMPLOYEESTORE, GIVE ME AN INSTANCE OF THIS CLASS”

    View Slide

  56. SYMFONY DEPENDENCYINJECTION
    LARAVEL IOC CONTAINER
    ORNO DI
    PIMPLE

    View Slide

  57. “Mathematics is the art of giving the same name to different things.”
    ― Henri Poincaré

    View Slide

  58. /**
    * @param string $searchTerm
    * @param string $productCategory
    * @param string $make
    * @param string $color
    */
    public function search($searchTerm, $productCategory, $make, $color)
    {
    switch ($productType) {
    // search products
    }
    }

    View Slide

  59. /**
    * @param SearchTerm $searchTerm
    * @param ProductCategory $productCategory
    * @param Make $make
    * @param Color $color
    */
    public function search(
    SearchTerm $searchTerm,
    ProductCategory $productCategory,
    Make $make,
    Color $color
    ) {
    switch($productType->getTypeId())
    // search products
    )
    }

    View Slide

  60. VALUE OBJECTS
    class SearchTerm
    {
    const MAX_TERM_LENGTH = 30;
    /**
    * @var string
    */
    private $searchTerm;
    /**
    * @param string $term
    * @throws InvalidTermException
    */
    public function __construct($term)
    {
    if (strlen($term) >= self::MAX_TERM_LENGTH) {
    throw new InvalidTermException($term);
    }
    }
    }

    View Slide

  61. ”But isn’t this coupling?”

    View Slide

  62. “Figuring out if two people are married is easy. Figuring out if they
    should be is hard.”
    - Michael Feathers

    View Slide

  63. /**
    * @param SearchQuery $query
    * @return SearchResults
    */
    public function search(SearchQuery $query)
    {
    /** @var SearchWorker $worker */
    $worker = $this->getWorkerForQuery($query);
    /** @var SearchResults $results */
    $results = $worker->process($query);
    return $results;
    }

    View Slide

  64. ABSTRACTION TOOL
    NAMESPACES

    View Slide

  65. ABSTRACTION LEVELS

    View Slide

  66. LOW LEVEL OF ABSTRACTION
    $statement = $database->prepare(
    “INSERT INTO `fish`(‘name’, ‘species’, ‘gender’, ‘tankId’) VALUES(?, ?, ?, ?)”
    );
    $statement->execute(array(‘Nemo’, $speciesId, ‘M’, $tankId));

    View Slide

  67. HIGH LEVEL OF ABSTRACTION
    $command = new IntroduceFishCommand($fish, $tank);
    $this->commandBus->execute($command);

    View Slide

  68. LAYERED ARCHITECTURE

    View Slide

  69. HTTP LAYER
    FRAMEWORK LAYER
    SERVICE LAYER
    DOMAIN LAYER
    PRESENTATION LAYER

    View Slide

  70. ”You gotta keep ‘em separated”

    View Slide

  71. RELIABLE CODE
    ”YOU ARE NOT PHPREPARED”

    View Slide

  72. KEEP IT UNDERSTANDABLE
    CODE DEFENSIVELY
    KEEP IT TESTABLE
    USE YOUR LANGUAGE FEATURES

    View Slide

  73. In order to prevent defects in code, you first need to understand it

    View Slide

  74. ”Rely only on reliable things”
    - The Pragmatic Programmer

    View Slide

  75. Not a reliable thing
    THE PHP TYPE SYSTEM

    View Slide

  76. if (! $amount) {
    // do something
    }
    // $amount can actually be 0, “0”, “”, null or false for this to be true

    View Slide

  77. if ($amount == 0) {
    // do something
    }
    // you would think that explicitly checking for 0 would improve things...

    View Slide

  78. Computer says no...

    View Slide

  79. if ($amount === 0) {
    // do something
    }
    // this will only evaluate as true when $amount is actually the number 0

    View Slide

  80. CRANK YOUR ERROR REPORTING UP
    KNOW WHY YOUR CODE WORKS
    BE AWARE OF FAILURE SCENARIOS
    CATCH AND THROW EXCEPTIONS

    View Slide

  81. ”Test your software, or your users will”
    - The Pragmatic Programmer

    View Slide

  82. TEST-DRIVEN DEVELOPMENT

    View Slide

  83. PHPUNIT
    PHPSPEC
    CODECEPTION
    BEHAT

    View Slide

  84. TDD IS NOT JUST UNIT TESTING

    View Slide

  85. ACCEPTANCE
    FUNCTIONAL
    INTEGRATION
    UNIT

    View Slide

  86. MORE CONFIDENCE
    BETTER DESIGN
    FEWER DEFECTS
    REGRESSION CATCHING
    TESTS AS DOCUMENTATION

    View Slide

  87. TYPEHINTING
    VISIBILITY (PRIVATE BY DEFAULT)
    INTERFACES
    EXCEPTIONS
    ISSET, IS_NUMERIC, ETC.

    View Slide

  88. USE PHPDOC WITH A GOOD IDE

    View Slide

  89. DECOUPLED CODE
    GOOD ABSTRACTIONS
    NAMESPACES
    COMPOSER
    AUTOLOADING/IDES

    View Slide

  90. COMPOSER
    OPEN SOURCE
    PRIVATE REPOSITORIES IN COMPOSER.JSON
    TORAN PROXY

    View Slide

  91. // FILE: composer.json
    {
    "name": "behat/behat",
    "description": "Scenario-oriented BDD framework for PHP 5.3",
    “require”: {}
    }

    View Slide

  92. // FILE: composer.json
    {
    "repositories": [
    { "type": "vcs", "url": "http://github.com/user/repo" },
    ],
    “require”: {
    “user/package”: “*”
    }
    }

    View Slide

  93. AUTOLOADING
    NO MORE REQUIRE/INCLUDE
    JUST USE THE CLASS
    (OPTIONAL) USE STATEMENT

    View Slide

  94. SOLID PRINCIPLES

    View Slide

  95. SINGLE RESPONSIBILITY PRINCIPLE
    OPEN-CLOSED PRINCIPLE
    LISKOV SUBSTITUTION PRINCIPLE
    INTERFACE SEGREGATION PRINCIPLE
    DEPENDENCY INVERSION PRINCIPLE

    View Slide

  96. SINGLE RESPONSIBILITY PRINCIPLE
    ”A class should have one, and only one, reason to change.”

    View Slide

  97. ”Every class, or similar structure, should have only one job to do.”
    - Richard Carr

    View Slide

  98. ASK YOURSELF
    “What responsibilities does this piece of code have?”

    View Slide

  99. $user = new User($userName, $password, $firstName, $lastName, $email);
    $user->save();
    // later on...
    $user->login($userName, $password);

    View Slide

  100. CONTAIN LOGIN CREDENTIALS
    CONTAIN PERSONAL INFORMATION
    STORE TO DATABASE
    ACCESS MANAGEMENT

    View Slide

  101. USER CLASS
    CUSTOMER CLASS
    USERSTORE / CUSTOMERSTORE CLASSES
    AUTHENTICATIONMANAGER CLASS

    View Slide

  102. $userAccount = new UserAccount($userName, $password);
    $authenticationManager->register($userAccount);
    $customer = new Customer($firstName, $lastName, $email);
    $customer->linkUserAccount($userAccount);
    $customerService->register($customer);
    $authenticationService->login($userName, $password)

    View Slide

  103. OPEN-CLOSED PRINCIPLE
    “A class should be open for extension, but closed for modification”

    View Slide

  104. public function ship(Product $product, $shippingMethod)
    {
    switch ($shippingmethod) {
    case shipping::shipping_default:
    $this->defaultshipping($product);
    break;
    case shipping::shipping_priority:
    $this->priorityshipping($product);
    break;
    }
    }

    View Slide

  105. public function addShippingMethod(ShippingMethodInterface $method, $identifier)
    {
    $this->shippingMethods[$identifier] = $method;
    }
    public function ship(Product $product, $shippingMethodIdentifier)
    {
    $shippingMethod = $this->shippingMethods[$shippingMethodIdentifier];
    $shippingMethod->ship($product);
    }

    View Slide

  106. LISKOV SUBSTITUTION PRINCIPE
    ”Subclasses must be substitutable for their base classes.”

    View Slide

  107. “Any implementation of an abstraction (interface) should be
    substitutable in any place that the abstraction is accepted”
    - Jeffrey Way

    View Slide

  108. interface VehicleInterface
    {
    public function driveTo(Destination $destination);
    }

    View Slide

  109. class Boat implements VehicleInterface
    {
    public function driveTo(Destination $destination)
    {
    // start driving
    }
    }

    View Slide

  110. class Car implements VehicleInterface
    {
    public function driveTo(Destination $destination)
    {
    // start driving
    }
    public function swapTires(TyreTypeInterface $tyreType)
    {
    // swap tyres
    }
    }

    View Slide

  111. /** @var VehicleInterface $vehicle */
    $vehicle->swapTyres($tyreType);
    // Car: works
    // Boat: Method ‘swapTires’ not found in class Boat

    View Slide

  112. INTERFACE SEGREGATION PRINCIPLE
    ”Never force classes to implement methods that they do not use”

    View Slide

  113. interface VehicleInterface
    {
    public function driveTo(Destination $destination);
    public function swapTires(TyreTypeInterface $tyreType);
    }

    View Slide

  114. class Boat implements VehicleInterface
    {
    public function driveTo(Destination $destination)
    {
    // start driving
    }
    public function swapTires(TyreTypeInterface $tyreType)
    {
    throw new BoatHasNoWheelsException();
    }
    }

    View Slide

  115. interface VehicleInterface
    {
    public function driveTo(Destination $destination);
    }
    interface WheeledVehicleInterface
    {
    public function swapTires(TyreTypeInterface $tyreType);
    }

    View Slide

  116. class Car implements VehicleInterface, WheeledVehicleInterface

    View Slide

  117. class Boat implements VehicleInterface, BoatInterface

    View Slide

  118. DEPENDENCY INVERSION PRINCIPE
    ”Depend on abstractions, not on concretions.”

    View Slide

  119. class StoreHouse
    {
    private $supplier;
    public function __construct(FooBarInc $supplier)
    {
    $this->supplier = $supplier;
    }
    }

    View Slide

  120. class StoreHouse
    {
    private $supplier;
    public function __construct(SupplierInterface $supplier)
    {
    $this->supplier = $supplier;
    }
    }

    View Slide

  121. class FooBarIncSupplier implements SupplierInterface
    {
    public function order(Product $product)
    {
    $orderId = $this->fooBarInc->createProductOrder();
    $this->fooBarInc->addProductToOrder($orderId);
    }
    }

    View Slide

  122. QUESTIONS?

    View Slide

  123. READING LIST
    Patterns of Enterprise Application Architecture - Martin Fowler
    Clean Code - Robert C. Martin
    Growing Object-Oriented Software, Guided By Tests (the GOOS book) -
    Steve Freeman & Nat Pryce
    The Pragmatic Programmer - Dave Thomas & Andy Hunt

    View Slide

  124. VIDEOS
    Models and Service Layers; Hemoglobin and Hobgoblins - Ross Tuck
    (https://www.youtube.com/watch?v=3uV3ngl1Z8g)
    Unbreakable Domain Models - Mathias Verraes (https://
    www.youtube.com/watch?v=ZJ63ltuwMaE)
    The Framework As An Implementation Detail (https://
    www.youtube.com/watch?v=0L_9NutiJlc)

    View Slide

  125. PODCASTS
    Elephant in the Room (http://elephantintheroom.io)

    View Slide

  126. View Slide