A resource-oriented micro PHP framework

Overview

Bullet

Bullet is a resource-oriented micro PHP framework built around HTTP URIs. Bullet takes a unique functional-style approach to URL routing by parsing each path part independently and one at a time using nested closures. The path part callbacks are nested to produce different responses and to follow and execute deeper paths as paths and parameters are matched.

Build Status

PROJECT MAINTENANCE RESUMES

Bullet becomes an active project again. Currently there's a changing of the guard. Feel free to further use and contribute to the framework.

Requirements

  • PHP 5.6+ (PHP 7.1 recommended)
  • Composer for all package management and autoloading (may require command-line access)

Rules

  • Apps are built around HTTP URIs and defined paths, not forced MVC (but MVC-style separation of concerns is still highly recommenended and encouraged)
  • Bullet handles one segment of the path at a time, and executes the callback for that path segment before proceesing to the next segment (path callbacks are executed from left to right, until the entire path is consumed).
  • If the entire path cannot be consumed, a 404 error will be returned (note that some callbacks may have been executed before Bullet can know this due to the nature of callbacks and closures). Example: path /events/45/edit may return a 404 because there is no edit path callback, but paths events and 45 would have already been executed before Bullet can know to return a 404. This is why all your primary logic should be contained in get, post, or other method callbacks or in the model layer (and not in the bare path handlers).
  • If the path can be fully consumed, and HTTP method handlers are present in the path but none are matched, a 405 "Method Not Allowed" response will be returned.
  • If the path can be fully consumed, and format handlers are present in the path but none are matched, a 406 "Not Acceptable" response will be returned.

Advantages

  • Super flexible routing. Because of the way the routing callbacks are nested, Bullet's routing system is one of the most flexible of any other PHP framework or library. You can build any URL you want and respond to any HTTP method on that URL. Routes are not restricted to specific patterns or URL formats, and do not require a controller with specific method names to respond to specific HTTP methods. You can nest routes as many levels deep as you want to expose nested resources like posts/42/comments/943/edit with a level of ease not found in most other routing libraries or frameworks.

  • Reduced code duplication (DRY). Bullet takes full advantage of its nested closure routing system to reduce a lot of typical code duplication required in most other frameworks. In a typical MVC framework controller, some code has to be duplicated across methods that perform CRUD operations to run ACL checks and load required resources like a Post object to view, edit or delete. With Bullet's nested closure style, this code can be written just once in a path or param callback, and then you can use the loaded object in subsequent path, param, or HTTP method handlers. This eliminates the need for "before" hooks and filters, because you can just run the checks and load objects you need before you define other nested paths and use them when required.

Installing with Composer

Use the basic usage guide, or follow the steps below:

Setup your composer.json file at the root of your project

{
    "require": {
        "vlucas/bulletphp": "~1.7"
    }
}

Install Composer

curl -s http://getcomposer.org/installer | php

Install Dependencies (will download Bullet)

php composer.phar install

Create index.php (use the minimal example below to get started)

<?php
require __DIR__ . '/vendor/autoload.php';

/* Simply build the application around your URLs */
$app = new Bullet\App();

$app->path('/', function($request) {
    return "Hello World!";
});
$app->path('/foo', function($request) {
    return "Bar!";
}); 

/* Run the app! (takes $method, $url or Bullet\Request object)
 * run() always return a \Bullet\Response object (or throw an exception) */

$app->run(new Bullet\Request())->send();

This application can be placed into your server's document root. (Make sure it is correctly configured to serve php applications.) If index.php is in the document root on your local host, the application may be called like this:

http://localhost/index.php?u=/

and

http://localhost/index.php?u=/foo

If you're using Apache, use an .htaccess file to beautify the URLs. You need mod_rewrite to be installed and enabled.

<IfModule mod_rewrite.c>
  RewriteEngine On

  # Reroute any incoming requestst that is not an existing directory or file
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ index.php?u=$1 [L,QSA,B]
</IfModule>

With this file in place Apache will pass the request URI to index.php using the $_GET['u'] parameter. This works in subdirectories as expected i.e. you don't have to explicitly take care of removing the path prefix e.g. if you use mod_userdir, or just install a Bullet application under an existing web app to serve an API or simple, quick dynamic pages. Now your application will answer to these pretty urls:

http://localhost/

