Slide 1

Slide 1 text

Bullet: The Functional PHP Micro-Framework Vance Lucas • Co-Founder, Brightbit http://bulletphp.com

Slide 2

Slide 2 text

Who are You? • Vance Lucas • http://vancelucas.com • @vlucas (for heckling) • PHP since 1999 (PHP3) • Brightbit • http://brightbit.com • Design, Development & Consulting for web apps, mobile apps and APIs

Slide 3

Slide 3 text

History & Philosophy (Don't worry, it won't be as boring as school was)

Slide 4

Slide 4 text

MVC Frameworks • I’ve created LOTS of MVC frameworks. • They all sucked. • Except maybe one. • Alloy Framework • Released Feb. 2011 • But it’s dead to me now…

Slide 5

Slide 5 text

“The same thing, only different”

Slide 6

Slide 6 text

“Lightweight” “Simple” “Flexible” “Artisan” “Modular” “Fast”

Slide 7

Slide 7 text

“The fool hath said in his heart, There is no better architectural pattern than MVC” * may not be exact quote

Slide 8

Slide 8 text

“I don't like MVC because that's not how the web works. Symfony2 is an HTTP framework; it is a Request/Response framework. That's the big deal.” Fabien Potencier http://fabien.potencier.org/article/49/what-is-symfony2 October 25, 2011

Slide 9

Slide 9 text

Philosophy • Do more with less (code) • Low cognitive overhead/complexity • Embrace HTTP • Leverage raw PHP without introducing too many “framework concepts” • Only PHP knowledge should be enough • Shouldn’t have to “fight the framework” • “Micro” != No Structure

Slide 10

Slide 10 text

“PluginBroker” “TemplateMapResolver” “AggregateResolver” “RouteNotFoundStrategy” “TemplatePathStack” “RouteStack” “SharedEventManager” “DefaultListenerAggregate”

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

What is Bullet? Well, it’s a Micro-framework for starters…

Slide 13

Slide 13 text

Main Concepts • Micro-framework • URL Routing, Request, Response, Templates • Built around HTTP and defined URIs • Parses one URI segment at a time • Declarative, functional-style nested routing • Leverages closures for structure and scope • Less repetitive code, cleaner routes

Slide 14

Slide 14 text

Guiding Rules • Only one path segment at a time, and only Closures can be used • Response must be explicitly returned • Path must be fully consumed (or error) • Handlers for different behavior: • Path, Param, Method, Format • Method and format handlers only run when path has been fully consumed

Slide 15

Slide 15 text

Show me some code! ! GET /posts/42

Slide 16

Slide 16 text

// Bullet index file! define('BULLET_ROOT', dirname(__DIR__));! define('BULLET_APP_ROOT', BULLET_ROOT . '/app/');! define('BULLET_SRC_ROOT', BULLET_APP_ROOT . '/src/');! ! // Composer Autoloader! $loader = require BULLET_ROOT . '/vendor/autoload.php';! ! // Bullet App! $app = new Bullet\App(require BULLET_APP_ROOT . 'config.php');! $request = new Bullet\Request();! ! // Common include! require BULLET_APP_ROOT . '/common.php';! ! // Require all paths/routes! $routesDir = BULLET_APP_ROOT . '/routes/';! require $routesDir . 'index.php';! require $routesDir . 'posts.php';! require $routesDir . 'events.php';! require $routesDir . 'users.php';! ! // Response! echo $app->run($request);

Slide 17

Slide 17 text

