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

http://bulletphp.com
Who are You?
• Vance Lucas	

• http://vancelucas.com	

• @vlucas (for heckling)	

• PHP since 1999 (PHP3)	

• Brightbit	

...
History & Philosophy	

(Don't worry, it won't be as boring as school was)
MVC Frameworks
• I’ve created LOTS of MVC frameworks.	

• They all sucked.	

• Except maybe one.	

• Alloy Framework
• Rel...
“The same thing, only
different”
“Lightweight”

“Flexible”

“Fast”

“Simple”
“Modular”

“Artisan”
“The fool hath said in
his heart, There is no
better architectural
pattern than MVC”
* may not be exact quote
“I don't like MVC because that's
not how the web works.
Symfony2 is an HTTP framework;
it is a Request/Response
framework....
Philosophy
• Do more with less (code)	

• Low cognitive overhead/complexity	

• Embrace HTTP	

• Leverage raw PHP without ...
“SharedEventManager”
“PluginBroker”

“RouteStack”

“TemplateMapResolver”
“AggregateResolver”
“DefaultListenerAggregate”
“R...
What is Bullet?
Well, it’s a Micro-framework for starters…
Main Concepts
• Micro-framework	

•

URL Routing, Request, Response, Templates	


• Built around HTTP and defined URIs	

• ...
Guiding Rules
• Only one path segment at a time, and only
Closures can be used	


• Response must be explicitly returned	
...
Show me some code!	

!

