keeping it small: getting to know the slim micro framework

Post on 18-May-2015

1.118 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Keeping it smallGetting to know the Slim micro framework

@JeremyKendall

Jeremy Kendall

Jeremy Kendall

I love to code

Jeremy Kendall

I love to code

I’m terribly forgetful

Jeremy Kendall

I love to code

I’m terribly forgetful

I take pictures

Jeremy Kendall

I love to code

I’m 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 ;-)

Don’t forget .htaccess!

RewriteEngine OnRewriteCond %{REQUEST_FILENAME} !-fRewriteRule ^ 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();

Let’s look at a Slim application

Flaming Archer

Flaming Archer

wat

“Great repository names are short and memorable. Need inspiration? How about flaming-archer.”

Flaming Archer

Flaming ArcherPhoto 365 project

Flaming ArcherPhoto 365 project

Built in 4 days (Saturday through Tuesday)

Flaming ArcherPhoto 365 project

Built in 4 days (Saturday through Tuesday)

Basic application — a few bells, no whistles

Flaming ArcherPhoto 365 project

Built in 4 days (Saturday through Tuesday)

Basic application — a few bells, no whistles

Routing

Flaming ArcherPhoto 365 project

Built in 4 days (Saturday through Tuesday)

Basic application — a few bells, no whistles

Routing

Twig views

Flaming ArcherPhoto 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: 7Files: 13

Lines of Code (LOC): 876 Cyclomatic Complexity / Lines of Code: 0.04Comment Lines of Code (CLOC): 272Non-Comment Lines of Code (NCLOC): 604

Configuration

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' ) ) ), 'twig' => array( // . . . ), 'cookies' => array( // . . . ), 'flickr.api.key' => 'FLICKR API KEY', 'pdo' => array( // . . . ));

Slim

Views

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

Views

Cookies

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

Views

Cookies

My stuff

$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']);

Config array goes here

Routing

Routing

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

HTTP Method

Routing

$app->get('/', function () use ($app, $service) { $images = $service->findAll(); $app->render('index.html', array('images' => $images)); });

HTTP Method Resource URI

Routing

$app->get('/', function () use ($app, $service) { $images = $service->findAll(); $app->render('index.html', array('images' => $images)); });

HTTP Method Resource URI Anonymous Function

Routing

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

Grabs all the pics

Routing

$app->get('/', function () use ($app, $service) { $images = $service->findAll(); $app->render('index.html', array('images' => $images)); });

Grabs all the pics

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])'));

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... gets passed as an

argument

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... gets passed as an

argument

Condition

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... gets passed as an

argument

Condition 1 to 366

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])'));

404!

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

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

Not an HTTP Method

Multiple methods

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

Not an HTTP Method

via() is the awesome sauce

Logging and flash 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(); $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'); });

Get the log from $app

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

Get the log from $app

Error!

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

Get the log from $app

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

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

Hooks

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

Define 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

Define call()

Inspect, analyze, and modify!

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

Define call()

On to the next!

Inspect, analyze, and modify!

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{

/** * @var \Zend\Authentication\AuthenticationService */ private $auth;

public function __construct(AuthenticationService $auth) { $this->auth = $auth; }

public function call() { // . . . }

}

extends

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

}

Constructor injection FTW

extends

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

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

// . . .}

Arrays of nav items

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

// . . .}

Arrays of nav items

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

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

Delicious hook goodness

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

Delicious hook goodness

Match dispatched path

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

Delicious hook goodness

Match dispatched path

Append $navigation to

view

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

Delicious hook goodness

Match dispatched path

Append $navigation to

viewOn to the next!

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

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

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

<title />

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

<title />

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

<title />iterator

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

<title />iterator

else

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

<title />iterator

elseformat

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 viewswould be redundant

GOTO 0

Small but powerfulGOTO 0

Small but powerful

Excellent tools to write elegant code

GOTO 0

Small but powerful

Excellent tools to write elegant code

Routing, middleware & hooks, views

GOTO 0

Small but powerful

Excellent tools to write elegant code

Routing, middleware & hooks, views

I just scratched the surface

GOTO 0

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

top related