Commit 8a509371 authored by Fabien Potencier's avatar Fabien Potencier

feature #922 Various fixes (fabpot)

This PR was merged into the 1.2.x-dev branch.

Discussion
----------

Various fixes

Some merged PRs from fabpot/Silex repo.

Commits
-------

72aae9e1 fixed CS
e4e0fef3 fixed wrong merge
33903336 minor #4 Changed fetchAssoc to fetchAll (gelbander)
c119f90e Merge remote-tracking branch 'WouterJ/doc_fixes'
19265c0c feature #7 Provides support for the PATCH method for HTTP (ramsey)
0f6956d1 Provides support for the PATCH method for HTTP
919c32c7 Some doc fixes
03839264 Changed fetchAssoc to fetchAll
parents 40398077 72aae9e1
...@@ -18,10 +18,12 @@ aims to be: ...@@ -18,10 +18,12 @@ aims to be:
In a nutshell, you define controllers and map them to routes, all in one step. In a nutshell, you define controllers and map them to routes, all in one step.
**Let's go!**:: Usage
-----
// web/index.php .. code-block:: php
// web/index.php
require_once __DIR__.'/../vendor/autoload.php'; require_once __DIR__.'/../vendor/autoload.php';
$app = new Silex\Application(); $app = new Silex\Application();
...@@ -35,17 +37,23 @@ In a nutshell, you define controllers and map them to routes, all in one step. ...@@ -35,17 +37,23 @@ In a nutshell, you define controllers and map them to routes, all in one step.
All that is needed to get access to the Framework is to include the All that is needed to get access to the Framework is to include the
autoloader. autoloader.
Next we define a route to ``/hello/{name}`` that matches for ``GET`` requests. Next a route for ``/hello/{name}`` that matches for ``GET`` requests is defined.
When the route matches, the function is executed and the return value is sent When the route matches, the function is executed and the return value is sent
back to the client. back to the client.
Finally, the app is run. Visit ``/hello/world`` to see the result. It's really Finally, the app is run. Visit ``/hello/world`` to see the result. It's really
that easy! that easy!
Installing Silex is as easy as it can get. `Download`_ the archive file, Installation
extract it, and you're done! ------------
Installing Silex is as easy as it can get. The recommend method is using
Composer_ and requiring `silex/silex`_. Another way is to `download`_ the
archive file and extract it.
.. _Download: http://silex.sensiolabs.org/download
.. _Symfony2: http://symfony.com/ .. _Symfony2: http://symfony.com/
.. _Pimple: http://pimple.sensiolabs.org/ .. _Pimple: http://pimple.sensiolabs.org/
.. _Sinatra: http://www.sinatrarb.com/ .. _Sinatra: http://www.sinatrarb.com/
.. _Composer: http://getcomposer.org/
.. _`download`: http://silex.sensiolabs.org/download
.. _`silex/silex`: https://packagist.org/packages/silex/silex
...@@ -123,9 +123,9 @@ The first registered connection is the default and can simply be accessed as ...@@ -123,9 +123,9 @@ The first registered connection is the default and can simply be accessed as
you would if there was only one connection. Given the above configuration, you would if there was only one connection. Given the above configuration,
these two lines are equivalent:: these two lines are equivalent::
$app['db']->fetchAssoc('SELECT * FROM table'); $app['db']->fetchAll('SELECT * FROM table');
$app['dbs']['mysql_read']->fetchAssoc('SELECT * FROM table'); $app['dbs']['mysql_read']->fetchAll('SELECT * FROM table');
Using multiple connections:: Using multiple connections::
......
...@@ -18,8 +18,8 @@ it, you should have the following directory structure: ...@@ -18,8 +18,8 @@ it, you should have the following directory structure:
└── web └── web
└── index.php └── index.php
If you want more flexibility, use Composer instead. Create a If you want more flexibility, use Composer_ instead. Create a
``composer.json``: ``composer.json`` file and put this in it:
.. code-block:: json .. code-block:: json
...@@ -60,17 +60,16 @@ file and create an instance of ``Silex\Application``. After your controller ...@@ -60,17 +60,16 @@ file and create an instance of ``Silex\Application``. After your controller
definitions, call the ``run`` method on your application:: definitions, call the ``run`` method on your application::
// web/index.php // web/index.php
require_once __DIR__.'/../vendor/autoload.php'; require_once __DIR__.'/../vendor/autoload.php';
$app = new Silex\Application(); $app = new Silex\Application();
// definitions // ... definitions
$app->run(); $app->run();
Then, you have to configure your web server (read the :doc:`dedicated chapter Then, you have to configure your web server (read the
<web_servers>` for more information). :doc:`dedicated chapter <web_servers>` for more information).
.. tip:: .. tip::
...@@ -81,9 +80,9 @@ Then, you have to configure your web server (read the :doc:`dedicated chapter ...@@ -81,9 +80,9 @@ Then, you have to configure your web server (read the :doc:`dedicated chapter
.. tip:: .. tip::
If your application is hosted behind a reverse proxy at address $ip, and you If your application is hosted behind a reverse proxy at address ``$ip``,
want Silex to trust the ``X-Forwarded-For*`` headers, you will need to run and you want Silex to trust the ``X-Forwarded-For*`` headers, you will
your application like this:: need to run your application like this::
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
...@@ -109,7 +108,7 @@ A route pattern consists of: ...@@ -109,7 +108,7 @@ A route pattern consists of:
The controller is defined using a closure like this:: The controller is defined using a closure like this::
function () { function () {
// do something // ... do something
} }
Closures are anonymous functions that may import state from outside of their Closures are anonymous functions that may import state from outside of their
...@@ -119,13 +118,13 @@ import local variables of that function. ...@@ -119,13 +118,13 @@ import local variables of that function.
.. note:: .. note::
Closures that do not import scope are referred to as lambdas. Because in Closures that do not import scope are referred to as lambdas. Because all
PHP all anonymous functions are instances of the ``Closure`` class, we anonymous functions are instances of the ``Closure`` class in PHP, the
will not make a distinction here. documentation will not make a distinction here.
The return value of the closure becomes the content of the page. The return value of the closure becomes the content of the page.
Example GET route Example GET Route
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
Here is an example definition of a ``GET`` route:: Here is an example definition of a ``GET`` route::
...@@ -151,10 +150,10 @@ Here is an example definition of a ``GET`` route:: ...@@ -151,10 +150,10 @@ Here is an example definition of a ``GET`` route::
Visiting ``/blog`` will return a list of blog post titles. The ``use`` Visiting ``/blog`` will return a list of blog post titles. The ``use``
statement means something different in this context. It tells the closure to 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 import the ``$blogPosts`` variable from the outer scope. This allows you to
from within the closure. use it from within the closure.
Dynamic routing Dynamic Routing
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
Now, you can create another controller for viewing individual blog posts:: Now, you can create another controller for viewing individual blog posts::
...@@ -176,15 +175,15 @@ closure. ...@@ -176,15 +175,15 @@ closure.
The current ``Application`` is automatically injected by Silex to the Closure The current ``Application`` is automatically injected by Silex to the Closure
thanks to the type hinting. thanks to the type hinting.
When the post does not exist, we are using ``abort()`` to stop the request When the post does not exist, you are using ``abort()`` to stop the request
early. It actually throws an exception, which we will see how to handle later early. It actually throws an exception, which you will see how to handle later
on. on.
Example POST route Example POST Route
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
POST routes signify the creation of a resource. An example for this is a 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:: feedback form. You will use the ``mail`` function to send an e-mail::
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
...@@ -204,14 +203,12 @@ It is pretty straightforward. ...@@ -204,14 +203,12 @@ It is pretty straightforward.
included that you can use instead of ``mail()``. included that you can use instead of ``mail()``.
The current ``request`` is automatically injected by Silex to the Closure The current ``request`` is automatically injected by Silex to the Closure
thanks to the type hinting. It is an instance of `Request thanks to the type hinting. It is an instance of
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_, Request_, so you can fetch variables using the request ``get`` method.
so you can fetch variables using the request ``get`` method.
Instead of returning a string we are returning an instance of `Response Instead of returning a string you are returning an instance of Response_.
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html>`_. This allows setting an HTTP status code, in this case it is set to
This allows setting an HTTP status code, in this case it is set to ``201 ``201 Created``.
Created``.
.. note:: .. note::
...@@ -225,11 +222,11 @@ You can create controllers for most HTTP methods. Just call one of these ...@@ -225,11 +222,11 @@ You can create controllers for most HTTP methods. Just call one of these
methods on your application: ``get``, ``post``, ``put``, ``delete``:: methods on your application: ``get``, ``post``, ``put``, ``delete``::
$app->put('/blog/{id}', function ($id) { $app->put('/blog/{id}', function ($id) {
... // ...
}); });
$app->delete('/blog/{id}', function ($id) { $app->delete('/blog/{id}', function ($id) {
... // ...
}); });
.. tip:: .. tip::
...@@ -237,15 +234,17 @@ methods on your application: ``get``, ``post``, ``put``, ``delete``:: ...@@ -237,15 +234,17 @@ methods on your application: ``get``, ``post``, ``put``, ``delete``::
Forms in most web browsers do not directly support the use of other HTTP Forms in most web browsers do not directly support the use of other HTTP
methods. To use methods other than GET and POST you can utilize a special methods. To use methods other than GET and POST you can utilize a special
form field with a name of ``_method``. The form's ``method`` attribute must form field with a name of ``_method``. The form's ``method`` attribute must
be set to POST when using this field:: be set to POST when using this field:
.. code-block:: html
<form action="/my/target/route/" method="post"> <form action="/my/target/route/" method="post">
... <!-- ... -->
<input type="hidden" id="_method" name="_method" value="PUT" /> <input type="hidden" id="_method" name="_method" value="PUT" />
</form> </form>
If using Symfony Components 2.2+ you will need to explicitly enable this If you are using Symfony Components 2.2+, you will need to explicitly
method override:: enable this method override::
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
...@@ -256,16 +255,16 @@ You can also call ``match``, which will match all methods. This can be ...@@ -256,16 +255,16 @@ You can also call ``match``, which will match all methods. This can be
restricted via the ``method`` method:: restricted via the ``method`` method::
$app->match('/blog', function () { $app->match('/blog', function () {
... // ...
}); });
$app->match('/blog', function () { $app->match('/blog', function () {
... // ...
}) })
->method('PATCH'); ->method('PATCH');
$app->match('/blog', function () { $app->match('/blog', function () {
... // ...
}) })
->method('PUT|POST'); ->method('PUT|POST');
...@@ -274,35 +273,34 @@ restricted via the ``method`` method:: ...@@ -274,35 +273,34 @@ restricted via the ``method`` method::
The order in which the routes are defined is significant. The first 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. matching route will be used, so place more generic routes at the bottom.
Route Variables
Route variables
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
As it has been shown before you can define variable parts in a route like As it has been shown before you can define variable parts in a route like
this:: this::
$app->get('/blog/{id}', function ($id) { $app->get('/blog/{id}', function ($id) {
... // ...
}); });
It is also possible to have more than one variable part, just make sure the It is also possible to have more than one variable part, just make sure the
closure arguments match the names of the variable parts:: closure arguments match the names of the variable parts::
$app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) { $app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) {
... // ...
}); });
While it's not suggested, you could also do this (note the switched While it's not recommend, you could also do this (note the switched
arguments):: arguments)::
$app->get('/blog/{postId}/{commentId}', function ($commentId, $postId) { $app->get('/blog/{postId}/{commentId}', function ($commentId, $postId) {
... // ...
}); });
You can also ask for the current Request and Application objects:: You can also ask for the current Request and Application objects::
$app->get('/blog/{id}', function (Application $app, Request $request, $id) { $app->get('/blog/{id}', function (Application $app, Request $request, $id) {
... // ...
}); });
.. note:: .. note::
...@@ -311,10 +309,10 @@ You can also ask for the current Request and Application objects:: ...@@ -311,10 +309,10 @@ You can also ask for the current Request and Application objects::
based on the type hinting and not on the variable name:: based on the type hinting and not on the variable name::
$app->get('/blog/{id}', function (Application $foo, Request $bar, $id) { $app->get('/blog/{id}', function (Application $foo, Request $bar, $id) {
... // ...
}); });
Route variables converters Route Variables Converters
~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
Before injecting the route variables into the controller, you can apply some Before injecting the route variables into the controller, you can apply some
...@@ -396,33 +394,33 @@ The following will make sure the ``id`` argument is numeric, since ``\d+`` ...@@ -396,33 +394,33 @@ The following will make sure the ``id`` argument is numeric, since ``\d+``
matches any amount of digits:: matches any amount of digits::
$app->get('/blog/{id}', function ($id) { $app->get('/blog/{id}', function ($id) {
... // ...
}) })
->assert('id', '\d+'); ->assert('id', '\d+');
You can also chain these calls:: You can also chain these calls::
$app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) { $app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) {
... // ...
}) })
->assert('postId', '\d+') ->assert('postId', '\d+')
->assert('commentId', '\d+'); ->assert('commentId', '\d+');
Default values Default Values
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
You can define a default value for any route variable by calling ``value`` on You can define a default value for any route variable by calling ``value`` on
the ``Controller`` object:: the ``Controller`` object::
$app->get('/{pageName}', function ($pageName) { $app->get('/{pageName}', function ($pageName) {
... // ...
}) })
->value('pageName', 'index'); ->value('pageName', 'index');
This will allow matching ``/``, in which case the ``pageName`` variable will This will allow matching ``/``, in which case the ``pageName`` variable will
have the value ``index``. have the value ``index``.
Named routes Named Routes
~~~~~~~~~~~~ ~~~~~~~~~~~~
Some providers (such as ``UrlGeneratorProvider``) can make use of named Some providers (such as ``UrlGeneratorProvider``) can make use of named
...@@ -431,45 +429,44 @@ really be used. You can give a route a name by calling ``bind`` on the ...@@ -431,45 +429,44 @@ really be used. You can give a route a name by calling ``bind`` on the
``Controller`` object that is returned by the routing methods:: ``Controller`` object that is returned by the routing methods::
$app->get('/', function () { $app->get('/', function () {
... // ...
}) })
->bind('homepage'); ->bind('homepage');
$app->get('/blog/{id}', function ($id) { $app->get('/blog/{id}', function ($id) {
... // ...
}) })
->bind('blog_post'); ->bind('blog_post');
.. note:: .. note::
It only makes sense to name routes if you use providers that make use of It only makes sense to name routes if you use providers that make use of
the ``RouteCollection``. the ``RouteCollection``.
Controllers in classes Controllers in Classes
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
If you don't want to use anonymous functions, you can also define your If you don't want to use anonymous functions, you can also define your
controllers as methods. By using the ``ControllerClass::methodName`` syntax, controllers as methods. By using the ``ControllerClass::methodName`` syntax,
you can tell Silex to lazily create the controller object for you:: you can tell Silex to lazily create the controller object for you::
$app->get('/', 'Igorw\\Foo::bar'); $app->get('/', 'Acme\\Foo::bar');
use Silex\Application; use Silex\Application;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
namespace Igorw namespace Acme
{ {
class Foo class Foo
{ {
public function bar(Request $request, Application $app) public function bar(Request $request, Application $app)
{ {
... // ...
} }
} }
} }
This will load the ``Igorw\Foo`` class on demand, create an instance and call This will load the ``Acme\Foo`` class on demand, create an instance and call
the ``bar`` method to get the response. You can use ``Request`` and the ``bar`` method to get the response. You can use ``Request`` and
``Silex\Application`` type hints to get ``$request`` and ``$app`` injected. ``Silex\Application`` type hints to get ``$request`` and ``$app`` injected.
...@@ -505,7 +502,7 @@ the defaults for new controllers. ...@@ -505,7 +502,7 @@ the defaults for new controllers.
The converters are run for **all** registered controllers. The converters are run for **all** registered controllers.
Error handlers Error Handlers
-------------- --------------
If some part of your code throws an exception you will want to display some If some part of your code throws an exception you will want to display some
...@@ -561,8 +558,7 @@ once a response is returned, the following handlers are ignored. ...@@ -561,8 +558,7 @@ once a response is returned, the following handlers are ignored.
.. note:: .. note::
Silex ships with a provider for `Monolog Silex ships with a provider for Monolog_ which handles logging of errors.
<https://github.com/Seldaek/monolog>`_ which handles logging of errors.
Check out the *Providers* chapter for details. Check out the *Providers* chapter for details.
.. tip:: .. tip::
...@@ -580,7 +576,7 @@ once a response is returned, the following handlers are ignored. ...@@ -580,7 +576,7 @@ once a response is returned, the following handlers are ignored.
return; return;
} }
// logic to handle the error and return a Response // ... logic to handle the error and return a Response
}); });
The error handlers are also called when you use ``abort`` to abort a request The error handlers are also called when you use ``abort`` to abort a request
...@@ -645,6 +641,7 @@ response for you:: ...@@ -645,6 +641,7 @@ response for you::
if (!$user) { if (!$user) {
$error = array('message' => 'The user was not found.'); $error = array('message' => 'The user was not found.');
return $app->json($error, 404); return $app->json($error, 404);
} }
...@@ -784,3 +781,7 @@ to prevent Cross-Site-Scripting attacks. ...@@ -784,3 +781,7 @@ to prevent Cross-Site-Scripting attacks.
}); });
.. _download: http://silex.sensiolabs.org/download .. _download: http://silex.sensiolabs.org/download
.. _Composer: http://getcomposer.org/
.. _Request: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html
.. _Response: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html
.. _Monolog: https://github.com/Seldaek/monolog
...@@ -259,6 +259,19 @@ class Application extends \Pimple implements HttpKernelInterface, TerminableInte ...@@ -259,6 +259,19 @@ class Application extends \Pimple implements HttpKernelInterface, TerminableInte
return $this['controllers']->delete($pattern, $to); return $this['controllers']->delete($pattern, $to);
} }
/**
* Maps a PATCH request to a callable.
*
* @param string $pattern Matched route pattern
* @param mixed $to Callback that returns the response when matched
*
* @return Controller
*/
public function patch($pattern, $to = null)
{
return $this['controllers']->patch($pattern, $to);
}
/** /**
* Adds an event listener that listens on the specified events. * Adds an event listener that listens on the specified events.
* *
......
...@@ -139,6 +139,19 @@ class ControllerCollection ...@@ -139,6 +139,19 @@ class ControllerCollection
return $this->match($pattern, $to)->method('DELETE'); return $this->match($pattern, $to)->method('DELETE');
} }
/**
* Maps a PATCH request to a callable.
*
* @param string $pattern Matched route pattern
* @param mixed $to Callback that returns the response when matched
*
* @return Controller
*/
public function patch($pattern, $to = null)
{
return $this->match($pattern, $to)->method('PATCH');
}
public function __call($method, $arguments) public function __call($method, $arguments)
{ {
if (!method_exists($this->defaultRoute, $method)) { if (!method_exists($this->defaultRoute, $method)) {
......
...@@ -46,6 +46,9 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase ...@@ -46,6 +46,9 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$returnValue = $app->put('/foo', function () {}); $returnValue = $app->put('/foo', function () {});
$this->assertInstanceOf('Silex\Controller', $returnValue); $this->assertInstanceOf('Silex\Controller', $returnValue);
$returnValue = $app->patch('/foo', function () {});
$this->assertInstanceOf('Silex\Controller', $returnValue);
$returnValue = $app->delete('/foo', function () {}); $returnValue = $app->delete('/foo', function () {});
$this->assertInstanceOf('Silex\Controller', $returnValue); $this->assertInstanceOf('Silex\Controller', $returnValue);
} }
...@@ -146,7 +149,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase ...@@ -146,7 +149,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$app = new Application(); $app = new Application();
$app['pass'] = false; $app['pass'] = false;
$app->on('test', function(Event $e) use ($app) { $app->on('test', function (Event $e) use ($app) {
$app['pass'] = true; $app['pass'] = true;
}); });
...@@ -380,7 +383,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase ...@@ -380,7 +383,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$app->get('/foo', function () use (&$containerTarget) { $app->get('/foo', function () use (&$containerTarget) {
$containerTarget[] = '1_routeTriggered'; $containerTarget[] = '1_routeTriggered';
return new StreamedResponse(function() use (&$containerTarget) { return new StreamedResponse(function () use (&$containerTarget) {
$containerTarget[] = '3_responseSent'; $containerTarget[] = '3_responseSent';
}); });
}); });
...@@ -553,7 +556,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase ...@@ -553,7 +556,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
ErrorHandler::register(); ErrorHandler::register();
$app['monolog.logfile'] = 'php://memory'; $app['monolog.logfile'] = 'php://memory';
$app->register(new MonologServiceProvider()); $app->register(new MonologServiceProvider());
$app->get('/foo/', function() { return 'ok'; }); $app->get('/foo/', function () { return 'ok'; });
$response = $app->handle(Request::create('/foo')); $response = $app->handle(Request::create('/foo'));
$this->assertEquals(301, $response->getStatusCode()); $this->assertEquals(301, $response->getStatusCode());
......
...@@ -130,6 +130,10 @@ class RouterTest extends \PHPUnit_Framework_TestCase ...@@ -130,6 +130,10 @@ class RouterTest extends \PHPUnit_Framework_TestCase
return 'put resource'; return 'put resource';
}); });
$app->patch('/resource', function () {
return 'patch resource';
});
$app->delete('/resource', function () { $app->delete('/resource', function () {
return 'delete resource'; return 'delete resource';
}); });
...@@ -140,6 +144,7 @@ class RouterTest extends \PHPUnit_Framework_TestCase ...@@ -140,6 +144,7 @@ class RouterTest extends \PHPUnit_Framework_TestCase
$this->checkRouteResponse($app, '/resource', 'get resource'); $this->checkRouteResponse($app, '/resource', 'get resource');
$this->checkRouteResponse($app, '/resource', 'post resource', 'post'); $this->checkRouteResponse($app, '/resource', 'post resource', 'post');
$this->checkRouteResponse($app, '/resource', 'put resource', 'put'); $this->checkRouteResponse($app, '/resource', 'put resource', 'put');
$this->checkRouteResponse($app, '/resource', 'patch resource', 'patch');
$this->checkRouteResponse($app, '/resource', 'delete resource', 'delete'); $this->checkRouteResponse($app, '/resource', 'delete resource', 'delete');
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment