Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Defensive Programming

Defensive Programming

Defensive programming may sound like something your granddad did after the war, but it's key to reducing the number of bugs and increasing maintainability. We're going to look at what defensive programming is and some steps to doing it in PHP.

061e3bae4ce4234a2194d20a382e5d19?s=128

Christopher Pitt

June 26, 2015
Tweet

Transcript

  1. defensive(programming

  2. None
  3. use$a$framework

  4. don't&trust&users (meet%bobby%tables)

  5. secure&input

  6. filter_var( $input, FILTER_VALIDATE_BOOLEAN ); FILTER_VALIDATE_EMAIL; FILTER_VALIDATE_IP; FILTER_VALIDATE_REGEXP; FILTER_VALIDATE_URL; # php.net/function.filter_var

  7. v::alnum() ->length(1, 15) ->validate("PHP 6"); v::regex("/[a-z]/")->validate("a"); v::startsWith("lorem")->validate("lorem ipsum"); v::leapYear()->validate("1988"); v::macAddress()->validate("af-AA-22-33-44-55");

    # github.com/respect/validation
  8. use$db$abstrac+ons

  9. /** * @entity * @table(name="cakes") */ class Cake { /**

    * @var string * @column(type="string") */ protected $type; /** * @param string $type */ public function setType($type) { $this->type = $type; } }
  10. $config = Setup::createAnnotationMetadataConfiguration( ["src"], $isDevMode = true ); $cake =

    new Cake(); $cake->setType("Brownie"); $manager = EntityManager::create($connection, $config); $manager->persist($cake); $manager->flush(); # github.com/doctrine/doctrine2
  11. $query = $factory->newSelect(); $query ->cols(["id", "type"]) ->from("cakes") ->where("cake.type = ?",

    "brownie"); print $query->getStatement(); # github.com/auraphp/aura.sqlquery
  12. just%don't%interpolate%strings%in%queries!

  13. rather&use&prepared&statements or#something#equally#secure

  14. secure&output

  15. $before = "This is an example of<br> some html content!

    <a href='http://twitter.com'>click for drama</a> <script>console.log('boo!');</script>"; $after = strip_tags($before, "<br>"); "This is an example of<br> some html content!" # php.net/function.strip_tags
  16. $before = "This is an example of<br> some html content!

    <a href='http://twitter.com'>click for drama</a> <script>console.log('boo!');</script>"; $after = htmlentities($before); "This is an example of&lt;br&gt; some html content! &lt;a href='http://twitter.com'&gt;click for drama&lt;/a&gt; &lt;script&gt;console.log('boo!');&lt;/script&gt;" # php.net/function.htmlentities
  17. don't&trust&developers (that%includes%you)

  18. write&tests

  19. seriously,*write*tests

  20. i'm$not$kidding

  21. cleancoders.com grumpy'learning.com

  22. follow%solid%principles

  23. hint%types

  24. function addTaskToQueue(Task $task, SplQueue $queue) { $clone = clone $queue;

    $clone->enqueue($task); return $clone; } function fetchProducts(array $products, Closure $then) { $then( array_map(function(Product $product) { return file_get_contents("http://api.com/{$product->id}"); }, $products) ); }
  25. function addTaxToPrice(float $percentage, float $price): float { return $price +

    ($price * $percentage); } declare(strict_types=1); $newPrice = addTaxToPrice(0.14, 120.0); # wiki.php.net/rfc/return_types # wiki.php.net/rfc/scalar_type_hints_v5
  26. un#l%then...

  27. /** * @param float $percentage * @param float $price *

    @return float */ function addTaxToPrice($percentage, $price) { assert(is_float($percentage)); assert(is_float($price)); return $price + ($price * $percentage); } # php.net/function.assert
  28. use$immutable$types

  29. /** * @param float $amount * @param string $reason *

    @param DateTime $timestamp */ public function __construct($amount, $reason, DateTime $timestamp) { assert(is_float($amount)); assert(is_string($reason)); $this->amount = $amount; $this->reason = $reason; $this->timestamp = $timestamp; }
  30. $timestamp = new DateTime("2015-06-26 12:15:00"); $transaction1 = new Transaction( 12.0,

    "bought a burrito", $timestamp ); $transaction2 = new Transaction( 9.0, "had shoes polished", $timestamp ); $timestamp->setTime(12, 20);
  31. /** * @param string $type */ public function withType($type) {

    assert(is_string($type)); $clone = clone $this; $clone->type = $type; return $clone; }
  32. /** * @param string $property * @param mixed $value */

    public function cloneWith($property, $value) { $clone = clone $this; $clone->$property = $value; return $clone; } /** * @param string $type */ public function withType($type) { assert(is_string($type)); return $this->cloneWith("type", $type); }
  33. clone&is&shallow, so#manage#deep#cloning#yourself

  34. write&simple&code (it's&the&hardest&thing)

  35. do#the#one#thing

  36. function sendOrdersAndRenderInvoice(array $orders, $templatePath) { foreach ($orders as $order) {

    if (!($order instanceof Order)) { throw new InvalidArgumentException("Invalid order type"); } try { $this->api->send($order); } catch (ApiException $exception) { $this->logger->log("There was a problem sending an order"); throw $exception; } } if (!file_exists($templatePath)) { throw new InvalidArgumentException("Template not found"); } $template = file_get_contents($templatePath); return $this->renderer->render($template, $orders); }
  37. function sendOrders(array $orders) { $this->validateOrders($orders); foreach ($orders as $order) {

    $this->sendOrder($order); } } function validateOrders(array $orders) { foreach ($orders as $order) { if (!($order instanceof Order)) { throw new InvalidArgumentException("Invalid order type"); } }; } function sendOrder(Order $order) { try { $this->api->send($order); } catch (ApiException $exception) { $this->logger->log("There was a problem sending an order"); throw $exception; } }
  38. exit%early

  39. function openAccount(AccountHolder $holder, AccountType $type) { if ($holder->getAge() > 18

    || $holder->hasParentPermission()) { if (in_array($type->getName(), $holder->getAllowedAccountTypes())) { if ($this->canOpenAccountType($type->getName())) { $this->openApprovedAccount($holder, $type); return true; } } } return false; }
  40. function openAccount(AccountHolder $holder, AccountType $type) { if ($holder->getAge() < 18

    && !$holder->hasParentPermission()) { return false; } if (!in_array($type->getName(), $holder->getAllowedAccountTypes())) { return false; } if ($this->canOpenAccountType($type->getName())) { return false; } $this->openApprovedAccount($holder, $type); return true; }
  41. avoid&flag&parameters

  42. $template = $this->getTemplate($templatePath, false); $template = trim($template); return $this->renderer->render($template, $data,

    false);
  43. avoid&mixed&parameter/return&types

  44. use$value$objects

  45. thanks joind.in/14209 twi$er.com/assertchris