REMOVE THE MAGIC

with

Functional PHP

https://slides.com/davesters/remove-magic-functional-php

OUR APPLICATIONS

ARE FULL OF MAGIC

  • Large frameworks
  • ORMs
  • Multiple layers of inheritance
  • Dependency Injection
  • Convention over Configuration

HOW DO WE

FIGHT MAGIC?

Level up our magic resistance

SIMPLICITY

The state or quality of being simple

+10 MR

CONVENTION

OVER

CONFIGURATION

ORMs

$results = $accounts
    ->where("first_name = 'Bob'")
    ->andWhere("last_name = 'Sacameno'")
    ->andWhere('deleted IS NULL')
    ->andWhere('ref_id = 2')
    ->not()->andWhere('other_id = 3')
    ->orderByDesc('created')
    ->limit(10);
$results = $db->query("SELECT * FROM accounts WHERE
    first_name = 'bob' AND last_name = 'Sacameno' AND
    deleted IS NULL AND ref_id = 2 AND other_id <> 3
    ORDER BY created DESC LIMIT 10");

DEPENDENCY INJECTION

$container->bind('iFooContext', '\Foo\Context');
$container->bind('iBarController', '\Controllers\BarController');
…

AUTOMAPPERS

class Account {
    public $id, $name, $email;
}
$account = Mapper::map('Account', $databaseAccount);
Mapper::createMap('Account')->forMember('name', function($a) {
    return $a['first_name'] . ‘ ‘ . $a['last_name'];
});

Mapper::createMap('Account')->forMember('email', function($a) {
    return strtolower($a['email']);
});

AUTOMAPPERS

class Account {
    public $id, $name, $email;

    public function map($account) {
        $this->id = $account->id;
        $this->name = $account->first_name.' '.$account->last_name;
        $this->email = strtolower($account->email);
    }
}
$account->map($databaseAccount);

"Typing is not the bottleneck"

- Michael Hill?

OBLIGATORY QUOTE SLIDE

SEGWAY

SEGUE

You are responsible for ALL the code in your project

FUNCTIONAL PROGRAMMING

"A style of building computer programs, that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data..."

- Wikipedia

+50 MR

TENETS OF FUNCTIONAL PROGRAMMING

  • First-class functions

  • Pure functions

  • Immutable Data

  • Plenty more...

FIRST-CLASS FUNCTIONS

$add = function ($a, $b) {
    return $a + $b;
});

HIGHER-ORDER FUNCTIONS

function getSomeAsyncData(‘foo’, function($data) {
    // do something with async data here.
});

PURE FUNCTIONS

function add($a, $b) {
    return $a + $b;
}

REFERENTIAL TRANSPARENCY

$sum = add(2, 3);

$sum = 5;

IMMUTABLE DATA

$numbers = [1, 2, 3, 4, 5];

for ($x = 0; $x < count($numbers); $x++) {
    $numbers[$x] = $numbers[$x] + 5;
}

An immutable object is an object whose state cannot be modified after it is created.

foreach ($numbers as &$num) {
    $num += 5;
}

IMMUTABLE DATA

$numbers = [1, 2, 3, 4, 5];

for ($x = 0; $x < count($numbers); $x++) {
    $numbers[$x] = $numbers[$x] + 5;
}
$biggerNumbers = array_map(function($num) {
    return $num + 5;
}, $numbers);

Functional Way

$biggerNumbers = array_map('add5', $numbers);
function add5($a) {
    return $a + 5;
}
function add5($a) {
    return add($a, 5);
}

$num = add5(7); // 12
function add5($num) {
    return $num + 5;
}
function add($a, $b) {
    return $a + $b;
}

FUNCTION COMPOSITION

TODO APP

LET'S  BUILD SOMETHING...

Index.php

require 'vendor/autoload.php';
require 'src/TodoApp.php';
require 'src/Mustache.php';

$handlers = [];

$mustache = Mustache::Render(new Mustache_Engine(), function($tpl) {
    return file_get_contents(__DIR__ . "/views/$tpl.mustache");
});

$klein = new \Klein\Klein();

$todoApp = new TodoApp($klein, $mustache, $handlers);
$todoApp->setRoutes();
$todoApp->start();

TodoApp.php

class TodoApp {
  private $app, $handlers, $mustache;

  public function __construct(Klein $app, $mustache, $handlers) {
    $this->app = $app;
    $this->handlers = $handlers;
    $this->mustache = $mustache;
  }

  public function start() {
    $this->app->dispatch();
  }

  public function setRoutes() { ... }

  private function handle($view, $handlers) { ... }
}

TodoApp.php routes

public function setRoutes() {
  $h = $this->handlers;

  $this->app->respond('/', $this->handle('index', $h['index']));
}

private function handle($view, array $handlers) {
  // Loop over $handlers array and build view model
    
  $this->render($this->mustache($view, $model));
}

IndexHandler.php

class IndexHandler {

  public static function Handle($dataSource) {
    return function($model, $params) {
      $todos = $dataSource('SELECT id, todo, completed
        FROM todos WHERE deleted IS NULL ORDER BY created');
    
      return [
        'title' => 'Todo App',
        'todos' => $todos,
        'count' => count($todos)
      ];
    };
  }

}
$handlers = [
  'index' => [ Handlers\IndexHandler::Handle($dataSource) ]
];

Add and Update Handlers

$handlers = [
  'index'  => [ Handlers\IndexHandler::Handle($dataSource) ],
  'add'    => [ Handlers\AddTodoHandler::Handle($dataSource) ],
  'update' => [ Handlers\UpdateTodoHandler::Handle($dataSource) ]
];

IndexQuery.php

class IndexQuery {

  public static function Query($dataSource) {
    return function($model, $params) {
      $todos = $dataSource('SELECT id, todo, completed
            FROM todos WHERE deleted IS NULL ORDER BY created');

      return [ 'todos' => $todos ];
    };
  }

}

IndexHandler.php

class IndexHandler {
  public static function Handle() {
    return function($model, $params) {
      return [
        'title' => 'Todo App',
        'todos' => $model['todos']
        'count' => count($model['todos']
      ];
    };
  }
}
$handlers = [
  'index' => [
    Queries\IndexQuery::Query($dataSource),
    Handlers\IndexHandler::Handle()
  ],
  ...
];

Summary

  • What problem am I having that this library is going to solve?
  • Can I change my problem around so I don’t need it?

Thanks

Sample App:

https://github.com/davesters/functional-todo-example


"8 Lines of Code" Keynote:

http://www.infoq.com/presentations/8-lines-code-refactoring


Contact:

Twitter - @davesters

Github - davesters

Website - http://www.lovesmesomecode.com

Made with Slides.com