and

http://localhost/foo

NGinx also has a rewrite command, and can be used to the same end:

server {
    # ...
    location / {
        # ...
        rewrite ^/(.*)$ /index.php?u=/$1;
        try_files $uri $uri/ =404;
        # ...
    }
    # ...
}

If the Bullet application is inside a subdirectory, you need to modify the rewrite line to serve it correctly:

server {
    # ...
    location / {
        rewrite ^/bulletapp/(.*)$ /bulletapp/index.php?u=/$1;
        try_files $uri $uri/ =404;
    }
    # ...
}

Note that if you need to serve images, stylesheets, or javascript too, you need to add a location for the static root directory without the reqrite to avoid passing those URLs to index.php.

View it in your browser!

Syntax

Bullet is not your typical PHP micro framework. Instead of defining a full path pattern or a typical URL route with a callback and parameters mapped to a REST method (GET, POST, etc.), Bullet parses only ONE URL segment at a time, and only has two methods for working with paths: path and param. As you may have guessed, path is for static path names like "blog" or "events" that won't change, and param is for variable path segments that need to be captured and used, like "42" or "my-post-title". You can then respond to paths using nested HTTP method callbacks that contain all the logic for the action you want to perform.

This type of unique callback nesting eliminates repetitive code for loading records, checking authentication, and performing other setup work found in typical MVC frameworks or other microframeworks where each callback or action is in a separate scope or controller method.

$app = new Bullet\App(array(
    'template.cfg' => array('path' => __DIR__ . '/templates')
));

// 'blog' subdirectory
$app->path('blog', function($request) use($app) {

    $blog = somehowGetBlogMapper(); // Your ORM or other methods here

    // 'posts' subdirectory in 'blog' ('blog/posts')
    $app->path('posts', function() use($app, $blog) {

        // Load posts once for handling by GET/POST/DELETE below
        $posts = $blog->allPosts(); // Your ORM or other methods here

        // Handle GET on this path
        $app->get(function() use($posts) {
            // Display all $posts
            return $app->template('posts/index', compact('posts'));
        });

        // Handle POST on this path
        $app->post(function() use($posts) {
            // Create new post
            $post = new Post($request->post());
            $mapper->save($post);
            return $this->response($post->toJSON(), 201);
        });

        // Handle DELETE on this path
        $app->delete(function() use($posts) {
            // Delete entire posts collection
            $posts->deleteAll();
            return 200;
        });

    });
});

// Run the app and echo the response
echo $app->run("GET", "blog/posts");

Capturing Path Parameters

Perhaps the most compelling use of URL routing is to capture path segments and use them as parameters to fetch items from a database, like /posts/42 and /posts/42/edit. Bullet has a special param handler for this that takes two arguments: a test callback that validates the parameter type for use, and and a Closure callback. If the test callback returns boolean false, the closure is never executed, and the next path segment or param is tested. If it returns boolean true, the captured parameter is passed to the Closure as the second argument.

Just like regular paths, HTTP method handlers can be nested inside param callbacks, as well as other paths, more parameters, etc.

$app = new Bullet\App(array(
    'template.cfg' => array('path' => __DIR__ . '/templates')
));
$app->path('posts', function($request) use($app) {
    // Integer path segment, like 'posts/42'
    $app->param('int', function($request, $id) use($app) {
        $app->get(function($request) use($id) {
            // View post
            return 'view_' . $id;
        });
        $app->put(function($request) use($id) {
            // Update resource
            $post->data($request->post());
            $post->save();
            return 'update_' . $id;
        });
        $app->delete(function($request) use($id) {
            // Delete resource
            $post->delete();
            return 'delete_' . $id;
        });
    });
    // All printable characters except space
    $app->param('ctype_graph', function($request, $slug) use($app) {
        return $slug; // 'my-post-title'
    });
});

// Results of above code
echo $app->run('GET',   '/posts/42'); // 'view_42'
echo $app->run('PUT',   '/posts/42'); // 'update_42'
echo $app->run('DELETE', '/posts/42'); // 'delete_42'

echo $app->run('DELETE', '/posts/my-post-title'); // 'my-post-title'

Returning JSON (Useful for PHP JSON APIs)