GET /posts/42
// Bullet index file!
define('BULLET_ROOT', dirname(__DIR__));!
define('BULLET_APP_ROOT', BULLET_ROOT . '/app/');!
define(...
Bullet Routing	

$app->path('posts', function($req) {!
// Param!
$this->param('int', function($req, $id) {!
$post = Post::...
Quick Code
Comparison
Typical Micro-Framework
$app->get('/posts/:id', function($id) use($app) {!
$post = Post::find($id);!
check_user_acl_for($p...
Typical MVC Controller
class BlogController extends BaseController {!
public function getView($slug)!
{!
// Get this blog ...
Bullet Closure Context	

$app->path('posts', function($req) {!
$this->param('int', function($req, $id) {!
$post = Post::fi...
Bullet Route Handlers
Path Handlers
$app->resource('posts', function($request) {!
// ...!
});!
!

$app->path('posts', function($request) {!
// ....
Path Handlers
• Return 404 File Not Found if request path
not found	


• Can be nested as deep as you want	

• /admin/arti...
Param Handlers
$app->param('int', function($request, $id) {!
// ...!
});!
!

$app->param('slug', function($request, $slug)...
Param Handlers
• Test function	

• true or scalar value executes route	

• false skips route	

• Value passed in as extra ...
Method Handlers
$app->resource('articles', function($request) {!
$this->get(function($request) {!
// ...!
});!
!

$this->p...
Method Handlers

• Return 405 Method Not Allowed if
request method not found
Format Handlers
$app->resource('articles', function($request) {!
$this->get(function($request) {!
$this->format(‘json', fu...
Format Handlers

• Return 406 Not Acceptable if request
format not found
Other Handlers
$app->domain(‘vancelucas.com', function($request) {!
// ...!
});!
!

$app->subdomain(‘api', function($reque...
Return Types
• String (“hello world”)	

• Integer (201 - Sends HTTP status code)	

• Boolean False (404 error)	

• Array (...
Building the URL you
want should be easy
$app->path('admin', function($req) use($app) {!
some_acl_check__that_throws_exception_if_not();!
!

require 'posts.php'; /...
…And Links Too
// RELATIVE url!
// /posts/25/comments/57,!
// /events/9/comments/57,!
// /comments/57!
echo $app->url('./c...
Recommended Setup

http://bulletphp.com/docs/organization/
Events
• Global: ‘before’, and ‘after’	

• Dynamic	

• [http_status_code] - 404, 500, etc.	

• [response_format] - json, h...
HTTP Error Handling

$app->on(404, function($req, $res){!
$response->content($app->template('errors/404'));!
});!
Exception Handling
$app->on('Exception', function($req, $res, Exception $e) {!
if($req->format() === 'json') {!
$data = ar...
Nested Sub Requests
$app = new BulletApp();!
$app->path('foo', function($request) {!
return "foo";!
});!
$app->path('bar',...
Getting Started
http://bulletphp.com	

!

https://github.com/vlucas/bulletphp	

!

Skeleton App (basic setup / starting po...
MVC Framework	

Anti-Patterns
Some more controversial than others
“REST Controller”	

vs	

“Base Controller”
Can’t use basic PHP
knowledge to change
the flow of your
application
!

$this->forward('someOtherAction' . $params);!
$this->beforeFilter('auth', array(!
'except' => 'getLogin'!
));!
/:controller/:action/:id
$response->setStatusCode(Response::HTTP_NOT_FOUND);!
!

class Response {!
// ...!
const HTTP_CONTINUE = 100;!
const HTTP_S...
class Response {!
// ...!
const STATUS_CODE_CUSTOM = 0;!
const STATUS_CODE_100 = 100;!
const STATUS_CODE_101 = 101;!
const...
Classes for Controllers
Questions?
@vlucas | vance@vancelucas.com	

!
!

Rate this talk!	

https://joind.in/10434
Bullet: The Functional PHP Micro-Framework
Upcoming SlideShare
Loading in …5
×

Bullet: The Functional PHP Micro-Framework

26,434 views

Published on

Published in: Technology
1 Comment
3 Likes
Statistics
Notes
  • Bullet is also a great framework, suitable for creating micro apps. No wonder why it is often counted among top PHP micro frameworks (https://www.cloudways.com/blog/install-php-microframeworks-on-cloud-server/ ), like Lumen and Slim.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
26,434
On SlideShare
0
From Embeds
0
Number of Embeds
88
Actions
Shares
0
Downloads
29
Comments
1
Likes
3
Embeds 0
No embeds

No notes for slide

Bullet: The Functional PHP Micro-Framework

  1. 1. Bullet: The Functional PHP Micro-Framework Vance Lucas • Co-Founder, Brightbit http://bulletphp.com
  2. 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. 3. History & Philosophy (Don't worry, it won't be as boring as school was)
  4. 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. 5. “The same thing, only different”
  6. 6. “Lightweight” “Flexible” “Fast” “Simple” “Modular” “Artisan”
  7. 7. “The fool hath said in his heart, There is no better architectural pattern than MVC” * may not be exact quote
  8. 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. 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. 10. “SharedEventManager” “PluginBroker” “RouteStack” “TemplateMapResolver” “AggregateResolver” “DefaultListenerAggregate” “RouteNotFoundStrategy” “TemplatePathStack”
  11. 11. What is Bullet? Well, it’s a Micro-framework for starters…
  12. 12. 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
  13. 13. 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
  14. 14. Show me some code! ! GET /posts/42
  15. 15. // 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 BulletApp(require BULLET_APP_ROOT . 'config.php');! $request = new BulletRequest();! ! // 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);
  16. 16. 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', …);! ! ! });! });!
  17. 17. Quick Code Comparison
  18. 18. 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'));! });!
  19. 19. 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'));! }! }
  20. 20. 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();! // ...! });! });! });
  21. 21. Bullet Route Handlers
  22. 22. Path Handlers $app->resource('posts', function($request) {! // ...! });! ! $app->path('posts', function($request) {! // ...! });! ! $app->path(['posts', 'articles'], function($req) {! // ...! });
  23. 23. Path Handlers • Return 404 File Not Found if request path not found • Can be nested as deep as you want • /admin/articles/3/comments
  24. 24. 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) {! // ...! });
  25. 25. Param Handlers • Test function • true or scalar value executes route • false skips route • Value passed in as extra parameter to handler closure
  26. 26. Method Handlers $app->resource('articles', function($request) {! $this->get(function($request) {! // ...! });! ! $this->post(function($request) {! // ...! });! ! $this->delete(function($request) {! // ...! });! });
  27. 27. Method Handlers • Return 405 Method Not Allowed if request method not found
  28. 28. Format Handlers $app->resource('articles', function($request) {! $this->get(function($request) {! $this->format(‘json', function($request) {! // ...! });! $this->format(‘html', function($request) {! // ...! });! });! });
  29. 29. Format Handlers • Return 406 Not Acceptable if request format not found
  30. 30. Other Handlers $app->domain(‘vancelucas.com', function($request) {! // ...! });! ! $app->subdomain(‘api', function($request) {! // ...! });
  31. 31. Return Types • String (“hello world”) • Integer (201 - Sends HTTP status code) • Boolean False (404 error) • Array (auto json_encode + headers) • BulletResponse instance • Custom obj. (w/custom response handler)
  32. 32. Building the URL you want should be easy
  33. 33. $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 ...! });
  34. 34. …And Links Too // 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);!
  35. 35. Recommended Setup http://bulletphp.com/docs/organization/
  36. 36. 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
  37. 37. HTTP Error Handling $app->on(404, function($req, $res){! $response->content($app->template('errors/404'));! });!
  38. 38. 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);! });!
  39. 39. Nested Sub Requests $app = new BulletApp();! $app->path('foo', function($request) {! return "foo";! });! $app->path('bar', function($request) {! $res = $this->run('GET', '/foo'); // `BulletResponse`! return $res->content() . 'bar';! });! echo $app->run('GET', 'bar'); // output => 'foobar'!
  40. 40. 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
  41. 41. MVC Framework Anti-Patterns Some more controversial than others
  42. 42. “REST Controller” vs “Base Controller”
  43. 43. Can’t use basic PHP knowledge to change the flow of your application
  44. 44. ! $this->forward('someOtherAction' . $params);!
  45. 45. $this->beforeFilter('auth', array(! 'except' => 'getLogin'! ));!
  46. 46. /:controller/:action/:id
  47. 47. $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;! // ...! }! HttpFoundation Component Docs Symfony/Component/HttpFoundation/Response.php
  48. 48. 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;! // ...! }! Zend Framework 2 - Zend/Http/Response.php
  49. 49. Classes for Controllers
  50. 50. Questions? @vlucas | vance@vancelucas.com ! ! Rate this talk! https://joind.in/10434

×