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

Bullet: The Functional PHP Micro-Framework

vlucas
January 17, 2014

Bullet: The Functional PHP Micro-Framework

vlucas

January 17, 2014
Tweet

More Decks by vlucas

Other Decks in Programming

Transcript

  1. 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
  2. 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…
  3. “The fool hath said in his heart, There is no

    better architectural pattern than MVC” * may not be exact quote
  4. “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
  5. 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
  6. 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
  7. 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
  8. // 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);
  9. 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', …);! ! ! ! ! ! ! });! });! });! });
  10. 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'));! });!
  11. 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'));! }! }
  12. 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();! // ...! });! });! });
  13. Path Handlers $app->resource('posts', function($request) {! // ...! });! ! $app->path('posts',

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

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

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

    ! $this->post(function($request) {! // ...! });! ! $this->delete(function($request) {! // ...! });! });
  18. 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)
  19. $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 ...! });
  20. // 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
  21. 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
  22. 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);! });!
  23. 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'!
  24. 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
  25. 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;! // ...! }!
  26. 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;! // ...! }!