This chapter describes how to use Silex.
To include the Silex all you need to do is require the silex.phar
file and create an instance of Silex\Application
. After your
controller definitions, call the run
method on your application:
require_once __DIR__.'/silex.phar'; $app = new Silex\Application(); // definitions $app->run();
One other thing you have to do is configure your web server. If you
are using apache you can use a .htaccess
file for this.
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
#RewriteBase /path/to/app
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
Note
If your site is not at the webroot level you will have to uncomment the
RewriteBase
statement and adjust the path to point to your directory,
relative from the webroot.
Tip
When developing a website, you might want to turn on the debug mode to ease debugging:
$app['debug'] = true;
Tip
If your application is hosted behind a reverse proxy and you want Silex to trust the X-Forwarded-For* headers, you will need to run your application like this:
use Symfony\Component\HttpFoundation\Request; Request::trustProxyData(); $app->run();
In Silex you define a route and the controller that is called when that route is matched
A route pattern consists of:
- Pattern: The route pattern defines a path that points to a resource. The pattern can include variable parts and you are able to set RegExp requirements for them.
- Method: One of the following HTTP methods:
GET
,POST
,PUT
DELETE
. This describes the interaction with the resource. Commonly onlyGET
andPOST
are used, but it is possible to use the others as well.
The controller is defined using a closure like this:
function () { // do something }
Closures are anonymous functions that may import state from outside of their definition. This is different from globals, because the outer state does not have to be global. For instance, you could define a closure in a function and import local variables of that function.
Note
Closures that do not import scope are referred to as lambdas.
Because in PHP all anonymous functions are instances of the
Closure
class, we will not make a distinction here.
The return value of the closure becomes the content of the page.
There is also an alternate way for defining controllers using a
class method. The syntax for that is ClassName::methodName
.
Static methods are also possible.
Here is an example definition of a GET
route:
$blogPosts = array( 1 => array( 'date' => '2011-03-29', 'author' => 'igorw', 'title' => 'Using Silex', 'body' => '...', ), ); $app->get('/blog', function () use ($blogPosts) { $output = ''; foreach ($blogPosts as $post) { $output .= $post['title']; $output .= '<br />'; } return $output; });
Visiting /blog
will return a list of blog post titles. The use
statement means something different in this context. It tells the
closure to import the $blogPosts variable from the outer scope. This
allows you to use it from within the closure.
Now, you can create another controller for viewing individual blog posts:
$app->get('/blog/show/{id}', function (Silex\Application $app, $id) use ($blogPosts) { if (!isset($blogPosts[$id])) { $app->abort(404, "Post $id does not exist."); } $post = $blogPosts[$id]; return "<h1>{$post['title']}</h1>". "<p>{$post['body']}</p>"; });
This route definition has a variable {id}
part which is passed
to the closure.
When the post does not exist, we are using abort()
to stop the request
early. It actually throws an exception, which we will see how to handle later
on.
POST routes signify the creation of a resource. An example for this is a
feedback form. We will use the mail
function to send an e-mail:
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $app->post('/feedback', function (Request $request) { $message = $request->get('message'); mail('feedback@yoursite.com', '[YourSite] Feedback', $message); return new Response('Thank you for your feedback!', 201); });
It is pretty straightforward.
Note
There is a :doc:`SwiftmailerServiceProvider <providers/swiftmailer>` included
that you can use instead of mail()
.
The current request
is automatically injected by Silex to the Closure
thanks to the type hinting. It is an instance of Request,
so you can fetch variables using the request get
method.
Instead of returning a string we are returning an instance of
Response.
This allows setting an HTTP
status code, in this case it is set to 201 Created
.
Note
Silex always uses a Response
internally, it converts strings to
responses with status code 200 Ok
.
You can create controllers for most HTTP methods. Just call one of these
methods on your application: get
, post
, put
, delete
. You
can also call match
, which will match all methods:
$app->match('/blog', function () { ... });
You can then restrict the allowed methods via the method
method:
$app->match('/blog', function () { ... }) ->method('PATCH');
You can match multiple methods with one controller using regex syntax:
$app->match('/blog', function () { ... }) ->method('PUT|POST');
Note
The order in which the routes are defined is significant. The first matching route will be used, so place more generic routes at the bottom.
As it has been shown before you can define variable parts in a route like this:
$app->get('/blog/show/{id}', function ($id) { ... });
It is also possible to have more than one variable part, just make sure the closure arguments match the names of the variable parts:
$app->get('/blog/show/{postId}/{commentId}', function ($postId, $commentId) { ... });
While it's not suggested, you could also do this (note the switched arguments):
$app->get('/blog/show/{postId}/{commentId}', function ($commentId, $postId) { ... });
You can also ask for the current Request and Application objects:
$app->get('/blog/show/{id}', function (Application $app, Request $request, $id) { ... });
Note
Note for the Application and Request objects, Silex does the injection based on the type hinting and not on the variable name:
$app->get('/blog/show/{id}', function (Application $foo, Request $bar, $id) { ... });
Before injecting the route variables into the controller, you can apply some converters:
$app->get('/user/{id}', function ($id) { // ... })->convert('id', function ($id) { return (int) $id; });
This is useful when you want to convert route variables to objects as it allows to reuse the conversion code across different controllers:
$userProvider = function ($id) { return new User($id); }; $app->get('/user/{user}', function (User $user) { // ... })->convert('user', $userProvider); $app->get('/user/{user}/edit', function (User $user) { // ... })->convert('user', $userProvider);
The converter callback also receives the Request
as its second argument:
$callback = function ($post, Request $request) { return new Post($request->attributes->get('slug')); }; $app->get('/blog/{id}/{slug}', function (Post $post) { // ... })->convert('post', $callback);
In some cases you may want to only match certain expressions. You can define
requirements using regular expressions by calling assert
on the
Controller
object, which is returned by the routing methods.
The following will make sure the id
argument is numeric, since \d+
matches any amount of digits:
$app->get('/blog/show/{id}', function ($id) { ... }) ->assert('id', '\d+');
You can also chain these calls:
$app->get('/blog/show/{postId}/{commentId}', function ($postId, $commentId) { ... }) ->assert('postId', '\d+') ->assert('commentId', '\d+');
You can define a default value for any route variable by calling value
on
the Controller
object:
$app->get('/{pageName}', function ($pageName) { ... }) ->value('pageName', 'index');
This will allow matching /
, in which case the pageName
variable will
have the value index
.
Some providers (such as UrlGeneratorProvider
) can make use of named routes.
By default Silex will generate a route name for you, that cannot really be
used. You can give a route a name by calling bind
on the Controller
object that is returned by the routing methods:
$app->get('/', function () { ... }) ->bind('homepage'); $app->get('/blog/show/{id}', function ($id) { ... }) ->bind('blog_post');
Note
It only makes sense to name routes if you use providers that make use
of the RouteCollection
.
Silex allows you to run code before and after every request. This happens
through before
and after
filters. All you need to do is pass a closure:
$app->before(function () { // set up }); $app->after(function () { // tear down });
The before filter has access to the current Request, and can short-circuit the whole rendering by returning a Response:
$app->before(function (Request $request) { // redirect the user to the login screen if access to the Resource is protected if (...) { return new RedirectResponse('/login'); } });
The after filter has access to the Request and the Response:
$app->after(function (Request $request, Response $response) { // tweak the Response });
Note
The filters are only run for the "master" Request.
Route middlewares are PHP callables which are triggered when their associated
route is matched. They are fired just before the route callback, but after the
application before
filters.
This can be used for a lot of use cases; for instance, here is a simple "anonymous/logged user" check:
$mustBeAnonymous = function (Request $request) use ($app) { if ($app['session']->has('userId')) { return $app->redirect('/user/logout'); } }; $mustBeLogged = function (Request $request) use ($app) { if (!$app['session']->has('userId')) { return $app->redirect('/user/login'); } }; $app->get('/user/subscribe', function () { ... }) ->middleware($mustBeAnonymous); $app->get('/user/login', function () { ... }) ->middleware($mustBeAnonymous); $app->get('/user/my-profile', function () { ... }) ->middleware($mustBeLogged);
The middleware
function can be called several times for a given route, in
which case they are triggered in the same order as you added them to the
route.
For convenience, the route middlewares functions are triggered with the
current Request
instance as their only argument.
If any of the route middlewares returns a Symfony HTTP Response, it will
short-circuit the whole rendering: the next middlewares won't be run, neither
the route callback. You can also redirect to another page by returning a
redirect response, which you can create by calling the Application
redirect
method.
If a route middleware does not return a Symfony HTTP Response or null
, a
RuntimeException
is thrown.
If some part of your code throws an exception you will want to display some kind of error page to the user. This is what error handlers do. You can also use them to do additional things, such as logging.
To register an error handler, pass a closure to the error
method
which takes an Exception
argument and returns a response:
use Symfony\Component\HttpFoundation\Response; $app->error(function (\Exception $e, $code) { return new Response('We are sorry, but something went terribly wrong.', $code); });
You can also check for specific errors by using the $code
argument, and
handle them differently:
use Symfony\Component\HttpFoundation\Response; $app->error(function (\Exception $e, $code) { switch ($code) { case 404: $message = 'The requested page could not be found.'; break; default: $message = 'We are sorry, but something went terribly wrong.'; } return new Response($message, $code); });
If you want to set up logging you can use a separate error handler for that. Just make sure you register it before the response error handlers, because once a response is returned, the following handlers are ignored.
Note
Silex ships with a provider for Monolog which handles logging of errors. Check out the Providers chapter for details.
Tip
Silex comes with a default error handler that displays a detailed error
message with the stack trace when debug is true, and a simple error
message otherwise. Error handlers registered via the error()
method
always take precedence but you can keep the nice error messages when debug
is turned on like this:
use Symfony\Component\HttpFoundation\Response; $app->error(function (\Exception $e, $code) use ($app) { if ($app['debug']) { return; } // logic to handle the error and return a Response });
The error handlers are also called when you use abort
to abort a request
early:
$app->get('/blog/show/{id}', function (Silex\Application $app, $id) use ($blogPosts) { if (!isset($blogPosts[$id])) { $app->abort(404, "Post $id does not exist."); } return new Response(...); });
You can redirect to another page by returning a redirect response, which
you can create by calling the redirect
method:
$app->get('/', function () use ($app) { return $app->redirect('/hello'); });
This will redirect from /
to /hello
.
If you want to return JSON data, you can use the json
helper method.
Simply pass it your data, status code and headers, and it will create a
JSON response for you.
$app->get('/users/{id}', function ($id) use ($app) {
$user = getUser($id);
if (!$user) {
$error = array('message' => 'The user was not found.');
return $app->json($error, 404);
}
return $app->json($user);
});
It's possible to create a streaming response, which is important in cases when you cannot buffer the data being sent.
$app->get('/images/{file}', function ($file) use ($app) {
if (!file_exists(__DIR__.'/images/'.$file)) {
return $app->abort(404, 'The image was not found.');
}
$stream = function () use ($file) {
readfile($file);
};
return $app->stream($stream, 200, array('Content-Type' => 'image/png'));
});
If you need to send chunks, make sure you call ob_flush
and flush
after
every chunk.
$stream = function () {
$fh = fopen('http://www.example.com/', 'rb');
while (!feof($fh)) {
echo fread($fh, 1024);
ob_flush();
flush();
}
fclose($fh);
};
Make sure to protect your application against attacks.
When outputting any user input (either route variables GET/POST variables obtained from the request), you will have to make sure to escape it correctly, to prevent Cross-Site-Scripting attacks.
Escaping HTML: PHP provides the
htmlspecialchars
function for this. Silex provides a shortcutescape
method:$app->get('/name', function (Silex\Application $app) { $name = $app['request']->get('name'); return "You provided the name {$app->escape($name)}."; });
If you use the Twig template engine you should use its escaping or even auto-escaping mechanisms.
Escaping JSON: If you want to provide data in JSON format you should use the PHP
json_encode
function:use Symfony\Component\HttpFoundation\Response; $app->get('/name.json', function (Silex\Application $app) { $name = $app['request']->get('name'); return new Response( json_encode(array('name' => $name)), 200, array('Content-Type' => 'application/json') ); });
Silex includes a lightweight console for updating to the latest version.
To find out which version of Silex you are using, invoke silex.phar
on the
command-line with version
as an argument:
$ php silex.phar version
Silex version 0a243d3 2011-04-17 14:49:31 +0200
To check that your are using the latest version, run the check
command:
$ php silex.phar check
To update silex.phar
to the latest version, invoke the update
command:
$ php silex.phar update
This will automatically download a new silex.phar
from
silex.sensiolabs.org
and replace the existing one.
There are some things that can go wrong. Here we will try and outline the most frequent ones.
Certain PHP distributions have restrictive default Phar settings. Setting the following may help.
detect_unicode = Off
phar.readonly = Off
phar.require_hash = Off
If you are on Suhosin you will also have to set this:
suhosin.executor.include.whitelist = phar
Note
Ubuntu's PHP ships with Suhosin, so if you are using Ubuntu, you will need this change.
Some PHP installations have a bug that throws a PharException
when trying
to include the Phar. It will also tell you that Silex\Application
could not
be found. A workaround is using the following include line:
require_once 'phar://'.__DIR__.'/silex.phar/autoload.php';
The exact cause of this issue could not be determined yet.
Ioncube loader is an extension that can decode PHP encoded file. Unfortunately, old versions (prior to version 4.0.9) are not working well with phar archives. You must either upgrade Ioncube loader to version 4.0.9 or newer or disable it by commenting or removing this line in your php.ini file:
zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so
If you are using the Internet Information Services from Windows, you can use
this sample web.config
file:
<?xml version="1.0"?>
<configuration>
<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="index.php" />
</files>
</defaultDocument>
<rewrite>
<rules>
<rule name="Silex Front Controller" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>