All Projects → bungle → web.php

bungle / web.php

Licence: other
web.php is a zero configuration web development library for PHP.

Programming Languages

PHP
23972 projects - #3 most used programming language

web.php-logo
web.php is a zero configuration web development library for PHP.

Hello World in web.php

<?php
include 'web.php';
get('/', function() {
    die('Hello, World!');
});

Table of Contents

Installation

Download web.php (for logging download log.php, and for password hashing download password.php).

There are also libraries for Tumblr (tumblr.php), OpenID (openid.php), SQLite 3 (sqlite.php), and Postmark (postmark.php). New libraries are added now and then. These libraries follow the minimalistic approach of web.php.

On Apache Modify .htaccess

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

On Nginx with PHP-FPM

server {
    location / {
        try_files                 $uri $uri/ /index.php$is_args$args;
    }
    location = /index.php {
        try_files                 $uri = 404;
        fastcgi_pass              127.0.0.1:9000;
        fastcgi_index             index.php;
        fastcgi_param             SCRIPT_FILENAME   $document_root$fastcgi_script_name;
        fastcgi_split_path_info   ^(.+\.php)(/.+)$;
        include                   fastcgi_params;
    }
}

If you are using something other than Apache with mod_rewrite or nginx, Google for instructions.

Create index.php Bootstrap File

<?php
include 'web.php';
get('/', function() {
    die('Hello, World!');
});
http_response_code(404);
die('404 Not Found');

Routing

web.php has support for routing HTTP GET, POST, PUT, HEAD, and DELETE request. Routes are case-insensitive, and the trailing / is omitted.

GET Routes

Use get($path, $func, $head = true) to route HTTP GET requests, and HTTP HEAD requests (by default).

Routes without Parameters (the Routes Work on Sub-Directories too):

<?php
get('/', function() {
    die('Hello, World!');
});

or:

<?php
get('/posts', function() {
    die(json_encode([
        [
            'id'    => 1,
            'title' => 'Trying out web.php',
            'body'  => 'Lorem...' 
        ],
        [
            'id'    => 2,
            'title' => "I'm really starting to like web.php",
            'body'  => 'Lorem...' 
        ]
    ]));
});

You may use HTTP_X_HTTP_METHOD_OVERRIDE HTTP header to override the HTTP method.

Parameterized Routes

Route parameters in web.php are parsed with sscanf and vsprintf, but we have added extra parameter %p which acts the same as %[^/] (everything until or except /). Please read the documentation for the format from sprintf's documentation.

