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:
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';
$app = new Silex\Application();
......@@ -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
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
back to the client.
Finally, the app is run. Visit ``/hello/world`` to see the result. It's really
that easy!
Installing Silex is as easy as it can get. `Download`_ the archive file,
extract it, and you're done!
Installation
------------
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/
.. _Pimple: http://pimple.sensiolabs.org/
.. _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
you would if there was only one connection. Given the above configuration,
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::
......
......@@ -18,8 +18,8 @@ it, you should have the following directory structure:
└── web
└── index.php
If you want more flexibility, use Composer instead. Create a
``composer.json``:
If you want more flexibility, use Composer_ instead. Create a
``composer.json`` file and put this in it:
.. code-block:: json
......@@ -60,17 +60,16 @@ file and create an instance of ``Silex\Application``. After your controller
definitions, call the ``run`` method on your application::
// web/index.php
require_once __DIR__.'/../vendor/autoload.php';
$app = new Silex\Application();
// definitions
// ... definitions
$app->run();
Then, you have to configure your web server (read the :doc:`dedicated chapter
<web_servers>` for more information).
Then, you have to configure your web server (read the
:doc:`dedicated chapter <web_servers>` for more information).
.. tip::
......@@ -81,9 +80,9 @@ Then, you have to configure your web server (read the :doc:`dedicated chapter
.. tip::
If your application is hosted behind a reverse proxy at address $ip, and you
want Silex to trust the ``X-Forwarded-For*`` headers, you will need to run
your application like this::
If your application is hosted behind a reverse proxy at address ``$ip``,
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;
......@@ -109,7 +108,7 @@ A route pattern consists of:
The controller is defined using a closure like this::
function () {
// do something
// ... do something
}
Closures are anonymous functions that may import state from outside of their
......@@ -119,13 +118,13 @@ 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.
Closures that do not import scope are referred to as lambdas. Because all
anonymous functions are instances of the ``Closure`` class in PHP, the
documentation will not make a distinction here.
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::
......@@ -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``
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.
import the ``$blogPosts`` variable from the outer scope. This allows you to
use it from within the closure.
Dynamic routing
Dynamic Routing
~~~~~~~~~~~~~~~
Now, you can create another controller for viewing individual blog posts::
......@@ -176,15 +175,15 @@ closure.
The current ``Application`` is automatically injected by Silex to the Closure
thanks to the type hinting.
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
When the post does not exist, you are using ``abort()`` to stop the request
early. It actually throws an exception, which you will see how to handle later
on.
Example POST route
Example POST Route
~~~~~~~~~~~~~~~~~~
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\Response;
......@@ -204,14 +203,12 @@ It is pretty straightforward.
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
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_,
so you can fetch variables using the request ``get`` method.
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
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html>`_.
This allows setting an HTTP status code, in this case it is set to ``201
Created``.
Instead of returning a string you are returning an instance of Response_.
This allows setting an HTTP status code, in this case it is set to
``201 Created``.
.. note::
......@@ -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``::
$app->put('/blog/{id}', function ($id) {
...
// ...
});
$app->delete('/blog/{id}', function ($id) {
...
// ...
});
.. tip::
......@@ -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
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
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">
...
<!-- ... -->
<input type="hidden" id="_method" name="_method" value="PUT" />
</form>
If using Symfony Components 2.2+ you will need to explicitly enable this
method override::
If you are using Symfony Components 2.2+, you will need to explicitly
enable this method override::
use Symfony\Component\HttpFoundation\Request;
......@@ -256,16 +255,16 @@ You can also call ``match``, which will match all methods. This can be
restricted via the ``method`` method::
$app->match('/blog', function () {
...
// ...
});
$app->match('/blog', function () {
...
// ...
})
->method('PATCH');
$app->match('/blog', function () {
...
// ...
})
->method('PUT|POST');
......@@ -274,35 +273,34 @@ restricted via the ``method`` method::
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.
Route variables
Route Variables
~~~~~~~~~~~~~~~
As it has been shown before you can define variable parts in a route like
this::
$app->get('/blog/{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/{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)::
$app->get('/blog/{postId}/{commentId}', function ($commentId, $postId) {
...
// ...
});
You can also ask for the current Request and Application objects::
$app->get('/blog/{id}', function (Application $app, Request $request, $id) {
...
// ...
});
.. note::
......@@ -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::
$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
......@@ -396,33 +394,33 @@ The following will make sure the ``id`` argument is numeric, since ``\d+``
matches any amount of digits::
$app->get('/blog/{id}', function ($id) {
...
// ...
})
->assert('id', '\d+');
You can also chain these calls::
$app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) {
...
// ...
})
->assert('postId', '\d+')
->assert('commentId', '\d+');
Default values
Default Values
~~~~~~~~~~~~~~
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``.
Named routes
Named Routes
~~~~~~~~~~~~
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
``Controller`` object that is returned by the routing methods::
$app->get('/', function () {
...
// ...
})
->bind('homepage');
$app->get('/blog/{id}', function ($id) {
...
// ...
})
->bind('blog_post');
.. note::
It only makes sense to name routes if you use providers that make use of
the ``RouteCollection``.
Controllers in classes
Controllers in Classes
~~~~~~~~~~~~~~~~~~~~~~
If you don't want to use anonymous functions, you can also define your
controllers as methods. By using the ``ControllerClass::methodName`` syntax,
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 Symfony\Component\HttpFoundation\Request;
namespace Igorw
namespace Acme
{
class Foo
{
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
``Silex\Application`` type hints to get ``$request`` and ``$app`` injected.
......@@ -505,7 +502,7 @@ the defaults for new 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
......@@ -561,8 +558,7 @@ once a response is returned, the following handlers are ignored.
.. note::
Silex ships with a provider for `Monolog
<https://github.com/Seldaek/monolog>`_ which handles logging of errors.
Silex ships with a provider for Monolog_ which handles logging of errors.
Check out the *Providers* chapter for details.
.. tip::
......@@ -580,7 +576,7 @@ once a response is returned, the following handlers are ignored.
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
......@@ -645,6 +641,7 @@ response for you::
if (!$user) {
$error = array('message' => 'The user was not found.');
return $app->json($error, 404);
}
......@@ -784,3 +781,7 @@ to prevent Cross-Site-Scripting attacks.
});
.. _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
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.
*
......
......@@ -139,6 +139,19 @@ class ControllerCollection
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)
{
if (!method_exists($this->defaultRoute, $method)) {
......
......@@ -46,6 +46,9 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$returnValue = $app->put('/foo', function () {});
$this->assertInstanceOf('Silex\Controller', $returnValue);
$returnValue = $app->patch('/foo', function () {});
$this->assertInstanceOf('Silex\Controller', $returnValue);
$returnValue = $app->delete('/foo', function () {});
$this->assertInstanceOf('Silex\Controller', $returnValue);
}
......@@ -146,7 +149,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$app = new Application();
$app['pass'] = false;
$app->on('test', function(Event $e) use ($app) {
$app->on('test', function (Event $e) use ($app) {
$app['pass'] = true;
});
......@@ -380,7 +383,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$app->get('/foo', function () use (&$containerTarget) {
$containerTarget[] = '1_routeTriggered';
return new StreamedResponse(function() use (&$containerTarget) {
return new StreamedResponse(function () use (&$containerTarget) {
$containerTarget[] = '3_responseSent';
});
});
......@@ -553,7 +556,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
ErrorHandler::register();
$app['monolog.logfile'] = 'php://memory';
$app->register(new MonologServiceProvider());
$app->get('/foo/', function() { return 'ok'; });
$app->get('/foo/', function () { return 'ok'; });
$response = $app->handle(Request::create('/foo'));
$this->assertEquals(301, $response->getStatusCode());
......
......@@ -130,6 +130,10 @@ class RouterTest extends \PHPUnit_Framework_TestCase
return 'put resource';
});
$app->patch('/resource', function () {
return 'patch resource';
});
$app->delete('/resource', function () {
return 'delete resource';
});
......@@ -140,6 +144,7 @@ class RouterTest extends \PHPUnit_Framework_TestCase
$this->checkRouteResponse($app, '/resource', 'get resource');
$this->checkRouteResponse($app, '/resource', 'post resource', 'post');
$this->checkRouteResponse($app, '/resource', 'put resource', 'put');
$this->checkRouteResponse($app, '/resource', 'patch resource', 'patch');
$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