shroophp / framework
A framework for HTTP applications.
Requires
- php: >=5.4
- paragonie/random_compat: ^2.0
- shroophp/core: ^3.0
- shroophp/pattern: ^1.0
Requires (Dev)
- phpunit/phpunit: ^6.3 || ^5.7 || ^4.8
Suggests
- shroophp/psr: A library for building PSR-compliant applications with ShrooPHP.
- shroophp/restful: A library for building RESTful applications with ShrooPHP.
This package is auto-updated.
Last update: 2024-12-08 23:16:05 UTC
README
A framework for HTTP applications.
Installation
composer require 'shroophp/framework ^3.0'
Example Usage
Hello, world!
In the following example, every request will be responded to by printing the
string 'Hello, world!'
.
It is assumed that the method and path of the request will be read from
$_SERVER['REQUEST_METHOD']
and $_SERVER['REQUEST_URI']
respectively.
<?php
use ShrooPHP\Core\Request;
use ShrooPHP\Framework\Application;
use ShrooPHP\Framework\Request\Responses\Response;
use ShrooPHP\Framework\Requests\PhpRequest;
require 'vendor/autoload.php';
$app = new Application;
$app->push(Response::string('Hello, world!'));
$app->run();
Content Types
To change the format of the response, a content type can be specified as the second parameter of the response string.
Response::string('Hello, world!', 'text/plain');
URLs
It is also possible to match against specific request paths. In the following
example, only requests to the path /hello/world
will be responded to.
$app->path('/hello/world', Response::string('Hello, world!'));
Request Methods
Request methods can also be matched against. In following example, only
requests using the GET
method will be responded to.
$app->method('GET', Response::string('Hello, world!'));
Multiple methods can be matched by specifying them as an array, as in the
following example (which will respond to either GET
or POST
).
$app->method(array('GET', 'POST'), Response::string('Hello, world!'));
Dynamic URLs
It is possible to dynamically match request paths using a simple curly-brace
syntax. Each specified key will equate to a lazy regular expression group (i.e.
(.*?)
).
In the following example, any path starting with /hello/
will be responded to.
$app->path('/hello/{subject}', Response::string('Hello, subject!'));
Dynamic Responses
The response to the matched request can be determined dynamically. To achieve this, a series of callbacks may be specified.
In the following example, the matched request is responded to by printing
Hello, '<subject>'!
, where <subject>
is the suffix of the request path.
$app->path('/hello/{subject}', function (Application $app) {
$app->push(function (Request $request) {
$subject = $request->args()->get('subject');
return Response::string("Hello, '{$subject}'!", 'text/plain');
});
});
The \ShrooPHP\Framework\Application::toModifier()
helper method is aliased
as an underscore in order to make the previous example more succinct, as in
the following example.
$app->path('/hello/{subject}', $app->_(function (Request $request) {
$subject = $request->args()->get('subject');
return Response::string("Hello, '{$subject}'!", 'text/plain');
}));
Logic Chaining
The logic of the application can be chained using nested modifiers.
The application in the following example will match all GET
requests to the
path /chained
.
$app->path('/chained', function (Application $app) {
$app->method('GET', Response::string('Chained!', 'text/plain'));
});
Evaluation
An application may consist of multiple handlers. The application will run through each of them sequentially until a request is handled, at which point it will halt.
In the following example, the application will print two
, as the second
handler is the first to respond.
$app->path('first', Response::string('one', 'text/plain'))
->path('second', Response::string('two', 'text/plain'))
->path('third', Response::string('three', 'text/plain'))
->handle(new PhpRequest('GET', 'second')); // Prints 'two'.
The application determines whether or not a handler has responded by evaluating
the handler's return value. A return value of FALSE
or NULL
informs the
application that it should continue to iterate over the handlers, while TRUE
or \ShrooPHP\Core\Request\Response
will cause the application to present the
response (if any) and halt.
In the following example, the application will print handled
, as the first
handler returns NULL
.
$app->path('{path}', function (Application $app) {
$app->push(function (Request $request) {
return null;
});
});
$app->path('{path}', Response::string('handled', 'text/plain'));
$app->handle(new PhpRequest('GET', 'path'));
Method Overriding
Traditionally, forms on the web are only capable of submitting HTTP requests
with either the GET
or POST
method.
To overcome this, an overriding method can be sent in the request body and detected by the application as in the following example.
<?php use ShrooPHP\Framework\Application; ?>
<!-- method.php -->
<!DOCTYPE html>
<html>
<head>
<title>Method Overriding</title>
</head>
<body>
<form action="" method="POST">
<p>
<label for="method">Method</label>
<input id="method" type="text"
name="<?= htmlentities(Application::METHOD); ?>"
value="PUT">
</p>
<p>
<input type="submit">
</p>
</form>
</body>
</html>
<?php
use ShrooPHP\Core\Request;
use ShrooPHP\Framework\Application;
use ShrooPHP\Framework\Request\Responses\Response;
require 'vendor/autoload.php';
$app = new Application;
$app->web(function (Application $app) {
$app->method('PUT', function (Application $app) {
$app->push(function (Request $request) {
$dump = var_export($request->data()->getArrayCopy(), true);
return Response::string($dump, 'text/plain');
});
});
ob_start();
require 'method.php';
$app->push(Response::string(ob_get_clean(), 'text/html'));
});
$app->run();
Preventing Cross-Site Request Forgery (CSRF)
A common method for preventing cross-site request forgery is to validate requests against the target origin and a token associated with the session. This is achieved in the following example.
<?php use ShrooPHP\Framework\Application; ?>
<!-- token.php -->
<!DOCTYPE html>
<html>
<head>
<title>Preventing Cross-Site Request Forgery (CSRF)</title>
</head>
<body>
<form action="" method="POST">
<p>
<label for="token">Token</label>
<input id="token" type="text"
name="<?= htmlentities(Application::TOKEN); ?>"
value="<?= htmlentities($request->token()->expected()); ?>">
</p>
<p>
<input type="submit">
</p>
</form>
</body>
</html>
<?php
use ShrooPHP\Core\Request;
use ShrooPHP\Framework\Application;
use ShrooPHP\Framework\Request\Responses\Response;
require 'vendor/autoload.php';
$app = new Application;
$app->token(function (Application $app) {
$app->origin('https://example.org/', function (Application $app) {
$app->valid(Response::string('The token is valid.', 'text/plain'));
});
$app->push(function (Request $request) {
ob_start();
require 'token.php';
return Response::string(ob_get_clean(), 'text/html');
});
});
$app->run();
Configuration
The recommended configuration is to rewrite all requests to a single PHP file
(most commonly named index.php
), with the request path being passed via the
server API to $_SERVER['REQUEST_URI']
.
PHP Built-in Web Server
The PHP built-in web server is ideal for development, testing, and demonstration purposes.
Do not use the PHP built-in web server in production environments.
php -S localhost:80 index.php
Apache
Add a virtual host to your Apache web server that resembles the following.
<VirtualHost *:80>
DocumentRoot "/path/to/directory"
ServerName example.org
<Directory "/path/to/directory">
Require all granted
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !index.php
RewriteRule .* index.php [QSA,L]
</IfModule>
</Directory>
</VirtualHost>
nginx
Ensure that your nginx configuration resembles the following (assuming you are using FastCGI).
worker_processes auto;
worker_cpu_affinity auto;
pcre_jit on;
events {
worker_connections 2048;
}
http {
include mime.types;
default_type application/octet-stream;
index index.php;
server {
listen 80;
server_name example.org;
root /path/to/directory;
rewrite .* /index.php last;
location ~ \.php$ {
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
include fastcgi.conf;
}
}
}