SkillAgentSearch skills...

Bento

Simple PHP micro-framework

Install / Use

/learn @nramenta/Bento
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Bento - A simple PHP micro framework

Bento provides a simple yet flexible routing system, built-in CSRF prevention, flash session variables, and numerous little helper functions to make developing web apps enjoyable.

Installation

Bento is self contained; it consists of a single PHP file. Simply copy the file into your lib or vendor directory, require it from your front controller script, and you're all set. The alternative is through [composer][Composer]; the minimum composer.json configuration is:

{
    "require": {
        "bento/bento": "@stable"
    }
}

PHP 5.3 or newer is required. PHP 5.4 or newer is strongly recommended.

Apache

Minimum .htaccess configuration for mod_rewrite:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

Or, if you are not uploading to the root of your site:

<IfModule mod_rewrite.c>
    RewriteEngine On
    
    # append %{ENV:REWRITE_BASE} to rules.
    RewriteCond %{REQUEST_URI}::$1 ^(.*?/)(.*)::\2$
    RewriteRule ^(.*)$ - [E=REWRITE_BASE:%1]
    
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ %{ENV:REWRITE_BASE}index.php [L]
</IfModule>

Nginx + PHP-FPM

Minimum nginx configuration:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

If you want to install your application in a subdirectory, for example myapp, do the following:

location /myapp {
    try_files $uri $uri/ /myapp/index.php?$query_string;
}

Built-in server

To use the built-in server available in PHP 5.4, invoke the following command from your terminal:

> php -S localhost:8000 index.php

Adjust the host and port as necessary. Replace all instances of index.php above with your front controller file.

Example Application

There is a simple blog application found in examples/blog that shows off all the major features and usage of Bento. Refer to its README.md for more info.

Usage

The canonical "Hello, World!" example:

<?php
require 'path/to/bento.php';

get('/hello/<world>', function($world)
{
    echo 'Hello, ' . e($world);
});

return run(__FILE__);

To ensure compatibility with the built-in server in PHP 5.4, always return the value returned by run(__FILE__). The following usage examples will omit the require and return run(__FILE__) parts.

Built-in routing functions to map routes to callbacks:

  • get(): Routes GET requests and acts as the fallback router for HEAD requests.
  • post(): Routes POST requests.
  • form(): Routes both GET and POST requests with automatic CSRF protection.
  • put(): Routes PUT requests.
  • patch(): Routes PATCH requests.
  • delete(): Routes DELETE requests.
  • head(): Routes HEAD requests.
  • options(): Routes OPTIONS requests.
  • any(): Routes all the above HTTP methods.

All routes implicitly handle the OPTIONS method. All routing functions take two parameters:

  • $route: Route pattern.
  • $callback: Route handler; it can be any valid PHP callable.

Route patterns

Routes must always begin with a forward slash. Routes can contain dynamic paths along with their optional rules. Dynamic paths will be translated to positional parameters passed on to the route handler. They can also be accessed using the params() function.

The syntax for dynamic paths is:

<rule:name>

The rule is optional; if you omit it, be sure to also omit the : separator. Named paths without rules matches all characters up to but not including /.

Some examples of routes:

/posts/<#:id>
/users/<username>
/pages/about
/blog/<#:year>/<#:month>/<#:date>/<#:id>-<$:title>
/files/<*:path>

There are three built-in rules:

  • #: digits only, equivalent to \d+.
  • $: alphanums and dashes only, equivalent to [a-zA-Z0-9-_]+.
  • *: any characters including /, equivalent to .+.

Custom rules are defined using regular expressions:

/users/<[a-zA-Z0-9_]+:username>

Using the # character inside a custom rule should be avoided; URL paths cannot contain any # characters as they are interpreted as URL fragments. If you want to use them to match url-encoded # (encoded as %23), you must escape them as \#.

Routes are matched first-only, meaning if a route matches the request path then its route handler will be executed and no more routes will be matched. Requests that do not match any routes will yield a "404 Not Found" error.

Named Routes

You can assign names to routes by using the route_for function:

<?php
route_for('user.edit', '/users/<id>');

get('user.edit', function() {
    // display user edit/update form
});

The functions url_for, flash_redirect_to and redirect_to accept named routes:

<?php
route_for('user.edit', '/users/<id>');

url_for('user.edit', array('id' => 42));

To get the current request route, use the request_route function.

Automatic 301 redirection

