Professional Documents
Culture Documents
Jeremy Kendall
Jeremy Kendall
I love to code
Jeremy Kendall
I love to code Im terribly forgetful
Jeremy Kendall
I love to code Im terribly forgetful I take pictures
Jeremy Kendall
I love to code Im terribly forgetful I take pictures I work at OpenSky
Micro framework?
Micro framework?
Concise codebase
Micro framework?
Concise codebase Clear codebase
Micro framework?
Concise codebase Clear codebase Addresses a small set of use cases
Micro framework?
Concise codebase Clear codebase Addresses a small set of use cases Addresses those use cases well
What is Slim?
What is Slim?
Inspired by Sinatra
What is Slim?
Inspired by Sinatra Favors cleanliness over terseness
What is Slim?
Inspired by Sinatra Favors cleanliness over terseness Favors common cases over edge cases
Installing Slim
RTFM
;-) RTFM
http://docs.slimframework.com/pages/routing-url-rewriting/
Hello world
<?php require '../vendor/autoload.php'; $app = new \Slim\Slim(); $app->get('/hello/:name', function ($name) { echo "Hello, $name"; }); $app->run();
Hello world
<?php require '../vendor/autoload.php'; $app = new \Slim\Slim(); $app->get('/hello/:name', function ($name) { echo "Hello, $name"; }); $app->run();
Hello world
<?php require '../vendor/autoload.php'; $app = new \Slim\Slim(); $app->get('/hello/:name', function ($name) { echo "Hello, $name"; }); $app->run();
Hello world
<?php require '../vendor/autoload.php'; $app = new \Slim\Slim(); $app->get('/hello/:name', function ($name) { echo "Hello, $name"; }); $app->run();
Hello world
<?php require '../vendor/autoload.php'; $app = new \Slim\Slim(); $app->get('/hello/:name', function ($name) { echo "Hello, $name"; }); $app->run();
Flaming Archer
Flaming Archer
wat
Great repository names are short and memorable. Need inspiration? How about aming-archer.
Flaming Archer
Flaming Archer
Photo 365 project
Flaming Archer
Photo 365 project Built in 4 days (Saturday through Tuesday)
Flaming Archer
Photo 365 project Built in 4 days (Saturday through Tuesday) Basic application a few bells, no whistles
Flaming Archer
Photo 365 project Built in 4 days (Saturday through Tuesday) Basic application a few bells, no whistles Routing
Flaming Archer
Photo 365 project Built in 4 days (Saturday through Tuesday) Basic application a few bells, no whistles Routing Twig views
Flaming Archer
Photo 365 project Built in 4 days (Saturday through Tuesday) Basic application a few bells, no whistles Routing Twig views Middleware
4 views
phploc --exclude vendor,tests,templates . phploc 1.6.4 by Sebastian Bergmann. Directories: Files: Lines of Code (LOC): Cyclomatic Complexity / Lines of Code: Comment Lines of Code (CLOC): Non-Comment Lines of Code (NCLOC): 7 13 876 0.04 272 604
Conguration
return array( 'slim' => array( 'templates.path' => __DIR__ . '/templates', 'log.level' => 4, 'log.enabled' => true, 'log.writer' => new Slim\Extras\Log\DateTimeFileWriter( array( 'path' => __DIR__ . '/logs', 'name_format' => 'y-m-d' ) ) ), 'twig' => array( // . . . ), 'cookies' => array( // . . . ), 'flickr.api.key' => 'FLICKR API KEY', 'pdo' => array( // . . . ) );
return array( 'slim' => array( 'templates.path' => __DIR__ . '/templates', 'log.level' => 4, 'log.enabled' => true, 'log.writer' => new Slim\Extras\Log\DateTimeFileWriter( array( 'path' => __DIR__ . '/logs', 'name_format' => 'y-m-d' ) ) ), 'twig' => array( // . . . ), 'cookies' => array( // . . . ), 'flickr.api.key' => 'FLICKR API KEY', 'pdo' => array( // . . . ) );
Slim
return array( 'slim' => array( 'templates.path' => __DIR__ . '/templates', 'log.level' => 4, 'log.enabled' => true, 'log.writer' => new Slim\Extras\Log\DateTimeFileWriter( array( 'path' => __DIR__ . '/logs', 'name_format' => 'y-m-d' ) Views ) ), 'twig' => array( // . . . ), 'cookies' => array( // . . . ), 'flickr.api.key' => 'FLICKR API KEY', 'pdo' => array( // . . . ) );
Slim
return array( 'slim' => array( 'templates.path' => __DIR__ . '/templates', 'log.level' => 4, 'log.enabled' => true, 'log.writer' => new Slim\Extras\Log\DateTimeFileWriter( array( 'path' => __DIR__ . '/logs', 'name_format' => 'y-m-d' ) Views ) ), 'twig' => array( // . . . ), 'cookies' => array( Cookies // . . . ), 'flickr.api.key' => 'FLICKR API KEY', 'pdo' => array( // . . . ) );
Slim
return array( 'slim' => array( 'templates.path' => __DIR__ . '/templates', 'log.level' => 4, 'log.enabled' => true, 'log.writer' => new Slim\Extras\Log\DateTimeFileWriter( array( 'path' => __DIR__ . '/logs', 'name_format' => 'y-m-d' ) Views ) ), 'twig' => array( // . . . ), 'cookies' => array( Cookies // . . . ), 'flickr.api.key' => 'FLICKR API KEY', 'pdo' => array( // . . . My stuff ) );
Slim
Routing
Routing
$app->get('/', function () use ($app, $service) { $images = $service->findAll(); $app->render('index.html', array('images' => $images)); } );
Routing
HTTP Method
$app->get('/', function () use ($app, $service) { $images = $service->findAll(); $app->render('index.html', array('images' => $images)); } );
Routing
HTTP Method
Resource URI
$app->get('/', function () use ($app, $service) { $images = $service->findAll(); $app->render('index.html', array('images' => $images)); } );
Routing
HTTP Method
Resource URI
Anonymous Function
$app->get('/', function () use ($app, $service) { $images = $service->findAll(); $app->render('index.html', array('images' => $images)); } );
Routing
$app->get('/', function () use ($app, $service) { $images = $service->findAll(); $app->render('index.html', array('images' => $images)); } );
Routing
$app->get('/', function () use ($app, $service) { Grabs all the pics $images = $service->findAll(); $app->render('index.html', array('images' => $images)); } );
Routing
$app->get('/', function () use ($app, $service) { Grabs all the pics $images = $service->findAll(); $app->render('index.html', array('images' => $images)); } );
GET
$app->get('/:day', function($day) use ($app, $service) { $image = $service->find($day); if (!$image) { $app->notFound(); } $app->render('images.html', $image); } )->conditions(array('day' => '([1-9]\d?|[12]\d\d|3[0-5]\d|36[0-6])'));
URL parameter
GET
$app->get('/:day', function($day) use ($app, $service) { $image = $service->find($day); if (!$image) { $app->notFound(); } $app->render('images.html', $image); } )->conditions(array('day' => '([1-9]\d?|[12]\d\d|3[0-5]\d|36[0-6])'));
URL parameter
GET
$app->get('/:day', function($day) use ($app, $service) { $image = $service->find($day); if (!$image) { $app->notFound(); } $app->render('images.html', $image); } )->conditions(array('day' => '([1-9]\d?|[12]\d\d|3[0-5]\d|36[0-6])'));
URL parameter
GET
$app->get('/:day', function($day) use ($app, $service) { $image = $service->find($day); if (!$image) { $app->notFound(); } $app->render('images.html', $image); } )->conditions(array('day' => '([1-9]\d?|[12]\d\d|3[0-5]\d|36[0-6])'));
Condition
URL parameter
GET
$app->get('/:day', function($day) use ($app, $service) { $image = $service->find($day); if (!$image) { $app->notFound(); } $app->render('images.html', $image); } )->conditions(array('day' => '([1-9]\d?|[12]\d\d|3[0-5]\d|36[0-6])'));
Condition
1 to 366
GET
$app->get('/:day', function($day) use ($app, $service) { $image = $service->find($day); if (!$image) { $app->notFound(); }
404!
302 Redirect
Multiple methods
$app->map('/login', function() { // Login } )->via('GET', 'POST');
Multiple methods
Not an HTTP Method
Multiple methods
Not an HTTP Method
$app->post('/admin/clear-cache', function() use ($app) { $log = $app->getLog(); $cleared = null; $clear = $app->request()->post('clear'); if ($clear == 1) { if (apc_clear_cache('user')) { $cleared = 'Cache was successfully cleared!'; } else { $cleared = 'Cache was not cleared!'; $log->error('Cache not cleared'); } } $app->flash('cleared', $cleared); $app->redirect('/admin'); } );
$app->post('/admin/clear-cache', function() use ($app) { $log = $app->getLog(); Get the log from $cleared = null; $clear = $app->request()->post('clear');
$app
if ($clear == 1) { if (apc_clear_cache('user')) { $cleared = 'Cache was successfully cleared!'; } else { $cleared = 'Cache was not cleared!'; $log->error('Cache not cleared'); } } $app->flash('cleared', $cleared); $app->redirect('/admin'); } );
$app->post('/admin/clear-cache', function() use ($app) { $log = $app->getLog(); Get the log from $cleared = null; $clear = $app->request()->post('clear');
$app
if ($clear == 1) { if (apc_clear_cache('user')) { $cleared = 'Cache was successfully cleared!'; } else { $cleared = 'Cache was not cleared!'; $log->error('Cache not cleared'); } } $app->flash('cleared', $cleared); $app->redirect('/admin'); } );
Error!
$app->post('/admin/clear-cache', function() use ($app) { $log = $app->getLog(); Get the log from $cleared = null; $clear = $app->request()->post('clear');
$app
if ($clear == 1) { if (apc_clear_cache('user')) { $cleared = 'Cache was successfully cleared!'; } else { $cleared = 'Cache was not cleared!'; $log->error('Cache not cleared'); } } $app->flash('cleared', $cleared); $app->redirect('/admin'); } );
Error!
Middleware
. . . a Slim application can have middleware that may inspect, analyze, or modify the application environment, request, and response before and/or after the Slim application is invoked.
http://docs.slimframework.com/pages/middleware-overview/
Hooks
Hooks
slim.before
Hooks
slim.before slim.before.router
Hooks
slim.before slim.before.router slim.before.dispatch
Hooks
slim.before slim.before.router slim.before.dispatch slim.after.dispatch
Hooks
slim.before slim.before.router slim.before.dispatch slim.after.dispatch slim.after.router
Hooks
slim.before slim.before.router slim.before.dispatch slim.after.dispatch slim.after.router slim.after
Hooks
slim.before slim.before.router slim.before.dispatch slim.after.dispatch slim.after.router slim.after
class MyMiddleware extends \Slim\Middleware { public function call() { //The Slim application $app = $this->app; //The Environment object $env = $app->environment(); //The Request object $req = $app->request(); //The Response object $res = $app->response(); //Optionally call the next middleware $this->next->call(); } }
class MyMiddleware extends \Slim\Middleware { public function call() { //The Slim application $app = $this->app; //The Environment object $env = $app->environment(); //The Request object $req = $app->request(); //The Response object $res = $app->response(); //Optionally call the next middleware $this->next->call(); } }
Extend this
class MyMiddleware extends \Slim\Middleware { public function call() { Dene //The Slim application $app = $this->app; //The Environment object $env = $app->environment(); //The Request object $req = $app->request(); //The Response object $res = $app->response(); //Optionally call the next middleware $this->next->call(); } }
class MyMiddleware extends \Slim\Middleware { public function call() { Dene //The Slim application $app = $this->app; //The Environment object $env = $app->environment(); //The Request object $req = $app->request(); //The Response object $res = $app->response(); //Optionally call the next middleware $this->next->call(); } }
class MyMiddleware extends \Slim\Middleware { public function call() { Dene //The Slim application $app = $this->app; //The Environment object $env = $app->environment(); //The Request object $req = $app->request(); //The Response object $res = $app->response(); //Optionally call the next middleware $this->next->call(); } }
On to the next!
Navigation example
namespace Tsf\Middleware; use \Zend\Authentication\AuthenticationService; class Navigation extends \Slim\Middleware { /** * @var \Zend\Authentication\AuthenticationService */ private $auth; public function __construct(AuthenticationService $auth) { $this->auth = $auth; } public function call() { // . . . } }
extends
/** * @var \Zend\Authentication\AuthenticationService */ private $auth; public function __construct(AuthenticationService $auth) { $this->auth = $auth; } public function call() { // . . . } }
extends
/** * @var \Zend\Authentication\AuthenticationService */ private $auth; public function __construct(AuthenticationService $auth) { $this->auth = $auth; } public function call() { // . . . } }
public function call() { $app = $this->app; $auth = $this->auth; $req = $app->request(); $home = array('caption' => 'Home', 'href' => '/'); $admin = array('caption' => 'Admin', 'href' => '/admin'); $login = array('caption' => 'Login', 'href' => '/login'); $logout = array('caption' => 'Logout', 'href' => '/logout'); if ($auth->hasIdentity()) { $navigation = array($home, $admin, $logout); } else { $navigation = array($home, $login); } // . . . }
$home = array('caption' => 'Home', 'href' => '/'); $admin = array('caption' => 'Admin', 'href' => '/admin'); $login = array('caption' => 'Login', 'href' => '/login'); $logout = array('caption' => 'Logout', 'href' => '/logout'); if ($auth->hasIdentity()) { $navigation = array($home, $admin, $logout); } else { $navigation = array($home, $login); } // . . . }
$home = array('caption' => 'Home', 'href' => '/'); $admin = array('caption' => 'Admin', 'href' => '/admin'); $login = array('caption' => 'Login', 'href' => '/login'); $logout = array('caption' => 'Logout', 'href' => '/logout'); if ($auth->hasIdentity()) { $navigation = array($home, $admin, $logout); } else { $navigation = array($home, $login); } // . . . }
public function call() { // . . . $this->app->hook('slim.before.router', function () use (...) { foreach ($navigation as &$link) { if ($link['href'] == $req->getPath()) { $link['class'] = 'active'; } else { $link['class'] = ''; } } $app->view() ->appendData(array('navigation' => $navigation)); } ); $this->next->call(); }
$this->app->hook('slim.before.router', function () use (...) { foreach ($navigation as &$link) { if ($link['href'] == $req->getPath()) { $link['class'] = 'active'; } else { $link['class'] = ''; } } $app->view() ->appendData(array('navigation' => $navigation)); } ); $this->next->call(); }
$this->app->hook('slim.before.router', function () use (...) { foreach ($navigation as &$link) { if ($link['href'] == $req->getPath()) { $link['class'] = 'active'; } else { $link['class'] = ''; Match } } dispatched path $app->view() ->appendData(array('navigation' => $navigation)); } ); $this->next->call(); }
$this->app->hook('slim.before.router', function () use (...) { foreach ($navigation as &$link) { if ($link['href'] == $req->getPath()) { $link['class'] = 'active'; } else { $link['class'] = ''; Match } } dispatched path $app->view() ->appendData(array('navigation' => $navigation)); } ); $this->next->call(); }
$this->app->hook('slim.before.router', function () use (...) { foreach ($navigation as &$link) { if ($link['href'] == $req->getPath()) { $link['class'] = 'active'; } else { $link['class'] = ''; Match } } dispatched path $app->view() ->appendData(array('navigation' => $navigation)); } ); $this->next->call(); }
On to the next!
Views
Twig
Twig
Concise
Twig
Concise Template oriented
Twig
Concise Template oriented Fast
Twig
Concise Template oriented Fast Multiple inheritance
Twig
Concise Template oriented Fast Multiple inheritance Blocks
Twig
Concise Template oriented Fast Multiple inheritance Blocks Automatic escaping
layout.html
<ul class="nav"> {% for link in navigation %} <li class="{{link.class}}"> <a href="{{link.href}}">{{link.caption}}</a> </li> {% endfor %} </ul>
<h1>365 Days of Photography</h1> <h3>Photographer: Jeremy Kendall</h3> {% block content %} {% endblock %} <hr />
index.html
{% extends 'layout.html' %} {% block page_title %}365.jeremykendall.net{% endblock %} {% block content %} {% for image in images %} <div class="row"> <div class="span6"> <h2><a href="/{{image.day}}">{{image.day}}/365</a></h2> <p> <a href="/{{image.day}}"> <img src="{{image.sizes.size.5.source}}" /> </a> </p> <p>Posted {{image.posted|date("m/d/Y")}}</p> </div> </div> {% else %} <p>No images in project</p> {% endfor %} {% endblock %}
{% extends 'layout.html' %}
extends
{% block page_title %}365.jeremykendall.net{% endblock %} {% block content %} {% for image in images %} <div class="row"> <div class="span6"> <h2><a href="/{{image.day}}">{{image.day}}/365</a></h2> <p> <a href="/{{image.day}}"> <img src="{{image.sizes.size.5.source}}" /> </a> </p> <p>Posted {{image.posted|date("m/d/Y")}}</p> </div> </div> {% else %} <p>No images in project</p> {% endfor %} {% endblock %}
{% extends 'layout.html' %}
extends
{% block page_title %}365.jeremykendall.net{% endblock %} {% block content %} <title {% for image in images %} <div class="row"> <div class="span6"> <h2><a href="/{{image.day}}">{{image.day}}/365</a></h2> <p> <a href="/{{image.day}}"> <img src="{{image.sizes.size.5.source}}" /> </a> </p> <p>Posted {{image.posted|date("m/d/Y")}}</p> </div> </div> {% else %} <p>No images in project</p> {% endfor %} {% endblock %}
/>
{% extends 'layout.html' %}
extends
{% block page_title %}365.jeremykendall.net{% endblock %} {% block content %} <title {% for image in images %} <div class="row"> <div class="span6"> <h2><a href="/{{image.day}}">{{image.day}}/365</a></h2> <p> <a href="/{{image.day}}"> <img src="{{image.sizes.size.5.source}}" /> </a> </p> <p>Posted {{image.posted|date("m/d/Y")}}</p> </div> </div> {% else %} <p>No images in project</p> {% endfor %} {% endblock %}
/>
{% extends 'layout.html' %}
extends
{% block page_title %}365.jeremykendall.net{% endblock %} {% block content %} <title {% for image in images %} iterator <div class="row"> <div class="span6"> <h2><a href="/{{image.day}}">{{image.day}}/365</a></h2> <p> <a href="/{{image.day}}"> <img src="{{image.sizes.size.5.source}}" /> </a> </p> <p>Posted {{image.posted|date("m/d/Y")}}</p> </div> </div> {% else %} <p>No images in project</p> {% endfor %} {% endblock %}
/>
{% extends 'layout.html' %}
extends
{% block page_title %}365.jeremykendall.net{% endblock %} {% block content %} <title {% for image in images %} iterator <div class="row"> <div class="span6"> <h2><a href="/{{image.day}}">{{image.day}}/365</a></h2> <p> <a href="/{{image.day}}"> <img src="{{image.sizes.size.5.source}}" /> </a> </p> <p>Posted {{image.posted|date("m/d/Y")}}</p> </div> </div> else {% else %} <p>No images in project</p> {% endfor %} {% endblock %}
/>
{% extends 'layout.html' %}
extends
{% block page_title %}365.jeremykendall.net{% endblock %} {% block content %} <title {% for image in images %} iterator <div class="row"> <div class="span6"> <h2><a href="/{{image.day}}">{{image.day}}/365</a></h2> <p> <a href="/{{image.day}}"> <img src="{{image.sizes.size.5.source}}" /> </a> </p> <p>Posted {{image.posted|date("m/d/Y")}}</p> </div> </div> else {% else %} <p>No images in project</p> format {% endfor %} {% endblock %}
/>
login.html
{% extends 'layout.html' %} {% block page_title %}365.jeremykendall.net | Login{% endblock %} {% block content %} <div class="row"> <div class="span4"> <h2>Login</h2> {% if flash.error %} <p style="color: red;">{{flash.error}}</p> {% endif %} <form name="login" id="login" class="well" method="post"> // Login form . . . </form> </div> </div> {% endblock %}
{% extends 'layout.html' %} {% block page_title %}365.jeremykendall.net | Login{% endblock %} {% block content %} <div class="row"> <div class="span4"> <h2>Login</h2> {% if flash.error %} <p style="color: red;">{{flash.error}}</p> {% endif %} <form name="login" id="login" class="well" method="post"> // Login form . . . </form> </div> </div> {% endblock %}
GOTO 0
GOTO 0
GOTO 0
GOTO 0
Excellent tools to write elegant code Routing, middleware & hooks, views
GOTO 0
Excellent tools to write elegant code Routing, middleware & hooks, views I just scratched the surface
Read
Slim: slimframework.com Twig: twig.sensiolabs.org Composer: getcomposer.org MicroPHP Manifesto: microphp.org Flaming Archer: http://git.io/rH0nrg
Questions?
Thanks!
jeremy@jeremykendall.net @jeremykendall