Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bullet: The Functional PHP Micro-Framework

94cb827736e36e0f6343e9640e72fec7?s=47 vlucas
January 17, 2014

Bullet: The Functional PHP Micro-Framework

94cb827736e36e0f6343e9640e72fec7?s=128

vlucas

January 17, 2014
Tweet

Transcript

  1. Bullet: The Functional PHP Micro-Framework Vance Lucas • Co-Founder, Brightbit

    http://bulletphp.com
  2. 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
  3. History & Philosophy (Don't worry, it won't be as boring

    as school was)
  4. 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…
  5. “The same thing, only different”

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

  7. “The fool hath said in his heart, There is no

    better architectural pattern than MVC” * may not be exact quote
  8. “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
  9. 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
  10. “PluginBroker” “TemplateMapResolver” “AggregateResolver” “RouteNotFoundStrategy” “TemplatePathStack” “RouteStack” “SharedEventManager” “DefaultListenerAggregate”

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

  13. 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
  14. 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
  15. Show me some code! ! GET /posts/42

  16. // 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);
  17. 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', …);! ! ! ! ! ! ! });! });! });! });
  18. Quick Code Comparison

  19. 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'));! });!
  20. 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'));! }! }
  21. 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();! // ...! });! });! });
  22. Bullet Route Handlers

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

    function($request) {! // ...! });! ! $app->path(['posts', 'articles'], function($req) {! // ...! });
  24. Path Handlers • Return 404 File Not Found if request

    path not found • Can be nested as deep as you want • /admin/articles/3/comments
  25. 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) {! // ...! });
  26. Param Handlers • Test function • true or scalar value

    executes route • false skips route • Value passed in as extra parameter to handler closure
  27. Method Handlers $app->resource('articles', function($request) {! $this->get(function($request) {! // ...! });!

    ! $this->post(function($request) {! // ...! });! ! $this->delete(function($request) {! // ...! });! });
  28. Method Handlers • Return 405 Method Not Allowed if request

    method not found
  29. Format Handlers $app->resource('articles', function($request) {! $this->get(function($request) {! $this->format(‘json', function($request) {!

    // ...! });! $this->format(‘html', function($request) {! // ...! });! });! });
  30. Format Handlers • Return 406 Not Acceptable if request format

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

    function($request) {! // ...! });
  32. 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)
  33. Building the URL you want should be easy

  34. $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 ...! });
  35. // 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
  36. Recommended Setup http://bulletphp.com/docs/organization/

  37. 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
  38. HTTP Error Handling $app->on(404, function($req, $res){! $response->content($app->template('errors/404'));! });!

  39. 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);! });!
  40. 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'!
  41. 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
  42. MVC Framework Anti-Patterns Some more controversial than others

  43. “REST Controller” vs “Base Controller”

  44. Can’t use basic PHP knowledge to change the flow of

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

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

  47. /:controller/:action/:id

  48. 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;! // ...! }!
  49. 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;! // ...! }!
  50. Classes for Controllers

  51. Questions? @vlucas | vance@vancelucas.com ! ! Rate this talk! https://joind.in/10434