Bullet has built-in support for returning JSON responses. If you return an array from a route handler (callback), Bullet will assume the response is JSON and automatically json_encode the array and return the HTTP response with the appropriate Content-Type: application/json header.

$app->path('/', function($request) use($app) {
    $app->get(function($request) use($app) {
        // Links to available resources for the API
        $data = array(
            '_links' => array(
                'restaurants' => array(
                    'title' => 'Restaurants',
                    'href' => $app->url('restaurants')
                ),
                'events' => array(
                    'title' => 'Events',
                    'href' => $app->url('events')
                )
            )
        );

        // Format responders
        $app->format('json', function($request), use($app, $data) {
            return $data; // Auto json_encode on arrays for JSON requests
        });
        $app->format('xml', function($request), use($app, $data) {
            return custom_function_convert_array_to_xml($data);
        });
        $app->format('html', function($request), use($app, $data) {
            return $app->template('index', array('links' => $data));
        });
    });
});

HTTP Response Bullet Sends:

Content-Type:application/json

{"_links":{"restaurants":{"title":"Restaurants","href":"http:\/\/yourdomain.local\/restaurants"},"events":{"title":"Events","href":"http:\/\/yourdomain.local\/events"}}}

Bullet Response Types

There are many possible values you can return from a route handler in Bullet to produce a valid HTTP response. Most types can be either returned directly, or wrapped in the $app->response() helper for additional customization.

Strings

$app = new Bullet\App();
$app->path('/', function($request) use($app) {
    return "Hello World";
});
$app->path('/', function($request) use($app) {
    return $app->response("Hello Error!", 500);
});

Strings result in a 200 OK response with a body containing the returned string. If you want to return a quick string response with a different HTTP status code, use the $app->response() helper.

Booleans

$app = new Bullet\App();
$app->path('/', function($request) use($app) {
    return true;
});
$app->path('notfound', function($request) use($app) {
    return false;
});

Boolean false results in a 404 "Not Found" HTTP response, and boolean true results in a 200 "OK" HTTP response.

Integers

$app = new Bullet\App();
$app->path('teapot', function($request) use($app) {
    return 418;
});

Integers are mapped to their corresponding HTTP status code. In this example, a 418 "I'm a Teapot" HTTP response would be sent.

Arrays

$app = new Bullet\App();
$app->path('foo', function($request) use($app) {
    return array('foo' => 'bar');
});
$app->path('bar', function($request) use($app) {
    return $app->response(array('bar' => 'baz'), 201);
});

Arrays are automatically passed through json_encode and the appropriate Content-Type: application/json HTTP response header is sent.

Templates

// Configure template path with constructor
$app = new Bullet\App(array(
    'template.cfg' => array('path' => __DIR__ . '/templates')
));

// Routes
$app->path('foo', function($request) use($app) {
    return $app->template('foo');
});
$app->path('bar', function($request) use($app) {
    return $app->template('bar', array('bar' => 'baz'), 201);
});

The $app->template() helper returns an instance of Bullet\View\Template that is lazy-rendered on __toString when the HTTP response is sent. The first argument is a template name, and the second (optional) argument is an array of parameters to pass to the template for use.

Serving large responses

Bullet works by wrapping every possible reponse with a Response object. This would normally mean that the entire request must be known (~be in memory) when you construct a new Response (either explicitly, or trusting Bullet to construct one for you).

This would be bad news for those serving large files or contents of big database tables or collections, since everything would have to be loaded into memory.

Here comes \Bullet\Response\Chunked for the rescue.

This response type requires some kind of iterable type. It works with regular arrays or array-like objects, but most importatnly, it works with generator functions too. Here's an example (database functions are purely fictional):

$app->path('foo', function($request) use($app) {
    $g = function () {
        $cursor = new ExampleDatabaseQuery("select * from giant_table");
        foreach ($cursor as $row) {
            yield example_format_db_row($row);
        }
        $cursor->close();
    };
    return new \Bullet\Response\Chunked($g());
});

The $g variable will contain a Closure that uses yield to fetch, process, and return data from a big dataset, using only a fraction of the memory needed to store all the rows at once.

This results in a HTTP chunked response. See https://tools.ietf.org/html/rfc7230#section-4.1 for the technical details.

HTTP Server Sent Events

Server sent events are one way to open up a persistent channel to a web server, and receive notifications. This can be used to implement a simple webchat for example.