<?php
get('/posts/%d', function($id) {
    switch ($id) {
        case 1: die(json_encode([
                'id'    => 1,
                'title' => 'Trying out web.php',
                'body'  => 'Lorem...' 
            ]));
        case 2: die(json_encode([
                'id'    => 2,
                'title' => "I'm really starting to like web.php",
                'body'  => 'Lorem...' 
            ]));
    }
}

POST Routes

Use post($path, $func) to route HTTP POST requests. See the GET Routes examples.

You may use HTTP_X_HTTP_METHOD_OVERRIDE HTTP header to override the HTTP method.

PUT Routes

Use put($path, $func) to route HTTP PUT requests. See the GET Routes examples.

You can send PUT requests with POST method by sending _method parameter that has a value of PUT:

<form method="post">
    <input type="hidden" name="_method" value="PUT">
</form>

Or you may use HTTP_X_HTTP_METHOD_OVERRIDE HTTP header to override.

HEAD Routes

Use head($path, $func) to route HTTP PUT requests. See the GET Routes examples.

You can send HEAD requests with POST method by sending _method parameter that has a value of HEAD:

<form method="post">
    <input type="hidden" name="_method" value="PUT">
</form>

Or you may use HTTP_X_HTTP_METHOD_OVERRIDE HTTP header to override.

DELETE Routes

Use delete($path, $func) to route HTTP DELETE requests. See the GET Routes examples.

You can send DELETE requests with POST method by sending _method parameter that has a value of DELETE:

<form method="post">
    <input type="hidden" name="_method" value="DELETE">
</form>

Or you may use HTTP_X_HTTP_METHOD_OVERRIDE HTTP header to override.

Routing All Types of Requests

Use route($path, $func) to route all HTTP requests. See the GET Routes examples.

Content Negotiation

You can send different content based on client's Accept HTTP Header:

<?php
get('/ping', accept('text/html', 'application/xhtml+xml', function() {
$html =<<<'HTML'
<html>
    <body>PONG</body>
</html>
HTML;
die($html);
}));

get('/ping', accept('application/xml', function() {
    echo '<' . '?xml version="1.0" encoding="utf-8"?' . '>';
    die('<pong />');
}));

// Parameterized Routes with Content Negotiation:
get('/plus/%d/%d', accept('application/html', function($a, $b) {
    die('<html><body>' . ($a + $b) . '</body></html>');
}));

You can also use accept function like this:

<?php
get('/ping', function() {
    accept([
        'text/html'        => 'pong.html',
        'application/json' => 'pong.json',
        'application/xml'  => 'pong.xml'
    ]) and die;
});

These work too (as described here):

<?php
accept([
    'text/html'        => 'phpinfo',
    'application/xml'  => 'XML::xml',
    'application/json' => 'JSON->json',
    'text/plain'       => function() { echo 'Hello, World!'; }
]) and die;

Forwards and Redirects

Forwards

Use forward($name, $func) to register a named forward. Call a named forward with forward($name). Forwards need to be registered before using them.

<?php
forward('index', function() {
    die('Index Page');
});

get('/', function() {
    forward('index')
});

get('/another-url', function() {
    forward('index')
});

You can also register forwards when defining routes:

get('/', forward('/', function() {
    die('Index Page');
}));

get('/another-url', function() {
    forward('/')
});

Redirects

Use redirect($url, $code = 302, $die = true) to redirect the user in other page. Use $code value of 301 for permanent redirects.

<?php
session_start();
get('/', function() {
    die(isset($_SESSION['redirected']) ? 'Redirected' : 'Welcome!');
});

post('/', function() {
    flash('redirected');
    redirect('~/');
});

Flash Variables

Use flash($name, $value = true, $hops = 1) to set short living session variables. $hops argument tells us how long (for how many requests) should we keep the session variable.

Views, Layouts, Blocks, Partials, and Pagelets

web.php has support for views, and layouts (or even sub-layouts).

Views

<?php
get('/', function() {
    die(new view('view.php'));
});

view.php:

<!DOCTYPE html>
Hello World

Views with Properties

<?php
get('/%s', function($text) {
    $view = new view('view.php');
    $view->text = htmlspecialchars($text);
    die($view);
});

view.php:

<!DOCTYPE html>
Hello, <?= $text ?>!

Global View Variables

You can define global view variables that all the views will get with the following code:

<?php
view::$globals->title = 'web.php rocks!';

Note: If local view variables are defined with same name as global variables, local variables overwrite the global ones. The globals are still accessible from view::$globals.

Layouts

You can define the layout by setting the layout variable in a view, you can do it like this:

<?php
view::$globals->layout = 'layout.php';      // or
$view = new view('view.php', 'layout.php'); // or
$view = new view('view.php');
$view->layout = 'layout.php';               // or
$view = new view('view.php');               // see 'view.php'

view.php:

<?php $layout = 'layout.php'; ?>
Hello, World!

layout.php:

<!DOCTYPE html>
<html>
    <body><?= $view ?></body>
</html>

Note: All the view variables are also accessible from layouts.

Nested Layouts

view.php:

<?php $layout = 'section.php' ?>
<p>Hello World</p>

section.php:

<?php $layout = 'master.php' ?>
<section>
    <?= $view ?>
</section>

master.php:

<!DOCTYPE html>
<html>
    <body>
        <?= $view ?>
    </body>
</html>

Blocks

Blocks are a method to move particular block of 'text' from views to a particular location in layouts.

view.php:

<?php
$layout = 'layout.php';
$title  = 'Blocks - web.php';
?>
<?php block($head); ?>
    <meta name="description" content"web.php has blocks too!">
<?php block(); ?>

Hello World!

<?php block($aside); ?>
    Hello Aside, too!
<?php block(); ?>

<?php block($scripts); ?>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<?php block(); ?>

layout.php:

<!DOCTYPE html>
<html>
    <head>
        <title><?= isset($title) ? $title : 'Default Title' ?></title>
        <?= isset($head) ? $head : '' ?>
    </head>
    <body>
        <article>
            <?= $view ?>
        </article>
        <?php if (isset($aside)): ?>
        <aside>
            <?= $aside ?>
        </aside>
        <?php endif; ?>
        <?= isset($scripts) ? $scripts : '' ?>
    </body>
</html>

Partials

Partials are included fragments. They are almost similar to include or require but they allow passing arguments, and they return the content as a string instead of outputting directly.

<!DOCTYPE html>
<html>
    <body>
        <?= partial('./body.php') ?>
    </body>
</html>

You can also pass variables to partials:

<!DOCTYPE html>
<html>
    <body>
        <?= partial('./body.php', ['name' => 'web.php']) ?>
    </body>
</html>

body.php:

Hello, <?= $name ?>!

It is possible to render partials when constructing a view:

<?php
$view = new view('./view.php');
$view->body = partial('./body.php', ['name' => 'web.php']);
die($view);

view.php:

<!DOCTYPE html>
<html>
    <body>
        <?= body ?>
    </body>
</html>

If you want to pass all view variables to a partial, you can do it like this:

view.php:

<!DOCTYPE html>
<html>
    <body>
        <?= partial('./body.php', (array) this) ?>
    </body>
</html>

But maybe it is easier to just <?php include('./body.php'); ?>.

Pagelets

TBD (see: Facebook's BigPipe).

Filters, Forms, and Input Validation

Filtering and Validating Variables

Use filter() to filter a variable:

<?php
$email = '[email protected]';
echo filter($email, 'email') !== false ? 'Valid Email' : 'Invalid Email';

Built-in Filters and Validators

web.php has these built-in validators available that come with PHP's Filter Funtions:

'bool', 'int', 'float', 'ip', 'ipv4', 'ipv6', 'email', and 'url'

In addition to that you can validate using regular expressions:

<?php
$email = '[email protected]';
echo filter($email, '/^.+@.+$/') !== false ? 'Valid Email' : 'Invalid Email'; // Outputs 'Valid Email'

But that is not all, web.php comes with these functions to aid in validation:

  • not($filter)
  • equal($exact, $strict = true)
  • length($min, $max = null, $charset = 'UTF-8')
  • minlength($min, $charset = 'UTF-8')
  • maxlength($max, $charset = 'UTF-8')
  • between($min, $max)
  • minvalue($min)
  • maxvalue($max)
  • choice()

Example:

<?php
$email = '[email protected]';
echo filter(
    $email,
    'email',
    choice('[email protected]', '[email protected]')
) !== false ? 'Valid Email' : 'Invalid Email'; // Outputs 'Valid Email'

$age_o = '16';
$age_f = filter(
    $age_o,
    'int',
    'intval',
    not(between(0, 18))
);
echo $age_f !== false ? "Under-aged: {$age_o}" : "Over-aged: {$age_f}"; // Outputs 'Under-aged: 16'

Note: you can use multiple filters with single filter call.

User Defined Filters and Validators

Filters can either modify the $value or validate the $value. If validation filter fails, the filter function will return false immediately.

The most simple modifying filter:

<?php
function world_filter($value) {
    return "{$value} World!";
}
echo filter('Hello', 'world_filter');               // Outputs 'Hello World!'
echo filter('Hello', 'world_filter', 'strtoupper'); // Outputs 'HELLO WORLD!'

The most simple validating filters:

<?php
function true_validator($value) {
    return true;
}
function false_validator($value) {
    return false;
}    
$valid = filter('Hello', 'true_validator') === 'Hello';        // $valid holds bool(true)
$valid = filter('Hello', 'true_validator', 'false_validator'); // $valid holds bool(false)

You can also mix modifying filters and validating filters:

<?php
$value = filter('1', 'int', 'intval'); // $value holds int(1)
$value = filter('A', 'int', 'intval'); // $value holds bool(false)

Sometimes you need to write parameterized filters:

<?php
function lessthan($number) {
    return function($value) use ($number) {
        return $value < $number;
    };    
}

$num = '5';
echo filter($num, 'int', 'intval', lessthan(6)) !== false ? "{$num} is less than 6" : "{$num} is not less than 6";

Note: You can also have your filter functions namespaced.

Forms Filtering and Validation

TBD

Sending Files

X-Sendfile Support

Logging with log.php

Logging with ChromePHP

Logging with System Logger

Extending the Logger

Password Hashing & Checking with password.php

Database Access

Using SQLite with sqlite.php

sqlite.php has a few functions to make accessing SQLite 3 databases intuitive, and safe.

Transaction functions:

  • \sqlite\tx($func, $mode = null)

Single row/pair/value returning functions:

  • \sqlite\value()
  • \sqlite\pair()
  • \sqlite\row()
  • \sqlite\exists()

Multiple rows/pairs/values can be queried with following functions:

  • \sqlite\values()
  • \sqlite\pairs()
  • \sqlite\rows()

Data manipulation operations can be called with the following functions:

  • \sqlite\insert($table, $values, &$id)
  • \sqlite\update()
  • \sqlite\delete()

Low lewel, and utility functions:

  • \sqlite\connect($filename = null, $flags = SQLITE3_OPEN_READWRITE, $busyTimeout = null)
  • \sqlite\prepare($query, $params = array())
  • \sqlite\single($query, $params = array(), $type = 'r')
  • \sqlite\multi($query, $params = array(), $type = 'r', $filter = null)
  • \sqlite\modify($query, $params, &$id = null)
  • \sqlite\exec()
  • \sqlite\blob($data)

Querying Database

Use \sqlite\value() to get single value from database:

<?php
$max = \sqlite\value('SELECT MAX(amount) FROM sales');
// You can also pass arguments:
$max = \sqlite\value('SELECT MAX(amount) FROM sales WHERE cid = ?', 134);
// Or multiple arguments:
$max = \sqlite\value('SELECT MAX(amount) FROM sales WHERE cid = ? AND dtm < ?', 134, date_create());

FAQ

Is routing to anonymous function the only option?

You can actually route to files, functions, static class methods, and object instance methods:

get('/%d', 'router.php');          // Look for $args[0] inside router.php
get('/%p', 'die');                 // URL: /hello will output 'hello' with PHP's built-in function 'die'
get('/', 'Clazz::staticMethod');   // Executes a static method
get('/', 'Clazz->instanceMethod'); // Instantiates new object from class 'Clazz' using parameterless constructor

web.php pollutes the global root namespace!

Yes, that's true. web.php could be wrapped to namespace just by making this declaration on top of the web.php:

namespace web;

... and then just instead of calling for example get you would call web\get. The reason we didn't choose to do that is just that we like using the shorter versions for these functions. We welcome you to do a fork of web.php, if you see this as an issue. And we are also open for suggestions.

Why are you using 'die' inside controllers? How can I execute code after executing route, i.e. cleanup resources?

This design decision is probably something that people may or may not agree. We think that it is user's responsibility to manage the execution. You don't have to die, but keep in mind that any other route that is executed after the matching one will also be executed if it matches the url. That's why it's common to die with web.php.

If you want to run cleanup code, please try to build your code so that cleanup routines can be registered with register_shutdown_function.

How fast is web.php?

It depends on what you compare it to. But if you compare it to other PHP frameworks, web.php will surely stand the competition. If you know how web.php could be made faster, please let us know it too!

Why web.php is not object oriented?

We think that PHP enables us to do multiparadigm programming. We always try to evaluate different approaches, when we decide to add features, or when we are refactoring web.php. Sometimes the procedural approach wins (most of the web.php's core), and sometimes object oriented way of doing things stands out the best (e.g. views, and forms). PHP also allows us to mix procedural, and object oriented programming with functional programming. We tend to write the client code first, so that we can see how it looks like, before we go actually implementing the thing. In that process we usually try different paradigms. It might come to taste, but we tend to like procedural / functional way of doing things cleaner (less abstraction and encapsulation, but also more to the point solutions with less code). So basically we do use object oriented as we see it fit, but not exclusively.

Anthony Ferrara has said this better: Paradigm Soup on YouTube.

What is the philosophy behind web.php?

If there is any, this comes to close:

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
-- Antoine de Saint-Exupéry

web.php is trying to follow the principles of Unix philosophy.

web.php doesn't provide object relational mapper (ORM), what do you suggest?

You could try these:

You could also try out NoSQL DBs like:

How do I run the tests?

Right now the tests are work in progress.

cd tests
pear run-tests

PHP Sucks! Where to go next?

Try these:

I see that you are using goto inside view class' __toString. Isn't goto considered harmful?

Feel free to make a fork and change it to while (true) { ... } or do { ... } while (true); or for(;;) { ... }.

License

web.php is distributed with MIT License.

Copyright (c) 2012 — 2015 Aapo Talvensaari

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of
the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].