Bullet Routing $app->path('posts', function($req) {! // Param! $this->param('int', function($req, $id) {! $post = Post::find($id);! check_user_acl_for($post);! ! // Method! $this->get(function($req) use($post) {! ! ! // Format! ! ! ! ! ! ! $this->format('json', function() use($post) {! ! ! ! ! ! ! ! ! return $post->toArray();! ! ! ! ! ! ! });! ! ! ! ! ! ! $this->format('html', function() use($post) {! ! ! ! ! ! ! ! ! return $this->template('html', …);! ! ! ! ! ! ! });! });! });! });

Slide 18

Slide 18 text

Quick Code Comparison

Slide 19

Slide 19 text

Typical Micro-Framework $app->get('/posts/:id', function($id) use($app) {! $post = Post::find($id);! check_user_acl_for($post);! ! if(is_json()) {! header("Content-Type: application/json");! echo json_encode($result);! exit;! }! ! $app->render('posts/view', compact('post'));! });!

Slide 20

Slide 20 text

Typical MVC Controller class BlogController extends BaseController {! public function getView($slug)! {! // Get this blog post data! $post = $this->post->where('slug', '=', $slug)->first();! ! // Check if the blog post exists! if (is_null($post)) {! return App::abort(404);! }! ! // Show the page! return View::make('site/blog/view_post', compact('post', 'comments', 'canComment'));! }! }

Slide 21

Slide 21 text

Bullet Closure Context $app->path('posts', function($req) {! $this->param('int', function($req, $id) {! $post = Post::find($id);! check_user_acl_for($post);! ! // View (GET)! $this->get(function($req) use($post) {! // ...! });! ! // Delete! $this->delete(function($req) use($post) {! $post->delete();! // ...! });! });! });

Slide 22

Slide 22 text

Bullet Route Handlers

Slide 23

Slide 23 text

Path Handlers $app->resource('posts', function($request) {! // ...! });! ! $app->path('posts', function($request) {! // ...! });! ! $app->path(['posts', 'articles'], function($req) {! // ...! });

Slide 24

Slide 24 text

Path Handlers • Return 404 File Not Found if request path not found • Can be nested as deep as you want • /admin/articles/3/comments

Slide 25

Slide 25 text

Param Handlers $app->param('int', function($request, $id) {! // ...! });! ! $app->param('slug', function($request, $slug) {! // ...! });! ! // CUSTOM alphanumeric handler (returns boolean)! $app->registerParamType('alphanum',function($value) {! return ctype_alnum($value);! });! $app->param('alphanum', function($request, $alnum) {! // ...! });

Slide 26

Slide 26 text

Param Handlers • Test function • true or scalar value executes route • false skips route • Value passed in as extra parameter to handler closure

Slide 27

Slide 27 text

Method Handlers $app->resource('articles', function($request) {! $this->get(function($request) {! // ...! });! ! $this->post(function($request) {! // ...! });! ! $this->delete(function($request) {! // ...! });! });

Slide 28

Slide 28 text

Method Handlers • Return 405 Method Not Allowed if request method not found

Slide 29

Slide 29 text

Format Handlers $app->resource('articles', function($request) {! $this->get(function($request) {! $this->format(‘json', function($request) {! // ...! });! $this->format(‘html', function($request) {! // ...! });! });! });

Slide 30

Slide 30 text

Format Handlers • Return 406 Not Acceptable if request format not found

Slide 31

Slide 31 text

Other Handlers $app->domain(‘vancelucas.com', function($request) {! // ...! });! ! $app->subdomain(‘api', function($request) {! // ...! });

Slide 32

Slide 32 text

Return Types • String (“hello world”) • Integer (201 - Sends HTTP status code) • Boolean False (404 error) • Array (auto json_encode + headers) • Bullet\Response instance • Custom obj. (w/custom response handler)

Slide 33

Slide 33 text

Building the URL you want should be easy

Slide 34

Slide 34 text

$app->path('admin', function($req) use($app) {! some_acl_check__that_throws_exception_if_not();! ! require 'posts.php'; // For /admin/posts ...! require 'events.php'; // For /admin/events ...! require 'comments.php'; // For /admin/comments ...! });

Slide 35

Slide 35 text

// RELATIVE url! // /posts/25/comments/57,! // /events/9/comments/57,! // /comments/57! echo $app->url('./comments/' . $comment->id);! ! // ROOT url (always /comments/57)! echo $app->url('/comments/' . $comment->id);! …And Links Too

Slide 36

Slide 36 text

Recommended Setup http://bulletphp.com/docs/organization/

Slide 37

Slide 37 text

Events • Global: ‘before’, and ‘after’ • Dynamic • [http_status_code] - 404, 500, etc. • [response_format] - json, html, etc. • [exception_class] - exception class name like “InvalidArgumentException” or just “Exception” to catch all exceptions

Slide 38

Slide 38 text

HTTP Error Handling $app->on(404, function($req, $res){! $response->content($app->template('errors/404'));! });!

Slide 39

Slide 39 text

Exception Handling $app->on('Exception', function($req, $res, \Exception $e) {! if($req->format() === 'json') {! $data = array(! 'exception' => get_class($e),! 'message' => $e->getMessage()! );! if(BULLET_ENV !== 'production') {! $data['file'] = $e->getFile();! $data['line'] = $e->getLine();! $data['trace'] = $e->getTrace();! }! ! } else {! $data = $app->template('errors/exception', ['e' => $e]);! }! $res->content($data);! });!

Slide 40

Slide 40 text

Nested Sub Requests $app = new Bullet\App();! $app->path('foo', function($request) {! return "foo";! });! $app->path('bar', function($request) {! $res = $this->run('GET', '/foo'); // `Bullet\Response`! return $res->content() . 'bar';! });! echo $app->run('GET', 'bar'); // output => 'foobar'!

Slide 41

Slide 41 text

Getting Started http://bulletphp.com ! https://github.com/vlucas/bulletphp ! Skeleton App (basic setup / starting point) https://github.com/vlucas/bulletphp-skeleton ! Obligatory blog example https://github.com/vlucas/bulletphp-blog-example

Slide 42

Slide 42 text

MVC Framework Anti-Patterns Some more controversial than others

Slide 43

Slide 43 text

“REST Controller” vs “Base Controller”

Slide 44

Slide 44 text

Can’t use basic PHP knowledge to change the flow of your application

Slide 45

Slide 45 text

! $this->forward('someOtherAction' . $params);!

Slide 46

Slide 46 text

$this->beforeFilter('auth', array(! 'except' => 'getLogin'! ));!

Slide 47

Slide 47 text

/:controller/:action/:id

Slide 48

Slide 48 text

Symfony/Component/HttpFoundation/Response.php HttpFoundation Component Docs $response->setStatusCode(Response::HTTP_NOT_FOUND);! ! class Response {! // ...! const HTTP_CONTINUE = 100;! const HTTP_SWITCHING_PROTOCOLS = 101;! const HTTP_PROCESSING = 102;! const HTTP_OK = 200;! const HTTP_CREATED = 201;! const HTTP_ACCEPTED = 202;! const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;! const HTTP_NO_CONTENT = 204;! // ...! }!

Slide 49

Slide 49 text

Zend Framework 2 - Zend/Http/Response.php class Response {! // ...! const STATUS_CODE_CUSTOM = 0;! const STATUS_CODE_100 = 100;! const STATUS_CODE_101 = 101;! const STATUS_CODE_102 = 102;! const STATUS_CODE_200 = 200;! const STATUS_CODE_201 = 201;! const STATUS_CODE_202 = 202;! const STATUS_CODE_203 = 203;! const STATUS_CODE_204 = 204;! // ...! }!

Slide 50

Slide 50 text

Classes for Controllers

Slide 51

Slide 51 text

Questions? @vlucas | [email protected] ! ! Rate this talk! https://joind.in/10434