For every route that ends in a forward slash, any request for that route not ending in a forward slash will result in a "301 Moved Permanently" redirection to the same route ending in a forward slash:

<?php
get('/books/', function()
{
    echo 'Books!';
});

In the above example, a request to /books?page=42 will result in a redirect to /books/?page=42. This behavior is only true if /books is not a registered route. The reverse, however, is never true:

<?php
get('/about', function()
{
    echo 'About Us';
});

A request to /about/ will result in a "404 Not Found" error. This behavior is consistent with most popular web servers.

Method override

You can override the POST HTTP method by using two methods:

  1. Send a X-HTTP-Method-Override: METHOD header as part of your request, where METHOD is an overriding HTTP method such as PUT, DELETE, or PATCH.
  2. Send the overriding method as _method in your request data.

Routing events

  • before: Triggered just before a route handler is about to be called; this implies that a route is matched.
  • after: Triggered just after a route handler is called. If a route handler exits prematurely, e.g., by invoking halt(), this event is never triggered.

The functions before() and after() are provided as convenience:

<?php
before(function()
{
    echo 'Printed for every matched route prior to the route handler.';
});

after(function()
{
    echo 'As will this, but afer the route handler.';
});

Flash sessions

Flash sessions are key-value session variables that are available for use only once in a subsequent request and no further. Pass two arguments as key and value to set a flash session; pass a single argument as key to get a flash session:

<?php
get('/step-1', function()
{
    flash('words', 'available in the very next request, but no further.');
});

get('/step-2', function()
{
    echo 'Your words were ' . flash('words');
});

In the above example, when a request to /step-2 is made right after a request to /step-1, the flash variable words is available and set. If the user then visits any other URL after or in between those requests, all available flash variables will, by default, be cleared and would yield null.

There are a number flash-related helper functions:

  • flash(): Gets or sets a flash value.
  • flash_now(): Gets or sets a flash value only for the current request.
  • flash_keep(): Keeps a specific or all flash values on to the next request.
  • flash_discard(): Discards a specific or all flash values.
  • flash_remove(): Removes a specific or all flash values immediately.
  • flash_redirect(): Sets multiple flash values, redirects to a given URL.
  • flash_redirect_to(): Sets multiple flash values, redirects to a given path.

The configuration key _flash defines the name of the session variable holding the flash variables. It defaults to _flash. To change it, put the following somewhere at the top of your controller file:

<?php
config('_flash', 'custom_flash_key');

Built-in CSRF prevention

Effortlessly protect your forms against [cross site request forgeries][CSRF]:

<?php
get('/posts/new', function()
{
    $csrf_field = csrf_field();
    // pass this hidden field tag to your HTML form
});

The value returned from csrf_field() is an HTML hidden field tag. In your POST route handler, simply call prevent_csrf():

<?php
post('/posts/new', function()
{
    prevent_csrf();
    // create new post ...
});

Any CSRF-related errors will result in a "400 Bad Request" invoked automatically by calling halt(400, 'csrf'). Use SSL lest your CSRF tokens be subject to [man in the middle attacks][MITM].

Even though it is strongly advisable to keep your GET requests idempotent, you can also protect GET requests from CSRFs, for example, if you have a delete link in your application:

<?php
get('/posts/<#:id>', function($id)
{
    echo '<a href="'.url_for("/delete/$id?" . csrf_qs()).'">delete post</a>';
});

The value return from csrf_qs is a URL-encoded query string. In your GET route handler:

<?php
get('/delete/<#:id>', function($id)
{
    // auth() here is a custom user-defined authorization function
    auth() and prevent_csrf();
    // continue ...
});

The configuration key _csrf defines the name of the POST or GET variable used to compare tokens as well as the name of the session variable holding the CSRF token. It defaults to _csrf. To change it, put the following somewhere at the top of your controller file:

<?php
config('_csrf', 'custom_csrf_key');

Another way to send the CSRF token is by sending a X-CSRF-Token HTTP header with your CSRF token in it along with your request.

Post-Redirect-Get pattern

[Post/Redirect/Get][PRG] is a web development design pattern that prevents some duplicate form submissions, resulting in a more intuitive user interface:

<?php
form('/posts/<id>', function($id)
{
    if (reque
View on GitHub
GitHub Stars33
CategoryDevelopment
Updated4y ago
Forks3

Languages

PHP

Security Score

75/100

Audited on Mar 15, 2022

No findings