This standard is part of HTML5, see https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events for details.

The example below show a simple application using the fictional send_message and receive_message functions for communications. These can be implemented over various message queues, or simple named pipes.

$app->path('sendmsg', function($request) {
    $this->post(function($request) {
        $data = $request->postParam('message');
        send_message($data);
        return 201;
    });
});

$app->path('readmsgs', function($request) {
    $this->get(function($request) {
        $g = function () {
            while (true) {
                $data = receive_message();
                yield [
                    'event' => 'message',
                    'data'  => $data
                ];
            }
        };
        \Bullet\Response\Sse::cleanupOb(); // Remove any output buffering
        return new \Bullet\Response\Sse($g());
    });
});

The SSE response uses chunked encoding, contrary to the recommendation in the standard. We can do this, since we tailoe out chunks to be exactly message-sized.

This will not confuse upstream servers when they see no chunked encoding, AND no Content-Length header field, and might try to "fix" this by either reading the entire response, or doing the chunking on their own.

PHP's output buffering can also interfere with messaging, hence the call to \Bullet\Response\Sse::cleanupOb(). This method flushes and ends every level of output buffering that might present before sending the response.

The SSE response automatically sends the X-Accel-Buffering: no header to prevent the server from buffering the messages.

Nested Requests (HMVC style code re-use)

Since you explicitly return values from Bullet routes instead of sending output directly, nested/sub requests are straightforward and easy. All route handlers will return Bullet\Response instances (even if they return a raw string or other data type, they are wrapped in a response object by the run method), and they can be composed to form a single HTTP response.

$app = new Bullet\App();
$app->path('foo', function($request) use($app) {
    return "foo";
});
$app->path('bar', function($request) use($app) {
    $foo = $app->run('GET', 'foo'); // $foo is now a `Bullet\Response` instance
    return $foo->content() . "bar";
});
echo $app->run('GET', 'bar'); // echos 'foobar' with a 200 OK status

Running Tests

To run the Bullet test suite, simply run vendor/bin/phpunit in the root of the directory where the bullet files are in. Please make sure to add tests and run the test suite before submitting pull requests for any contributions.

Credits

Bullet - and specifically path-based callbacks that fully embrace HTTP and encourage a more resource-oriented design - is something I have been thinking about for a long time, and was finally moved to create it after seeing @joshbuddy give a presentation on Renee (Ruby) at Confoo 2012 in Montréal.

