web.php is a zero configuration web development library for PHP.
<?php
include 'web.php';
get('/', function() {
die('Hello, World!');
});
- Installation
- Routing
- Content Negotiation
- Forwards and Redirects
- Views, Layouts, Blocks, Partials, and Pagelets
- Filters, Forms, and Input Validation
- Sending Files
- Logging with log.php
- Password Hashing & Checking with password.php
- Database Access
- FAQ
- License
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
.
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
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.
<?php
include 'web.php';
get('/', function() {
die('Hello, World!');
});
http_response_code(404);
die('404 Not Found');
web.php
has support for routing HTTP GET, POST, PUT, HEAD, and DELETE request. Routes are case-insensitive, and the trailing /
is omitted.
Use get($path, $func, $head = true)
to route HTTP GET requests, and HTTP HEAD requests (by default).
<?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.
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...'
]));
}
}
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.
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.
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.
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.
Use route($path, $func)
to route all HTTP requests. See the GET Routes examples.
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;
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('/')
});
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('~/');
});
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.
web.php
has support for views, and layouts (or even sub-layouts).
<?php
get('/', function() {
die(new view('view.php'));
});
view.php:
<!DOCTYPE html>
Hello World
<?php
get('/%s', function($text) {
$view = new view('view.php');
$view->text = htmlspecialchars($text);
die($view);
});
view.php:
<!DOCTYPE html>
Hello, <?= $text ?>!
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
.
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.
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 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 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'); ?>
.
TBD (see: Facebook's BigPipe).
Use filter()
to filter a variable:
<?php
$email = '[email protected]';
echo filter($email, 'email') !== false ? 'Valid Email' : 'Invalid Email';
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.
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.
TBD
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)
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());
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
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
.
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!
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.
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.
You could try these:
You could also try out NoSQL DBs like:
Right now the tests are work in progress.
cd tests
pear run-tests
Try these:
- Perl: Mojolicious
- Ruby: Sinatra
- Python: web.py
- Javascript: Node.js + Express
Feel free to make a fork and change it to while (true) { ... }
or do { ... } while (true);
or for(;;) { ... }
.
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.