You are on page 1of 135

Keeping it small

Getting to know the Slim micro framework


@JeremyKendall

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

Dont forget .htaccess!


RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [QSA,L]

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();

Lets look at a Slim application

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

$config = require_once __DIR__ . '/../config.php'; // Prepare app $app = new Slim\Slim($config['slim']);

$config = require_once __DIR__ . '/../config.php'; // Prepare app $app = new Slim\Slim($config['slim']);

Cong array goes here

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)); } );

Passes array of image data to index.html

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

... gets passed as an argument

$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

... gets passed as an argument

$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

... gets passed as an argument

$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!

$app->render('images.html', $image); } )->conditions(array('day' => '([1-9]\d?|[12]\d\d|3[0-5]\d|36[0-6])'));

POST (with redirect)


$app->post('/admin/add-photo', function() use ($app, $service) { $data = $app->request()->post(); $service->save($data); $app->redirect('/admin'); } );

POST (with redirect)


$app->post('/admin/add-photo', function() use ($app, $service) { $data = $app->request()->post(); $service->save($data); $app->redirect('/admin'); $_POST data is in } the request object );

POST (with redirect)


$app->post('/admin/add-photo', function() use ($app, $service) { $data = $app->request()->post(); $service->save($data); $app->redirect('/admin'); $_POST data is in } the request object );

302 Redirect

Multiple methods
$app->map('/login', function() { // Login } )->via('GET', 'POST');

Multiple methods
Not an HTTP Method

$app->map('/login', function() { // Login } )->via('GET', 'POST');

Multiple methods
Not an HTTP Method

$app->map('/login', function() { // Login } )->via('GET', 'POST'); via() is the awesome sauce

Logging and ash messaging

$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!

Flash message available in the next request.

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(); } }

Extend this 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(); } }

Extend this call()

Inspect, analyze, and modify!

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(); } }

Extend this call()

Inspect, analyze, and modify!

On to the next!

Middleware + Hooks = WIN

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() { // . . . } }

namespace Tsf\Middleware; use \Zend\Authentication\AuthenticationService; class Navigation extends \Slim\Middleware {

extends

/** * @var \Zend\Authentication\AuthenticationService */ private $auth; public function __construct(AuthenticationService $auth) { $this->auth = $auth; } public function call() { // . . . } }

namespace Tsf\Middleware; use \Zend\Authentication\AuthenticationService; class Navigation extends \Slim\Middleware {

extends

/** * @var \Zend\Authentication\AuthenticationService */ private $auth; public function __construct(AuthenticationService $auth) { $this->auth = $auth; } public function call() { // . . . } }

Constructor injection FTW

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); } // . . . }

public function call() { $app = $this->app; $auth = $this->auth; $req = $app->request();

Arrays of nav items

$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() { $app = $this->app; $auth = $this->auth; $req = $app->request();

Arrays of nav items

$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); } // . . . }

Nav differs based on auth status

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(); }

public function call() { // . . .

Delicious hook goodness

$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(); }

public function call() { // . . .

Delicious hook goodness

$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(); }

public function call() { // . . .

Delicious hook goodness

$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(); }

Append $navigation to view

public function call() { // . . .

Delicious hook goodness

$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!

Append $navigation to view

Views

Two great tastes that taste great together

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 and index.html

layout.html

<title>{% block page_title %} {% endblock %}</title>

<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 %}

The other views would be redundant

GOTO 0

Small but powerful

GOTO 0

Small but powerful

GOTO 0

Excellent tools to write elegant code

Small but powerful

GOTO 0

Excellent tools to write elegant code Routing, middleware & hooks, views

Small but powerful

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

You might also like