Comments
  • Configuration Setting and Getting

    Configuration Setting and Getting

    Bullet needs a way to manage configuration beyond the provided way with Pimple. Some pseudocode:

    $app->config('some.nested.key', 'default_value_if_not_set');
    

    This would make retrieving config settings easy and provide the ability to set a default value in the case the specified config key was not set.

    enhancement 
    opened by vlucas 24
  • Should Bullet v2.0 Use Symfony HttpFoundation?

    Should Bullet v2.0 Use Symfony HttpFoundation?

    Using Symfony's HttpFoundation component is something I have been considering lightly since the beginning of Bullet, but the more time goes on, the more heavily I am considering using it. Here is my analysis:

    Why Change From Bullet's Own Request/Response?

    The primary reason for a switch to something like Symfony's HttpFoundation is that the current custom Request/Response classes that come with Bullet are not core to how Bullet functions, nor do they contribute to the uniqueness of Bullet itself. The primary selling point of Bullet is the routing style and structure, which will remain unchanged in Bullet\App.

    Pros

    • A library like Symfony HttpFoundation is more widely used, thoroughly tested, etc.
    • More features than Bullet's current Request/Response classes (specifically session and cookie handling that are currently absent in Bullet)
    • Bullet will be more attractive to people who have used Symfony HttpFoundation before (Symfony, Laravel, Silex, many others)
    • Less code to maintain for Bullet

    Cons

    • Heavier/slower than Bullet's custom Request/Response classes
    • Have to change the current template implementation as well (currently the Template object extends Bullet\Response so a template can be a literal response)
    • Will force lots of code changes to existing apps written with Bullet to upgrade to v2.0

    Your Thoughts

    Now I'd like your thoughts - will this affect you? How? For better or worse? What would you think about this change?

    question 
    opened by vlucas 12
  • Making bullet work with Whoops exception library

    Making bullet work with Whoops exception library

    This is more of a question than an "issue" ;)

    I was interested in getting BulletPHP working with the Whoops https://github.com/filp/whoops exception library.

    The code I tried so far was:

    $whoops = new \Whoops\Run;
    $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
    $whoops->register();
    
    $app = new Bullet\App();
    $app->path('/', function($request) {
        return $blah;
    });
    

    This appears to work, in that it gives me a stack dump, seemingly Whoops-generated as so:

    exception 'Whoops\Exception\ErrorException' with message 'Undefined variable: blah' in /var/sites/mysite/public/index.php:12
    Stack trace:
        #0 /var/sites/mysite/public/index.php(12): Whoops\Run->handleError(8, 'Undefined varia...', '/var/sites/mysi...', 12, Array)
        #1 [internal function]: Closure->{closure}(Object(Bullet\Request))
        #2 /var/sites/mysite/vendor/vlucas/bulletphp/src/Bullet/App.php(262): call_user_func(Object(Closure), Object(Bullet\Request))
        #3 /var/sites/mysite/vendor/vlucas/bulletphp/src/Bullet/App.php(193): Bullet\App->_runPath('GET', '')
        #4 /var/sites/mysite/public/index.php(16): Bullet\App->run(Object(Bullet\Request))
        #5 {main}
    

    but it's just that dump... no pretty page, no HTML at all. The dump pasted above is from the view source in the browser so it's only dumping the text itself.

    I know this isn't part of BulletPHP but I was wondering if you had any thoughts on why this might not be working? I will probably ask the Whoops folks too.

    opened by mackenza 11
  • Add Plugin/Service Provider Class

    Add Plugin/Service Provider Class

    The more apps I create with Bullet, the more I wish certain ways I configure and use Bullet were more modular and portable across other Bullet apps. I think it's time to look at creating a more serious/structured way to register a service with Bullet. I think a class file and interface should be used so that: (1) It is easy to register and autoload, and (2) so that it has predicable methods that we can call to add the desired functionality.

    Example use cases:

    • Doctrine ORM
    • Twig or other template engine
    • Better exception/error handling (like with #38)
    • many, many others...

    Example Class file

    use Bullet\Plugin, Bullet\PluginInterface;
    
    class TwigPlugin extends Plugin implements PluginInterface
    {
        public function run()
        {
            // do stuff with $this->app (instance of Bullet that is injected in constructor)
        }
    }
    

    Thoughts?

    enhancement 
    opened by vlucas 10
  • Redirect inside a 'before' event handler?

    Redirect inside a 'before' event handler?

    I have read the documentation on redirects and events but I can't seem to find the recommended way to accomplish what I am trying. Basically I want to run a check before every request is handled to see if the user is logged-in. If they are not I would like to redirect to the login page. The best I could come up with was this, but is this recommended practice?

    $app->on('before', function($request, $response) use($app) {
        if ( !Session::loggedIn() ) {
            $app->response()->redirect('/login', 302)->send();
            exit;
        }
    });
    
    opened by micmath 10
  • Path

    Path "/" not working in subfolder

    My project is in http://localhost/bullet

    Path("/") gives me a 404 other paths work as expected

    When I move my project to root (http://localhost) the "/" path works.

    I'm on windows 10, apache 2.4,10, PHP 5.4

    opened by jvdh 8
  • Add headers before response content is set

    Add headers before response content is set

    Hi again,

    I'm adding paging to my Rest API based on bullet and I'd like to add some headers (X-Total-Count and Link).

    Judging by the first lines of function header in response.php adding headers is impossible if the response has no content. Is there a reason why ?

    ~~For now I'm storing the content of my two headers in my $app ($app["X-Total-Count"] and $app["Link"]) and I add the headers in my response handler. It does not feel right even if it works without too much work.~~ See below

    Do you see a better solution ?

    Thanks in advance.

    opened by seblucas 8
  • Templates are rendered twice

    Templates are rendered twice

    Response::content() sets or retrieves stored rendered response content. Template extends Response and overrides content(), but without any internal storage--the template content is rendered every time content() is called.

    In the Bullet app flow, content() is initially called in the Bullet response handler (App::_handleResponse()), which checks to see if the response should be rendered as JSON. content() is called again via Response::__toString() when App::run() is printed. For Template responses, that means the full template (including the layout) is rendered at least twice (more if more response handlers are registered).

    index.php

    <?php
    global $tpl_counter; // tracks how many times the template is rendered
    $tpl_counter = 0;
    
    require_once dirname(__DIR__).'/vendor/autoload.php';
    
    $app = new \Bullet\App(array(
        'template.cfg' => array('path' => dirname(__DIR__).'/src/')
    ));
    
    $app->path('/', function($request) use ($app) {
        $app->get(function($request) use ($app) {
            return $app->template('template');
        });
    });
    
    echo $app->run(new \Bullet\Request());
    

    template.html.php

    <?php
    global $tpl_counter;
    $tpl_counter++; // increment the counter for each each template rendering
    ?>
    content in the template: <?php echo $tpl_counter; ?>
    

    page output

    content in the template: 2
    

    A possible solution could be to use the internal content storage in Template, so calling Template::content() only renders new content if the internal storage is empty. Alternatively the response handlers could skip any Template-type responses.

    bug 
    opened by ellotheth 8
  • [Response] `redirect()` default to 302 instead of 301

    [Response] `redirect()` default to 302 instead of 301

    I am using the following code in my routes:

    $this->response()->redirect('/', 302)->send();
    

    It would be great if by default redirect was a 302 to prevent browser caching of the redirect.

    opened by treffynnon 7
  • HMVC nested routing

    HMVC nested routing

    There seems to be a problem when calling App->run() from inside another App->run() instance. There doesn't seem to be separation of paths internally causing an recursive death.

    $app = new Bullet\App();
    
    $app->path('a', function($request) use($app) {
    
        $app->path('b', function($request) use($app) {
            return 'a/b';
        });
    });
    
    $app->path('c', function($request) use($app) {
    
        $app->path('a', function($request) use($app) {
    
            $app->path('b', function($request) use($app) {
    
                $a = $app->run('GET', 'a/b');
    
                return $a->content() . " + c/a/b\n";
            });
        });
    });
    
    echo $app->run('GET', 'c/a/b');
    
    opened by Xeoncross 7
  • #enhancement -- server sent events

    #enhancement -- server sent events

    I would love to see a new route type for server sent events filtering

    material: http://www.w3.org/TR/eventsource/ http://www.html5rocks.com/en/tutorials/eventsource/basics/ <<-- best one it shows server specs!

    https://github.com/Yaffle/EventSource/blob/master/php/events.php <-- example server code

    enhancement 
    opened by sam2332 6
  • Support PHP8

    Support PHP8

    First time trying this project with recently released PHP8 got me this error:

    Fatal error: Uncaught Error: Call to undefined function Bullet\get_magic_quotes_gpc() in /path/to/project/vendor/vlucas/bulletphp/src/Bullet/Request.php:73 Stack trace: #0 /path/to/project/public/index.php(11): Bullet\Request->__construct() #1 {main} thrown in /path/to/project/vendor/vlucas/bulletphp/src/Bullet/Request.php on line 73
    
    opened by crypticmind 0
  • Consider dropping php-5.6 and php-7.0 version supports

    Consider dropping php-5.6 and php-7.0 version supports

    Since the php-5.6 and php-7.0 versions are inactive for official PHP team.

    And I think it's time to drop these above PHP versions and let this framework require php-7.1 version at least.

    Once this issue is accepted, I'll happy to work on this :).

    opened by peter279k 5
  • Enhancement: Catch-all exception handling

    Enhancement: Catch-all exception handling

    When a Bullet app throws an exception, it triggers two handlers: the handler for Exception, and the handler for the exception subclass (i.e. RuntimeException or MyAwesomeCustomException). The handler for Exception always runs first:

    // Always trigger base 'Exception', plus actual exception class
    $events = array_unique(array('Exception', get_class($e)));
    

    As a result, the behavior in the Exception handler applies to every exception. Subclass handlers can overwrite behavior that manipulates the Response, but any error logging or notification will apply to every exception.

    In most of my Bullet apps, I use custom exceptions to handle all kinds of error conditions, including invalid user input. I don't necessarily want a stack trace in my error log every time a user mistypes their query, but I do want logs for otherwise unhandled or unexpected exceptions. In other words, I want to control my error logging on a per-exception (or per handler) basis. At the moment, that means I've got a giant switch statement (with a default case) in my Exception handler.

    Swapping the order of exception handling so Exception is last would be a big backwards-compatibility break. What if a catch-all exception handler (maybe except, in the same category as before and after) were added that only ran if no other exception handlers were present? If App::filter() returned false when no handlers were triggered, it could look something like this:

    // Run and get result
    try {
        $response = $this->_runPath($this->_requestMethod, $path);
    } catch(\Exception $e) {
        // Always trigger base 'Exception', plus actual exception class
        $events = array_unique(array('Exception', get_class($e)));
    
        // Default status is 500 and content is Exception object
        $this->response()->status(500)->content($e);
    
        // Run filters and assign response; returns false if nothing was run
        if (!$this->filter($events, array($e))) {
            $this->filter('except', array($e));
        }
    
        $response = $this->response();
        break;
    }
    
    enhancement 
    opened by ellotheth 2
  • Bullet doesn't fully support periods in URIs

    Bullet doesn't fully support periods in URIs

    There are times when one might like to have periods in the URI, but not a file extension. One such use case is for tokens, e.g. http://jwt.io/

    Here's the offending source: https://github.com/vlucas/bulletphp/blob/master/src/Bullet/App.php#L179

    One quick solution would be to wrap that line in a check for a match against the listed mime types.

    <?php
    
    if(isset($this->_request->_mimeTypes[$ext])) {
        $this->_requestPath = substr($this->_requestPath, 0, -(strlen($this->_request->format())+1));
    }
    

    This is just for illustration, the _mimeTypes array is protected. This approach passes existing unit tests, but would mean support for any additional extensions would need to be added to the _mimeTypes array. Perhaps a method for that?

    OR... probably the better solution would be to only strip the extension from the path if there's a matching call to $app->format('.ext'). This approach won't pass existing unit tests.

    For my token example, I'll either need to append a faux extension to the path (yuck), pass it using a POST call (yuck), pass it via a query string (yuck), or pass it via a request header (yuck). I'll end up moving forward using the least yucky of these... not sure which that is yet though :)

    bug 
    opened by acicali 3
  • Short-circuit path handling?

    Short-circuit path handling?

    I'm attempting to add authentication to my bullet-based web service. I'd like to be able to handle ALL requests and load a template for users that fail authentication, but this is proving more difficult than I expected.

    I authenticate all requests (even those that might otherwise result in a 404). Also, I authenticate via headers - the request must include valid ('X-Session-Username' AND 'X-Session-Password') OR ('X-Session-Token'). Lastly, I don't do any redirection - all URIs should return a 403 without proper authentication.

    The only way I can see to handle this "globally" is to use the 'before' event, but that won't stop the actual route from running.

    Am I missing something?

    enhancement 
    opened by acicali 9
  • HTTP Basic Auth in Request Class

    HTTP Basic Auth in Request Class

    Support should be added for retrieving the HTTP auth username and password in the request class, maybe with $request->user() and $request->pass() or similar. This is because there can be a number of ways to do it depending on the server setup, and the variables PHP_AUTH_USER and PHP_AUTH_PW are not always set, sometimes requiring the user to manually parse the Authorization header, and well... that just sucks.

    PHP Manual: http://php.net/manual/en/features.http-auth.php Same issue fixed in Symfony HttpFoundation: https://github.com/symfony/symfony/pull/3551/files

    enhancement 
    opened by vlucas 4
Owner
Vance Lucas
Co-founder of @techlahoma. Creator of BudgetSheet, Frisby.js, phpdotenv, valitron, and other open-source projects. Engineering Lead/Manager. FT Remote from OKC
Vance Lucas
Lemon is php micro framework built for simple applications.

Lemon is simple micro framework that provides routing, etc.

Lemon 20 Dec 16, 2022
TidyPHP is a micro php framework to build web applications

TidyPHP is a micro MVC PHP Framework made to understand how PHP Frameworks work behind the scense and build fast and tidy php web applications.

Amin 15 Jul 28, 2022
Frankie - A frankenstein micro-framework for PHP

Frankie - A frankenstein micro-framework for PHP Features Frankie is a micro-framework focused on annotation. The goal is to use annotation in order t

null 19 Dec 10, 2020
ExEngine is an ultra lightweight micro-services framework for PHP 5.6+

ExEngine is an ultra lightweight micro-services framework for PHP 5.6+. Documentation Checkout the Wiki. Examples Click here to browse examples, also

linkfast.io 1 Nov 23, 2020
REST-like PHP micro-framework.

Phprest Description REST-like PHP micro-framework. It's based on the Proton (StackPhp compatible) micro-framework. Phprest gives you only the very bas

Phprest 312 Dec 30, 2022
Silly CLI micro-framework based on Symfony Console

currentMenu home Silly CLI micro-framework based on Symfony Console. Professional support for Silly is available via Tidelift Video introduction in fr

Matthieu Napoli 862 Dec 23, 2022
Blink is a micro web framework for building long-running and high performance services

Blink is a micro web framework for building long-running and high performance services, the design heavily inspired by Yii2 and Laravel. Blink aims to provide the most expressive and elegant API and try to make the experience of web development as pleasant as possible.

Jin Hu 837 Dec 18, 2022
StackSync is a simple, lightweight and native fullstack PHP mini-framework.

StackSync is a fullstack PHP mini framework, with an MVC structure, custom API system with a Middleware and JWT authentication, components based views, flexible routing, PSR4 autoloading. Essential files generation (migrations, seeders, controllers and models) and other operations can be executed through custom commands.

Khomsi Adam 3 Jul 24, 2022
PHP微服务框架即Micro Service Framework For PHP

Micro Service Framework For PHP PHP微服务框架即“Micro Service Framework For PHP”,是Camera360社区服务器端团队基于Swoole自主研发现代化的PHP协程服务框架,简称msf或者php-msf,是Swoole的工程级企业应用框

Camera360 1.8k Jan 5, 2023
The Laravel Lumen Framework.

Lumen PHP Framework Laravel Lumen is a stunningly fast PHP micro-framework for building web applications with expressive, elegant syntax. We believe d

The Laravel Framework 7.6k Jan 7, 2023
Slim Framework 4 Skeleton Application

Slim Framework 4 Skeleton Application Use this skeleton application to quickly setup and start working on a new Slim Framework 4 application. This app

Slim Framework 1.5k Dec 29, 2022
🐺 Lightweight and easy to use framework for building web apps.

Wolff Web development made just right. Wolff is a ridiculously small and lightweight PHP framework, intended for those who want to build web applicati

Alejandro 216 Dec 8, 2022
Larasymf - mini framework for medium sized projects based on laravel and symfony packages

Larasymf, PHP Framework Larasymf, as its says is a mini framework for medium sized projects based on laravel and symfony packages We have not yet writ

Claude Fassinou 6 Jul 3, 2022
⚡ Flat-files and plain-old PHP functions rockin'on as a set of general purpose high-level abstractions.

Siler is a set of general purpose high-level abstractions aiming an API for declarative programming in PHP. ?? Files and functions as first-class citi

Leo Cavalcante 1.1k Dec 30, 2022
VELOX - The fastest way to build simple websites using PHP!

VELOX The fastest way to build simple websites using PHP! Table of Contents Installation About VELOX Architecture Config Classes Functions Themes Chan

Marwan Al-Soltany 53 Sep 13, 2022
A PHP client for (Spring Cloud) Netflix Eureka service registration and discovery.

PHP Netflix Eureka Client A PHP client for (Spring Cloud) Netflix Eureka service registration and discovery. Installation You can install this package

Hamid Mohayeji 72 Aug 21, 2022
Yet another PHP Microframework.

ρ Yet another PHP Microframework. The premise of this framework is to be backwards-compatible (PHP <= 5.6) with powerful utilities (Like caching and l

null 0 Apr 6, 2022
A resource-oriented micro PHP framework

Bullet Bullet is a resource-oriented micro PHP framework built around HTTP URIs. Bullet takes a unique functional-style approach to URL routing by par

Vance Lucas 415 Dec 27, 2022
A resource-oriented application framework

BEAR.Sunday A resource-oriented application framework What's BEAR.Sunday This resource orientated framework has both externally and internally a REST

Akihito Koriyama 6 May 11, 2022
A resource-oriented application framework

BEAR.Sunday A resource-oriented application framework What's BEAR.Sunday This resource orientated framework has both externally and internally a REST

BEAR.Sunday 236 Dec 27, 2022