Commit 1798d08f authored by Igor Wiedler's avatar Igor Wiedler

Merge branch 'master' into swiftmailer-test

* master:
  made monolog logger as the default logger when the monolog bridge is installed
  added the ServiceProviderInterface::boot() method
  fixed some skipped comments
  fixed Twig provider
  removed the Symfony bridges provider as it is not needed anymore with Composer
  added the possibility to register a logger for internal services
  fixed markup in the docs
  removed translation.messages in favor of translation.domains
  Remove dependency on the ClassLoader component.
parents 5ed7e45f 410c1ddd
{ {
"hash": "d22b382f0e20509713b06e6991c82e07", "hash": "142455bfe45755d318953c04880d43f2",
"packages": [ "packages": [
{ {
"package": "pimple/pimple", "package": "pimple/pimple",
...@@ -25,11 +25,6 @@ ...@@ -25,11 +25,6 @@
"version": "dev-master", "version": "dev-master",
"source-reference": "6ab1b5f07dd972d2c5e3b5d48c9776d0823149c7" "source-reference": "6ab1b5f07dd972d2c5e3b5d48c9776d0823149c7"
}, },
{
"package": "symfony/class-loader",
"version": "dev-master",
"source-reference": "c57e62c886899f8d88264efad23c857eb198dc09"
},
{ {
"package": "symfony/class-loader", "package": "symfony/class-loader",
"version": "dev-master", "version": "dev-master",
...@@ -88,14 +83,14 @@ ...@@ -88,14 +83,14 @@
{ {
"package": "symfony/http-foundation", "package": "symfony/http-foundation",
"version": "dev-master", "version": "dev-master",
"source-reference": "5b1581418381f46679604fd19efc8b518f2390ae", "source-reference": "5b1581418381f46679604fd19efc8b518f2390ae"
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
}, },
{ {
"package": "symfony/http-foundation", "package": "symfony/http-foundation",
"version": "dev-master", "version": "dev-master",
"source-reference": "5b1581418381f46679604fd19efc8b518f2390ae" "source-reference": "5b1581418381f46679604fd19efc8b518f2390ae",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
}, },
{ {
"package": "symfony/http-kernel", "package": "symfony/http-kernel",
...@@ -124,21 +119,28 @@ ...@@ -124,21 +119,28 @@
{ {
"package": "symfony/routing", "package": "symfony/routing",
"version": "dev-master", "version": "dev-master",
"source-reference": "aec133671d79d530d09aa0b5ca4cea9e4ee4b274" "source-reference": "aec133671d79d530d09aa0b5ca4cea9e4ee4b274",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
}, },
{ {
"package": "symfony/routing", "package": "symfony/routing",
"version": "dev-master", "version": "dev-master",
"source-reference": "aec133671d79d530d09aa0b5ca4cea9e4ee4b274", "source-reference": "aec133671d79d530d09aa0b5ca4cea9e4ee4b274"
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
} }
], ],
"packages-dev": [ "packages-dev": [
{ {
"package": "doctrine/common", "package": "doctrine/common",
"version": "2.2.x-dev", "version": "dev-master",
"source-reference": "1e0aa60d109c630d19543d999f12e2852ef8f932" "source-reference": "8b403cde97eaede30bd79acab4f18895fd5bdf27",
"alias-pretty-version": "2.3.x-dev",
"alias-version": "2.3.9999999.9999999-dev"
},
{
"package": "doctrine/common",
"version": "dev-master",
"source-reference": "8b403cde97eaede30bd79acab4f18895fd5bdf27"
}, },
{ {
"package": "doctrine/dbal", "package": "doctrine/dbal",
...@@ -147,20 +149,19 @@ ...@@ -147,20 +149,19 @@
}, },
{ {
"package": "monolog/monolog", "package": "monolog/monolog",
"version": "dev-master", "version": "1.1.0"
"source-reference": "2eb0c0978d290a1c45346a1955188929cb4e5db7"
}, },
{ {
"package": "symfony/form", "package": "symfony/form",
"version": "dev-master", "version": "dev-master",
"source-reference": "f410a9e3440b1248f8941d332d9e65b80e573ec5", "source-reference": "f410a9e3440b1248f8941d332d9e65b80e573ec5"
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
}, },
{ {
"package": "symfony/form", "package": "symfony/form",
"version": "dev-master", "version": "dev-master",
"source-reference": "f410a9e3440b1248f8941d332d9e65b80e573ec5" "source-reference": "f410a9e3440b1248f8941d332d9e65b80e573ec5",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
}, },
{ {
"package": "symfony/locale", "package": "symfony/locale",
...@@ -175,9 +176,14 @@ ...@@ -175,9 +176,14 @@
"source-reference": "6a9e36fef0eedcd52b0dde361faab38f4615fe48" "source-reference": "6a9e36fef0eedcd52b0dde361faab38f4615fe48"
}, },
{ {
"package": "symfony/options-resolver", "package": "symfony/monolog-bridge",
"version": "dev-master", "version": "dev-master",
"source-reference": "92c19fb262283a348093bbd32b5c5dfef8f00612", "source-reference": "7ef27aa6b88447989fe5da551c2d2746d4cf60aa"
},
{
"package": "symfony/monolog-bridge",
"version": "dev-master",
"source-reference": "7ef27aa6b88447989fe5da551c2d2746d4cf60aa",
"alias-pretty-version": "2.1.x-dev", "alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev" "alias-version": "2.1.9999999.9999999-dev"
}, },
...@@ -187,9 +193,11 @@ ...@@ -187,9 +193,11 @@
"source-reference": "92c19fb262283a348093bbd32b5c5dfef8f00612" "source-reference": "92c19fb262283a348093bbd32b5c5dfef8f00612"
}, },
{ {
"package": "symfony/translation", "package": "symfony/options-resolver",
"version": "dev-master", "version": "dev-master",
"source-reference": "f89af1b91bbb9ee9151e3516aeaf1eda7992a369" "source-reference": "92c19fb262283a348093bbd32b5c5dfef8f00612",
"alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev"
}, },
{ {
"package": "symfony/translation", "package": "symfony/translation",
...@@ -198,6 +206,11 @@ ...@@ -198,6 +206,11 @@
"alias-pretty-version": "2.1.x-dev", "alias-pretty-version": "2.1.x-dev",
"alias-version": "2.1.9999999.9999999-dev" "alias-version": "2.1.9999999.9999999-dev"
}, },
{
"package": "symfony/translation",
"version": "dev-master",
"source-reference": "f89af1b91bbb9ee9151e3516aeaf1eda7992a369"
},
{ {
"package": "symfony/twig-bridge", "package": "symfony/twig-bridge",
"version": "dev-master", "version": "dev-master",
...@@ -225,7 +238,7 @@ ...@@ -225,7 +238,7 @@
{ {
"package": "twig/twig", "package": "twig/twig",
"version": "dev-master", "version": "dev-master",
"source-reference": "e28663efd83d76ee36bf9e8a790f75bd880abbf2", "source-reference": "45d0f6fe67e2199d1148cf1c7e832674af4e8e93",
"alias-pretty-version": "1.8.x-dev", "alias-pretty-version": "1.8.x-dev",
"alias-version": "1.8.9999999.9999999-dev" "alias-version": "1.8.9999999.9999999-dev"
}, },
...@@ -239,7 +252,7 @@ ...@@ -239,7 +252,7 @@
{ {
"package": "twig/twig", "package": "twig/twig",
"version": "dev-master", "version": "dev-master",
"source-reference": "e28663efd83d76ee36bf9e8a790f75bd880abbf2" "source-reference": "45d0f6fe67e2199d1148cf1c7e832674af4e8e93"
} }
], ],
"aliases": [ "aliases": [
......
...@@ -3,6 +3,13 @@ Changelog ...@@ -3,6 +3,13 @@ Changelog
This changelog references all backward incompatibilities as we introduce them: This changelog references all backward incompatibilities as we introduce them:
* **2012-05-26**: added ``boot()`` to ``ServiceProviderInterface``
* **2012-05-26**: Removed ``SymfonyBridgesServiceProvider``
* **2012-05-26**: Removed the ``translator.messages`` parameter (use
``translator.domains`` instead).
* **2012-05-24**: Removed the ``autoloader`` service (use composer instead). * **2012-05-24**: Removed the ``autoloader`` service (use composer instead).
The ``*.class_path`` settings on all the built-in providers have also been The ``*.class_path`` settings on all the built-in providers have also been
removed in favor of Composer. removed in favor of Composer.
......
...@@ -4,31 +4,32 @@ Disable CSRF Protection on a form using the FormExtension ...@@ -4,31 +4,32 @@ Disable CSRF Protection on a form using the FormExtension
The *FormExtension* provides a service for building form in your application The *FormExtension* provides a service for building form in your application
with the Symfony2 Form component. By default, the *FormExtension* uses the with the Symfony2 Form component. By default, the *FormExtension* uses the
CSRF Protection avoiding Cross-site request forgery, a method by which a CSRF Protection avoiding Cross-site request forgery, a method by which a
malicious user attempts to make your legitimate users unknowingly submit malicious user attempts to make your legitimate users unknowingly submit data
data that they don't intend to submit. that they don't intend to submit.
You can find more details about CSRF Protection and CSRF token in the `Symfony2 Book: You can find more details about CSRF Protection and CSRF token in the
<http://symfony.com/doc/current/book/forms.html#csrf-protection>` `Symfony2 Book
<http://symfony.com/doc/current/book/forms.html#csrf-protection>`_.
In some cases (for example, when embedding a form in an html email) you might want In some cases (for example, when embedding a form in an html email) you might
not to use this protection. The easiest way to avoid this is to understand that it want not to use this protection. The easiest way to avoid this is to
is possible to give specific options to your form builder through the `createBuilder()` function. understand that it is possible to give specific options to your form builder
through the ``createBuilder()`` function.
Example Example
------- -------
:: .. code-block:: php
$form = $app['form.factory']->createBuilder('form', null, array('csrf_protection' => false)); $form = $app['form.factory']->createBuilder('form', null, array('csrf_protection' => false));
That's it, your form could be submited from everywhere without CSRF Protection. That's it, your form could be submited from everywhere without CSRF Protection.
Going further
-------------
Going further.. This specific example showed how to change the ``csrf_protection`` in the
--------------- ``$options`` parameter of the ``createBuilder()`` function. More of them could
be passed through this parameter, it is as simple as using the Symfony2
This specific example showed how to change the `csrf_protection` in the `$options` ``getDefaultOptions()`` method in your form classes. `See more here
parameter of the `createBuilder()` function. More of them could be passed through <http://symfony.com/doc/current/book/forms.html#book-form-creating-form-classes>`_.
this parameter, it is as simple as using the Symfony2 `getDefaultOptions()` method
in your form classes. `See more here
<http://symfony.com/doc/current/book/forms.html#book-form-creating-form-classes>`
...@@ -22,8 +22,10 @@ Recipes ...@@ -22,8 +22,10 @@ Recipes
* :doc:`Translating Validation Messages<translating_validation_messages>`. * :doc:`Translating Validation Messages<translating_validation_messages>`.
* :doc:`How to use PdoSessionStorage to store sessions in the database <session_storage>`. * :doc:`How to use PdoSessionStorage to store sessions in the database
<session_storage>`.
* :doc:`How to disable the CSRF Protection on a form using the FormExtension <form_no_csrf>`. * :doc:`How to disable the CSRF Protection on a form using the FormExtension
<form_no_csrf>`.
* :doc:`How to use YAML to configure validation <validator_yaml>`. * :doc:`How to use YAML to configure validation <validator_yaml>`.
...@@ -16,9 +16,9 @@ Request ...@@ -16,9 +16,9 @@ Request
~~~~~~~ ~~~~~~~
In the request we send the data for the blog post as a JSON object. We also In the request we send the data for the blog post as a JSON object. We also
indicate that using the ``Content-Type`` header. indicate that using the ``Content-Type`` header:
:: .. code-block:: text
POST /blog/posts POST /blog/posts
Accept: application/json Accept: application/json
...@@ -32,9 +32,9 @@ Response ...@@ -32,9 +32,9 @@ Response
The server responds with a 201 status code, telling us that the post was The server responds with a 201 status code, telling us that the post was
created. It tells us the ``Content-Type`` of the response, which is also created. It tells us the ``Content-Type`` of the response, which is also
JSON. JSON:
:: .. code-block:: text
HTTP/1.1 201 Created HTTP/1.1 201 Created
Content-Type: application/json Content-Type: application/json
...@@ -51,9 +51,7 @@ begins with ``application/json``. Since we want to do this for every request, ...@@ -51,9 +51,7 @@ begins with ``application/json``. Since we want to do this for every request,
the easiest solution is to use a before filter. the easiest solution is to use a before filter.
We simply use ``json_decode`` to parse the content of the request and then We simply use ``json_decode`` to parse the content of the request and then
replace the request data on the ``$request`` object. replace the request data on the ``$request`` object:
::
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\ParameterBag;
...@@ -69,9 +67,7 @@ Controller implementation ...@@ -69,9 +67,7 @@ Controller implementation
------------------------- -------------------------
Our controller will create a new blog post from the data provided and will Our controller will create a new blog post from the data provided and will
return the post object, including its ``id``, as JSON. return the post object, including its ``id``, as JSON:
::
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
...@@ -91,9 +87,9 @@ Manual testing ...@@ -91,9 +87,9 @@ Manual testing
-------------- --------------
In order to manually test our API, we can use the ``curl`` command line In order to manually test our API, we can use the ``curl`` command line
utility, which allows sending HTTP requests. utility, which allows sending HTTP requests:
:: .. code-block:: bash
$ curl http://blog.lo/blog/posts -d '{"title":"Hello World!","body":"This is my first post!"}' -H 'Content-Type: application/json' $ curl http://blog.lo/blog/posts -d '{"title":"Hello World!","body":"This is my first post!"}' -H 'Content-Type: application/json'
{"id":"1","title":"Hello World!","body":"This is my first post!"} {"id":"1","title":"Hello World!","body":"This is my first post!"}
...@@ -7,15 +7,14 @@ medium to large websites use a database to store sessions instead of files, ...@@ -7,15 +7,14 @@ medium to large websites use a database to store sessions instead of files,
because databases are easier to use and scale in a multi-webserver because databases are easier to use and scale in a multi-webserver
environment. environment.
Symfony2's ``NativeSessionStorage`` has multiple storage handlers and one of them uses PDO to Symfony2's ``NativeSessionStorage`` has multiple storage handlers and one of
store sessions, ``PdoSessionHandler``. them uses PDO to store sessions, ``PdoSessionHandler``. To use it, replace the
To use it, replace the ``session.storage.handler`` service in your application like ``session.storage.handler`` service in your application like explained below.
explained below.
Example Example
------- -------
:: .. code-block:: php
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
...@@ -59,4 +58,4 @@ PdoSessionStorage needs a database table with 3 columns: ...@@ -59,4 +58,4 @@ PdoSessionStorage needs a database table with 3 columns:
You can find examples of SQL statements to create the session table in the You can find examples of SQL statements to create the session table in the
`Symfony2 cookbook `Symfony2 cookbook
<http://symfony.com/doc/current/cookbook/configuration/pdo_session_storage.html>` <http://symfony.com/doc/current/cookbook/configuration/pdo_session_storage.html>`_
Translating Validation Messages Translating Validation Messages
=============================== ===============================
When working with Symfony2 validator, a common task would be to show localized validation messages. When working with Symfony2 validator, a common task would be to show localized
validation messages.
In order to do that, you will need to register translator and point to translated resources: In order to do that, you will need to register translator and point to
translated resources::
::
$app->register(new Silex\Provider\TranslationServiceProvider(), array( $app->register(new Silex\Provider\TranslationServiceProvider(), array(
'locale' => 'sr_Latn', 'locale' => 'sr_Latn',
'translator.messages' => array() 'translator.domains' => array(),
)); ));
$app->before(function () use ($app) { $app->before(function () use ($app) {
$app['translator']->addLoader('xlf', new Symfony\Component\Translation\Loader\XliffFileLoader()); $app['translator']->addLoader('xlf', new Symfony\Component\Translation\Loader\XliffFileLoader());
$app['translator']->addResource('xlf', __DIR__ . '/vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.sr_Latn.xlf', 'sr_Latn', 'validators'); $app['translator']->addResource('xlf', __DIR__.'/vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.sr_Latn.xlf', 'sr_Latn', 'validators');
}); });
And that's all you need to load translations from Symfony2 ``xlf`` files. And that's all you need to load translations from Symfony2 ``xlf`` files.
\ No newline at end of file
...@@ -15,9 +15,7 @@ your ``composer.json`` file: ...@@ -15,9 +15,7 @@ your ``composer.json`` file:
} }
Next, you need to tell the Validation Service that you are not using Next, you need to tell the Validation Service that you are not using
``StaticMethodLoader`` to load your class metadata but a YAML file: ``StaticMethodLoader`` to load your class metadata but a YAML file::
.. code-block:: php
$app->register(new ValidatorServiceProvider()); $app->register(new ValidatorServiceProvider());
......
...@@ -10,68 +10,59 @@ Silex ...@@ -10,68 +10,59 @@ Silex
Application Application
~~~~~~~~~~~ ~~~~~~~~~~~
The application is the main interface to Silex. It The application is the main interface to Silex. It implements Symfony2's
implements Symfony2's `HttpKernelInterface `HttpKernelInterface
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernelInterface.html>`_, <http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernelInterface.html>`_,
so you can pass a `Request so you can pass a `Request
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_ <http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_
to the ``handle`` method and it will return a `Response to the ``handle`` method and it will return a `Response
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html>`_. <http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html>`_.
It extends the ``Pimple`` service container, allowing It extends the ``Pimple`` service container, allowing for flexibility on the
for flexibility on the outside as well as the inside. you outside as well as the inside. you could replace any service, and you are also
could replace any service, and you are also able to read able to read them.
them.
The application makes strong use of the `EventDispatcher The application makes strong use of the `EventDispatcher
<http://api.symfony.com/master/Symfony/Component/EventDispatcher/EventDispatcher.html>`_ <http://api.symfony.com/master/Symfony/Component/EventDispatcher/EventDispatcher.html>`_
to hook into the Symfony2 `HttpKernel to hook into the Symfony2 `HttpKernel
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernel.html>`_ events. This allows <http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernel.html>`_
fetching the ``Request``, converting string responses into events. This allows fetching the ``Request``, converting string responses into
``Response`` objects and handling Exceptions. We also use it ``Response`` objects and handling Exceptions. We also use it to dispatch some
to dispatch some custom events like before/after filters and custom events like before/after filters and errors.
errors.
Controller Controller
~~~~~~~~~~ ~~~~~~~~~~
The Symfony2 `Route The Symfony2 `Route
<http://api.symfony.com/master/Symfony/Component/Routing/Route.html>`_ <http://api.symfony.com/master/Symfony/Component/Routing/Route.html>`_ is
is actually quite powerful. Routes actually quite powerful. Routes can be named, which allows for URL generation.
can be named, which allows for URL generation. They can They can also have requirements for the variable parts. In order to allow
also have requirements for the variable parts. In order settings these through a nice interface the ``match`` method (which is used by
to allow settings these through a nice interface the ``get``, ``post``, etc.) returns an instance of the ``Controller``, which
``match`` method (which is used by ``get``, ``post``, etc.) wraps a route.
returns an instance of the ``Controller``, which wraps
a route.
ControllerCollection ControllerCollection
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
One of the goals of exposing the `RouteCollection One of the goals of exposing the `RouteCollection
<http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_ <http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_
was to make it mutable, so providers could add stuff to it. was to make it mutable, so providers could add stuff to it. The challenge here
The challenge here is the fact that routes know nothing is the fact that routes know nothing about their name. The name only has
about their name. The name only has meaning in context meaning in context of the ``RouteCollection`` and cannot be changed.
of the ``RouteCollection`` and cannot be changed.
To solve this challenge we came up with a staging area for routes. The
To solve this challenge we came up with a staging area ``ControllerCollection`` holds the controllers until ``flush`` is called, at
for routes. The ``ControllerCollection`` holds the which point the routes are added to the ``RouteCollection``. Also, the
controllers until ``flush`` is called, at which point controllers are then frozen. This means that they can no longer be modified
the routes are added to the ``RouteCollection``. Also, and will throw an Exception if you try to do so.
the controllers are then frozen. This means that they can
no longer be modified and will throw an Exception if Unfortunately no good way for flushing implicitly could be found, which is why
you try to do so. flushing is now always explicit. The Application will flush, but if you want
to read the ``ControllerCollection`` before the request takes place, you will
Unfortunately no good way for flushing implicitly have to call flush yourself.
could be found, which is why flushing is now always
explicit. The Application will flush, but if you want The ``Application`` provides a shortcut ``flush`` method for flushing the
to read the ``ControllerCollection`` before the ``ControllerCollection``.
request takes place, you will have to call flush
yourself.
The ``Application`` provides a shortcut ``flush``
method for flushing the ``ControllerCollection``.
Symfony2 Symfony2
-------- --------
...@@ -88,5 +79,4 @@ Following Symfony2 components are used by Silex: ...@@ -88,5 +79,4 @@ Following Symfony2 components are used by Silex:
* **EventDispatcher**: For hooking into the HttpKernel. * **EventDispatcher**: For hooking into the HttpKernel.
For more information, `check out the Symfony website For more information, `check out the Symfony website <http://symfony.com/>`_.
<http://symfony.com/>`_.
Introduction Introduction
============ ============
Silex is a PHP microframework for PHP 5.3. It is built on the shoulders Silex is a PHP microframework for PHP 5.3. It is built on the shoulders of
of Symfony2 and Pimple and also inspired by sinatra. Symfony2 and Pimple and also inspired by sinatra.
A microframework provides the guts for building simple single-file apps. A microframework provides the guts for building simple single-file apps. Silex
Silex aims to be: aims to be:
* *Concise*: Silex exposes an intuitive and concise API that is fun to use. * *Concise*: Silex exposes an intuitive and concise API that is fun to use.
* *Extensible*: Silex has an extension system based around the Pimple * *Extensible*: Silex has an extension system based around the Pimple micro
micro service-container that makes it even easier to tie in third party service-container that makes it even easier to tie in third party libraries.
libraries.
* *Testable*: Silex uses Symfony2's HttpKernel which abstracts request and * *Testable*: Silex uses Symfony2's HttpKernel which abstracts request and
response. This makes it very easy to test apps and the framework itself. response. This makes it very easy to test apps and the framework itself. It
It also respects the HTTP specification and encourages its proper use. also respects the HTTP specification and encourages its proper use.
In a nutshell, you define controllers and map them to routes, all in one In a nutshell, you define controllers and map them to routes, all in one step.
step.
**Let's go!**:: **Let's go!**::
...@@ -37,12 +35,12 @@ step. ...@@ -37,12 +35,12 @@ 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`` Next we define a route to ``/hello/{name}`` that matches for ``GET`` requests.
requests. When the route matches, the function is executed and the return When the route matches, the function is executed and the return value is sent
value is sent back to the client. back to the client.
Finally, the app is run. Visit ``/hello/world`` to see the result. Finally, the app is run. Visit ``/hello/world`` to see the result. It's really
It's really that easy! that easy!
Installing Silex is as easy as it can get. Download the `silex.zip`_ file, Installing Silex is as easy as it can get. Download the `silex.zip`_ file,
extract it, and you're done! extract it, and you're done!
......
...@@ -31,26 +31,23 @@ will be set **before** the provider is registered:: ...@@ -31,26 +31,23 @@ will be set **before** the provider is registered::
Conventions Conventions
~~~~~~~~~~~ ~~~~~~~~~~~
You need to watch out in what order you do certain things when You need to watch out in what order you do certain things when interacting
interacting with providers. Just keep to these rules: with providers. Just keep to these rules:
* Overriding existing services must occur **after** the * Overriding existing services must occur **after** the provider is
provider is registered. registered.
*Reason: If the services already exist, the provider *Reason: If the services already exist, the provider will overwrite it.*
will overwrite it.*
* You can set parameters any time before the service is * You can set parameters any time before the service is accessed.
accessed.
Make sure to stick to this behavior when creating your Make sure to stick to this behavior when creating your own providers.
own providers.
Included providers Included providers
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
There are a few provider that you get out of the box. There are a few provider that you get out of the box. All of these are within
All of these are within the ``Silex\Provider`` namespace. the ``Silex\Provider`` namespace:
* :doc:`DoctrineServiceProvider <providers/doctrine>` * :doc:`DoctrineServiceProvider <providers/doctrine>`
* :doc:`MonologServiceProvider <providers/monolog>` * :doc:`MonologServiceProvider <providers/monolog>`
...@@ -66,8 +63,8 @@ All of these are within the ``Silex\Provider`` namespace. ...@@ -66,8 +63,8 @@ All of these are within the ``Silex\Provider`` namespace.
Third party providers Third party providers
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
Some service providers are developed by the community. Those Some service providers are developed by the community. Those third-party
third-party providers are listed on `Silex' repository wiki providers are listed on `Silex' repository wiki
<https://github.com/fabpot/Silex/wiki/Third-Party-ServiceProviders>`_. <https://github.com/fabpot/Silex/wiki/Third-Party-ServiceProviders>`_.
You are encouraged to share yours. You are encouraged to share yours.
...@@ -82,10 +79,9 @@ Providers must implement the ``Silex\ServiceProviderInterface``:: ...@@ -82,10 +79,9 @@ Providers must implement the ``Silex\ServiceProviderInterface``::
function register(Application $app); function register(Application $app);
} }
This is very straight forward, just create a new class that This is very straight forward, just create a new class that implements the
implements the ``register`` method. In this method you must ``register`` method. In this method you must define services on the
define services on the application which then may make use application which then may make use of other services and parameters.
of other services and parameters.
Here is an example of such a provider:: Here is an example of such a provider::
...@@ -107,10 +103,9 @@ Here is an example of such a provider:: ...@@ -107,10 +103,9 @@ Here is an example of such a provider::
} }
} }
This class provides a ``hello`` service which is a protected This class provides a ``hello`` service which is a protected closure. It takes
closure. It takes a ``name`` argument and will return a ``name`` argument and will return ``hello.default_name`` if no name is
``hello.default_name`` if no name is given. If the default given. If the default is also missing, it will use an empty string.
is also missing, it will use an empty string.
You can now use this provider as follows:: You can now use this provider as follows::
...@@ -126,8 +121,8 @@ You can now use this provider as follows:: ...@@ -126,8 +121,8 @@ You can now use this provider as follows::
return $app['hello']($name); return $app['hello']($name);
}); });
In this example we are getting the ``name`` parameter from the In this example we are getting the ``name`` parameter from the query string,
query string, so the request path would have to be ``/hello?name=Fabien``. so the request path would have to be ``/hello?name=Fabien``.
Controllers providers Controllers providers
--------------------- ---------------------
......
...@@ -8,7 +8,6 @@ Silex ...@@ -8,7 +8,6 @@ Silex
monolog monolog
session session
swiftmailer swiftmailer
symfony_bridges
translation translation
twig twig
url_generator url_generator
......
MonologServiceProvider MonologServiceProvider
====================== ======================
The *MonologServiceProvider* provides a default logging mechanism The *MonologServiceProvider* provides a default logging mechanism through
through Jordi Boggiano's `Monolog <https://github.com/Seldaek/monolog>`_ Jordi Boggiano's `Monolog <https://github.com/Seldaek/monolog>`_ library.
library.
It will log requests and errors and allow you to add debug It will log requests and errors and allow you to add debug logging to your
logging to your application, so you don't have to use application, so you don't have to use ``var_dump`` so much anymore. You can
``var_dump`` so much anymore. You can use the grown-up use the grown-up version called ``tail -f``.
version called ``tail -f``.
Parameters Parameters
---------- ----------
...@@ -33,9 +31,8 @@ Services ...@@ -33,9 +31,8 @@ Services
$app['monolog']->addDebug('Testing the Monolog logging.'); $app['monolog']->addDebug('Testing the Monolog logging.');
* **monolog.configure**: Protected closure that takes the * **monolog.configure**: Protected closure that takes the logger as an
logger as an argument. You can override it if you do not argument. You can override it if you do not want the default behavior.
want the default behavior.
Registering Registering
----------- -----------
...@@ -60,11 +57,9 @@ Registering ...@@ -60,11 +57,9 @@ Registering
Usage Usage
----- -----
The MonologServiceProvider provides a ``monolog`` service. You can use The MonologServiceProvider provides a ``monolog`` service. You can use it to
it to add log entries for any logging level through ``addDebug()``, add log entries for any logging level through ``addDebug()``, ``addInfo()``,
``addInfo()``, ``addWarning()`` and ``addError()``. ``addWarning()`` and ``addError()``:
::
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
......
...@@ -24,8 +24,8 @@ Parameters ...@@ -24,8 +24,8 @@ Parameters
* **secure**: Cookie secure (HTTPS) * **secure**: Cookie secure (HTTPS)
* **httponly**: Whether the cookie is http only * **httponly**: Whether the cookie is http only
However, all of these are optional. Sessions last as long as the browser However, all of these are optional. Sessions last as long as the browser is
is open. To override this, set the ``lifetime`` option. open. To override this, set the ``lifetime`` option.
Services Services
-------- --------
...@@ -33,11 +33,12 @@ Services ...@@ -33,11 +33,12 @@ Services
* **session**: An instance of Symfony2's `Session * **session**: An instance of Symfony2's `Session
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Session/Session.html>`_. <http://api.symfony.com/master/Symfony/Component/HttpFoundation/Session/Session.html>`_.
* **session.storage**: A service that is used for persistence of the * **session.storage**: A service that is used for persistence of the session
session data. Defaults to a ``NativeSessionStorage`` data. Defaults to a ``NativeSessionStorage``.
* **session.storage.handler**: A service that is used by the ``session.storage`` * **session.storage.handler**: A service that is used by the
for data access. Defaults to a ``NativeFileSessionHandler`` storage handler. ``session.storage`` for data access. Defaults to a
``NativeFileSessionHandler`` storage handler.
Registering Registering
----------- -----------
...@@ -49,8 +50,8 @@ Registering ...@@ -49,8 +50,8 @@ Registering
Usage Usage
----- -----
The Session provider provides a ``session`` service. Here is an The Session provider provides a ``session`` service. Here is an example that
example that authenticates a user and creates a session for him:: authenticates a user and creates a session for him::
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
......
SwiftmailerServiceProvider SwiftmailerServiceProvider
========================== ==========================
The *SwiftmailerServiceProvider* provides a service for sending The *SwiftmailerServiceProvider* provides a service for sending email through
email through the `Swift Mailer <http://swiftmailer.org>`_ the `Swift Mailer <http://swiftmailer.org>`_ library.
library.
You can use the ``mailer`` service to send messages easily. You can use the ``mailer`` service to send messages easily. By default, it
By default, it will attempt to send emails through SMTP. will attempt to send emails through SMTP.
Parameters Parameters
---------- ----------
* **swiftmailer.options**: An array of options for the default * **swiftmailer.options**: An array of options for the default SMTP-based
SMTP-based configuration. configuration.
The following options can be set: The following options can be set:
...@@ -75,9 +74,7 @@ Registering ...@@ -75,9 +74,7 @@ Registering
Usage Usage
----- -----
The Swiftmailer provider provides a ``mailer`` service. The Swiftmailer provider provides a ``mailer`` service:
::
$app->post('/feedback', function () use ($app) { $app->post('/feedback', function () use ($app) {
$request = $app['request']; $request = $app['request'];
......
SymfonyBridgesServiceProvider
=============================
The *SymfonyBridgesServiceProvider* provides additional integration between
Symfony2 components and libraries.
Parameters
----------
none
Twig
----
When the ``SymfonyBridgesServiceProvider`` is enabled, the ``TwigServiceProvider`` will
provide you with the following additional capabilities:
* **UrlGeneratorServiceProvider**: If you are using the ``UrlGeneratorServiceProvider``,
you will get ``path`` and ``url`` helpers for Twig. You can find more
information in the
`Symfony2 Routing documentation <http://symfony.com/doc/current/book/routing.html#generating-urls-from-a-template>`_.
* **TranslationServiceProvider**: If you are using the ``TranslationServiceProvider``,
you will get ``trans`` and ``transchoice`` helpers for translation in
Twig templates. You can find more information in the
`Symfony2 Translation documentation <http://symfony.com/doc/current/book/translation.html#twig-templates>`_.
* **FormServiceProvider**: If you are using the ``FormServiceProvider``,
you will get a set of helpers for working with forms in templates.
You can find more information in the
`Symfony2 Forms reference <http://symfony.com/doc/current/reference/forms/twig_reference.html>`_.
Registering
-----------
.. code-block:: php
$app->register(new Silex\Provider\SymfonyBridgesServiceProvider());
.. note::
The Symfony bridges do not come with the ``silex.zip`, so you need to add
them as a dependency to your ``composer.json`` file:
.. code-block:: json
"require": {
"symfony/twig-bridge": "2.1.*",
}
...@@ -7,10 +7,8 @@ application into different languages. ...@@ -7,10 +7,8 @@ application into different languages.
Parameters Parameters
---------- ----------
* **translator.messages** (optional): A mapping of locales to message arrays. * **translator.domains**: A mapping of domains/locales/messages. This
This parameter contains the translation data in all languages. parameter contains the translation data for all languages and domains.
* **translator.domains** (optional): Same as above but stored by domains.
* **locale** (optional): The locale for the translator. You will most likely * **locale** (optional): The locale for the translator. You will most likely
want to set this based on some request parameter. Defaults to ``en``. want to set this based on some request parameter. Defaults to ``en``.
...@@ -26,7 +24,8 @@ Services ...@@ -26,7 +24,8 @@ Services
that is used for translation. that is used for translation.
* **translator.loader**: An instance of an implementation of the translation * **translator.loader**: An instance of an implementation of the translation
`LoaderInterface <http://api.symfony.com/master/Symfony/Component/Translation/Loader/LoaderInterface.html>`_, `LoaderInterface
<http://api.symfony.com/master/Symfony/Component/Translation/Loader/LoaderInterface.html>`_,
defaults to an `ArrayLoader defaults to an `ArrayLoader
<http://api.symfony.com/master/Symfony/Component/Translation/Loader/ArrayLoader.html>`_. <http://api.symfony.com/master/Symfony/Component/Translation/Loader/ArrayLoader.html>`_.
...@@ -58,20 +57,27 @@ Usage ...@@ -58,20 +57,27 @@ Usage
----- -----
The Translation provider provides a ``translator`` service and makes use of The Translation provider provides a ``translator`` service and makes use of
the ``translator.messages`` parameter:: the ``translator.domains`` parameter::
$app['translator.messages'] = array( $app['translator.domains'] = array(
'en' => array( 'messages' => array(
'hello' => 'Hello %name%', 'en' => array(
'goodbye' => 'Goodbye %name%', 'hello' => 'Hello %name%',
), 'goodbye' => 'Goodbye %name%',
'de' => array( ),
'hello' => 'Hallo %name%', 'de' => array(
'goodbye' => 'Tschüss %name%', 'hello' => 'Hallo %name%',
'goodbye' => 'Tschüss %name%',
),
'fr' => array(
'hello' => 'Bonjour %name%',
'goodbye' => 'Au revoir %name%',
),
), ),
'fr' => array( 'validators' => array(
'hello' => 'Bonjour %name%', 'fr' => array(
'goodbye' => 'Au revoir %name%', 'This value should be a valid number.' => 'Cette valeur doit être un nombre.',
),
), ),
); );
...@@ -95,33 +101,6 @@ The above example will result in following routes: ...@@ -95,33 +101,6 @@ The above example will result in following routes:
* ``/it/hello/igor`` will return ``Hello igor`` (because of the fallback). * ``/it/hello/igor`` will return ``Hello igor`` (because of the fallback).
The messages defined with ``translator.messages`` are automatically stored in
the default domain. When you need to explicitly set the translation domain
(for the validation error messages for instance), use the
``translator.domains`` parameter instead::
$app['translator.domains'] = array(
'messages' => array(
'en' => array(
'hello' => 'Hello %name%',
'goodbye' => 'Goodbye %name%',
),
'de' => array(
'hello' => 'Hallo %name%',
'goodbye' => 'Tschüss %name%',
),
'fr' => array(
'hello' => 'Bonjour %name%',
'goodbye' => 'Au revoir %name%',
),
),
'validators' => array(
'fr' => array(
'This value should be a valid number.' => 'Cette valeur doit être un nombre.',
),
),
);
Recipes Recipes
------- -------
...@@ -149,13 +128,15 @@ use is ``locales/en.yml``. Just do the mapping in this file as follows: ...@@ -149,13 +128,15 @@ use is ``locales/en.yml``. Just do the mapping in this file as follows:
hello: Hello %name% hello: Hello %name%
goodbye: Goodbye %name% goodbye: Goodbye %name%
Repeat this for all of your languages. Then set up the ``translator.messages`` Repeat this for all of your languages. Then set up the ``translator.domains``
to map languages to files:: to map languages to files::
$app['translator.messages'] = array( $app['translator.domains'] = array(
'en' => __DIR__.'/locales/en.yml', 'messages' => array(
'de' => __DIR__.'/locales/de.yml', 'en' => __DIR__.'/locales/en.yml',
'fr' => __DIR__.'/locales/fr.yml', 'de' => __DIR__.'/locales/de.yml',
'fr' => __DIR__.'/locales/fr.yml',
),
); );
Finally override the ``translator.loader`` to use a ``YamlFileLoader`` instead Finally override the ``translator.loader`` to use a ``YamlFileLoader`` instead
...@@ -176,7 +157,7 @@ Just as you would do with YAML translation files, you first need to add the ...@@ -176,7 +157,7 @@ Just as you would do with YAML translation files, you first need to add the
Symfony2 ``Config`` component as a dependency (see above for details). Symfony2 ``Config`` component as a dependency (see above for details).
Then, similarly, create XLIFF files in your locales directory and setup the Then, similarly, create XLIFF files in your locales directory and setup the
``translator.messages`` to map to them. ``translator.domains`` to map to them.
Finally override the ``translator.loader`` to use a ``XliffFileLoader``:: Finally override the ``translator.loader`` to use a ``XliffFileLoader``::
...@@ -191,7 +172,8 @@ That's it. ...@@ -191,7 +172,8 @@ That's it.
Accessing translations in Twig templates Accessing translations in Twig templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Once loaded, the translation service provider is available from within Twig templates: Once loaded, the translation service provider is available from within Twig
templates:
.. code-block:: jinja .. code-block:: jinja
......
...@@ -29,9 +29,9 @@ Services ...@@ -29,9 +29,9 @@ Services
that takes the Twig environment as an argument. You can use it to add more that takes the Twig environment as an argument. You can use it to add more
custom globals. custom globals.
* **twig.loader**: The loader for Twig templates which uses * **twig.loader**: The loader for Twig templates which uses the ``twig.path``
the ``twig.path`` and the ``twig.templates`` options. You and the ``twig.templates`` options. You can also replace the loader
can also replace the loader completely. completely.
Registering Registering
----------- -----------
...@@ -53,6 +53,39 @@ Registering ...@@ -53,6 +53,39 @@ Registering
"twig/twig": ">=1.8,<2.0-dev" "twig/twig": ">=1.8,<2.0-dev"
} }
Symfony2 Components Integration
-------------------------------
Symfony provides a Twig bridge that provides additional integration between
some Symfony2 components and Twig. Add it as a dependency to your
``composer.json`` file:
.. code-block:: json
"require": {
"symfony/twig-bridge": "2.1.*",
}
When present, the ``TwigServiceProvider`` will provide you with the following
additional capabilities:
* **UrlGeneratorServiceProvider**: If you are using the
``UrlGeneratorServiceProvider``, you will have access to the ``path()`` and
``url()`` functions. You can find more information in the `Symfony2 Routing
documentation
<http://symfony.com/doc/current/book/routing.html#generating-urls-from-a-template>`_.
* **TranslationServiceProvider**: If you are using the
``TranslationServiceProvider``, you will get the ``trans()`` and
``transchoice()`` functions for translation in Twig templates. You can find
more information in the `Symfony2 Translation documentation
<http://symfony.com/doc/current/book/translation.html#twig-templates>`_.
* **FormServiceProvider**: If you are using the ``FormServiceProvider``, you
will get a set of helpers for working with forms in templates. You can find
more information in the `Symfony2 Forms reference
<http://symfony.com/doc/current/reference/forms/twig_reference.html>`_.
Usage Usage
----- -----
......
UrlGeneratorServiceProvider UrlGeneratorServiceProvider
=========================== ===========================
The *UrlGeneratorServiceProvider* provides a service for generating The *UrlGeneratorServiceProvider* provides a service for generating URLs for
URLs for named routes. named routes.
Parameters Parameters
---------- ----------
...@@ -16,9 +16,9 @@ Services ...@@ -16,9 +16,9 @@ Services
<http://api.symfony.com/master/Symfony/Component/Routing/Generator/UrlGenerator.html>`_, <http://api.symfony.com/master/Symfony/Component/Routing/Generator/UrlGenerator.html>`_,
using the `RouteCollection using the `RouteCollection
<http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_ <http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_
that is provided through the ``routes`` service. that is provided through the ``routes`` service. It has a ``generate``
It has a ``generate`` method, which takes the route name as an argument, method, which takes the route name as an argument, followed by an array of
followed by an array of route parameters. route parameters.
Registering Registering
----------- -----------
......
...@@ -12,13 +12,13 @@ Dependency Injection ...@@ -12,13 +12,13 @@ Dependency Injection
You can skip this if you already know what Dependency Injection is. You can skip this if you already know what Dependency Injection is.
Dependency Injection is a design pattern where you pass dependencies Dependency Injection is a design pattern where you pass dependencies to
to services instead of creating them from within the service or services instead of creating them from within the service or relying on
relying on globals. This generally leads to code that is decoupled, globals. This generally leads to code that is decoupled, re-usable, flexible
re-usable, flexible and testable. and testable.
Here is an example of a class that takes a ``User`` object and stores Here is an example of a class that takes a ``User`` object and stores it as a
it as a file in JSON format:: file in JSON format::
class JsonUserPersister class JsonUserPersister
{ {
...@@ -38,34 +38,33 @@ it as a file in JSON format:: ...@@ -38,34 +38,33 @@ it as a file in JSON format::
} }
} }
In this simple example the dependency is the ``basePath`` property. In this simple example the dependency is the ``basePath`` property. It is
It is passed to the constructor. This means you can create several passed to the constructor. This means you can create several independent
independent instances with different base paths. Of course instances with different base paths. Of course dependencies do not have to be
dependencies do not have to be simple strings. More often they are simple strings. More often they are in fact other services.
in fact other services.
Container Container
~~~~~~~~~ ~~~~~~~~~
A DIC or service container is responsible for creating and storing A DIC or service container is responsible for creating and storing services.
services. It can recursively create dependencies of the requested It can recursively create dependencies of the requested services and inject
services and inject them. It does so lazily, which means a service them. It does so lazily, which means a service is only created when you
is only created when you actually need it. actually need it.
Most containers are quite complex and are configured through XML Most containers are quite complex and are configured through XML or YAML
or YAML files. files.
Pimple is different. Pimple is different.
Pimple Pimple
------ ------
Pimple is probably the simplest service container out there. It Pimple is probably the simplest service container out there. It makes strong
makes strong use of closures and implements the ArrayAccess interface. use of closures and implements the ArrayAccess interface.
We will start off by creating a new instance of Pimple -- and We will start off by creating a new instance of Pimple -- and because
because ``Silex\Application`` extends ``Pimple`` all of this ``Silex\Application`` extends ``Pimple`` all of this applies to Silex as
applies to Silex as well:: well::
$container = new Pimple(); $container = new Pimple();
...@@ -76,28 +75,26 @@ or:: ...@@ -76,28 +75,26 @@ or::
Parameters Parameters
~~~~~~~~~~ ~~~~~~~~~~
You can set parameters (which are usually strings) by setting You can set parameters (which are usually strings) by setting an array key on
an array key on the container:: the container::
$app['some_parameter'] = 'value'; $app['some_parameter'] = 'value';
The array key can be anything, by convention periods are The array key can be anything, by convention periods are used for
used for namespacing:: namespacing::
$app['asset.host'] = 'http://cdn.mysite.com/'; $app['asset.host'] = 'http://cdn.mysite.com/';
Reading parameter values is possible with the same Reading parameter values is possible with the same syntax::
syntax::
echo $app['some_parameter']; echo $app['some_parameter'];
Service definitions Service definitions
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Defining services is no different than defining parameters. Defining services is no different than defining parameters. You just set an
You just set an array key on the container to be a closure. array key on the container to be a closure. However, when you retrieve the
However, when you retrieve the service, the closure is executed. service, the closure is executed. This allows for lazy service creation::
This allows for lazy service creation::
$app['some_service'] = function () { $app['some_service'] = function () {
return new Service(); return new Service();
...@@ -107,43 +104,40 @@ And to retrieve the service, use:: ...@@ -107,43 +104,40 @@ And to retrieve the service, use::
$service = $app['some_service']; $service = $app['some_service'];
Every time you call ``$app['some_service']``, a new instance Every time you call ``$app['some_service']``, a new instance of the service is
of the service is created. created.
Shared services Shared services
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
You may want to use the same instance of a service across all You may want to use the same instance of a service across all of your code. In
of your code. In order to do that you can make a *shared* order to do that you can make a *shared* service::
service::
$app['some_service'] = $app->share(function () { $app['some_service'] = $app->share(function () {
return new Service(); return new Service();
}); });
This will create the service on first invocation, and then This will create the service on first invocation, and then return the existing
return the existing instance on any subsequent access. instance on any subsequent access.
Access container from closure Access container from closure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In many cases you will want to access the service container In many cases you will want to access the service container from within a
from within a service definition closure. For example when service definition closure. For example when fetching services the current
fetching services the current service depends on. service depends on.
Because of this, the container is passed to the closure as Because of this, the container is passed to the closure as an argument::
an argument::
$app['some_service'] = function ($app) { $app['some_service'] = function ($app) {
return new Service($app['some_other_service'], $app['some_service.config']); return new Service($app['some_other_service'], $app['some_service.config']);
}; };
Here you can see an example of Dependency Injection. Here you can see an example of Dependency Injection. ``some_service`` depends
``some_service`` depends on ``some_other_service`` and on ``some_other_service`` and takes ``some_service.config`` as configuration
takes ``some_service.config`` as configuration options. options. The dependency is only created when ``some_service`` is accessed, and
The dependency is only created when ``some_service`` is it is possible to replace either of the dependencies by simply overriding
accessed, and it is possible to replace either of the those definitions.
dependencies by simply overriding those definitions.
.. note:: .. note::
...@@ -152,15 +146,14 @@ dependencies by simply overriding those definitions. ...@@ -152,15 +146,14 @@ dependencies by simply overriding those definitions.
Protected closures Protected closures
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
Because the container sees closures as factories for Because the container sees closures as factories for services, it will always
services, it will always execute them when reading them. execute them when reading them.
In some cases you will however want to store a closure In some cases you will however want to store a closure as a parameter, so that
as a parameter, so that you can fetch it and execute it you can fetch it and execute it yourself -- with your own arguments.
yourself -- with your own arguments.
This is why Pimple allows you to protect your closures This is why Pimple allows you to protect your closures from being executed, by
from being executed, by using the ``protect`` method:: using the ``protect`` method::
$app['closure_parameter'] = $app->protect(function ($a, $b) { $app['closure_parameter'] = $app->protect(function ($a, $b) {
return $a + $b; return $a + $b;
...@@ -172,62 +165,62 @@ from being executed, by using the ``protect`` method:: ...@@ -172,62 +165,62 @@ from being executed, by using the ``protect`` method::
// calling it now // calling it now
echo $add(2, 3); echo $add(2, 3);
Note that protected closures do not get access to Note that protected closures do not get access to the container.
the container.
Core services Core services
------------- -------------
Silex defines a range of services which can be used Silex defines a range of services which can be used or replaced. You probably
or replaced. You probably don't want to mess with most don't want to mess with most of them.
of them.
* **request**: Contains the current request object, * **request**: Contains the current request object, which is an instance of
which is an instance of `Request `Request
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_. <http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_.
It gives you access to ``GET``, ``POST`` parameters It gives you access to ``GET``, ``POST`` parameters and lots more!
and lots more!
Example usage:: Example usage::
$id = $app['request']->get('id'); $id = $app['request']->get('id');
This is only available when a request is being served, you can only access it This is only available when a request is being served, you can only access
from within a controller, before filter, after filter or error handler. it from within a controller, before filter, after filter or error handler.
* **routes**: The `RouteCollection * **routes**: The `RouteCollection
<http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_ <http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_
that is used internally. You can add, modify, read that is used internally. You can add, modify, read routes.
routes.
* **controllers**: The ``Silex\ControllerCollection`` * **controllers**: The ``Silex\ControllerCollection`` that is used internally.
that is used internally. Check the *Internals* Check the *Internals* chapter for more information.
chapter for more information.
* **dispatcher**: The `EventDispatcher * **dispatcher**: The `EventDispatcher
<http://api.symfony.com/master/Symfony/Component/EventDispatcher/EventDispatcher.html>`_ <http://api.symfony.com/master/Symfony/Component/EventDispatcher/EventDispatcher.html>`_
that is used internally. It is the core of the Symfony2 that is used internally. It is the core of the Symfony2 system and is used
system and is used quite a bit by Silex. quite a bit by Silex.
* **resolver**: The `ControllerResolver * **resolver**: The `ControllerResolver
<http://api.symfony.com/master/Symfony/Component/HttpKernel/Controller/ControllerResolver.html>`_ <http://api.symfony.com/master/Symfony/Component/HttpKernel/Controller/ControllerResolver.html>`_
that is used internally. It takes care of executing the that is used internally. It takes care of executing the controller with the
controller with the right arguments. right arguments.
* **kernel**: The `HttpKernel * **kernel**: The `HttpKernel
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernel.html>`_ <http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernel.html>`_
that is used internally. The HttpKernel is the heart of that is used internally. The HttpKernel is the heart of Symfony2, it takes a
Symfony2, it takes a Request as input and returns a Request as input and returns a Response as output.
Response as output.
* **request_context**: The request context is a simplified representation * **request_context**: The request context is a simplified representation of
of the request that is used by the Router and the UrlGenerator. the request that is used by the Router and the UrlGenerator.
* **exception_handler**: The Exception handler is the default handler that is * **exception_handler**: The Exception handler is the default handler that is
used when you don't register one via the `error()` method or if your handler used when you don't register one via the `error()` method or if your handler
does not return a Response. Disable it with does not return a Response. Disable it with
`unset($app['exception_handler'])`. `unset($app['exception_handler'])`.
* **logger**: A
`http://api.symfony.com/master/Symfony/Component/HttpKernel/Log/LoggerInterface.html`_
instance. By default, logging is disabled as the value is set to `null`.
When the Symfony2 Monolog bridge is installed, Monolog is automatically used
as the default logger.
.. note:: .. note::
All of these Silex core services are shared. All of these Silex core services are shared.
......
...@@ -3,30 +3,32 @@ Testing ...@@ -3,30 +3,32 @@ Testing
Because Silex is built on top of Symfony2, it is very easy to write functional Because Silex is built on top of Symfony2, it is very easy to write functional
tests for your application. Functional tests are automated software tests that tests for your application. Functional tests are automated software tests that
ensure that your code is working correctly. They go through the user interface, ensure that your code is working correctly. They go through the user
using a fake browser, and mimic the actions a user would do. interface, using a fake browser, and mimic the actions a user would do.
Why Why
--- ---
If you are not familiar with software tests, you may be wondering why you would If you are not familiar with software tests, you may be wondering why you
need this. Every time you make a change to your application, you have to test would need this. Every time you make a change to your application, you have to
it. This means going through all the pages and making sure they are still test it. This means going through all the pages and making sure they are still
working. Functional tests save you a lot of time, because they enable you to working. Functional tests save you a lot of time, because they enable you to
test your application in usually under a second by running a single command. test your application in usually under a second by running a single command.
For more information on functional testing, unit testing, and automated For more information on functional testing, unit testing, and automated
software tests in general, check out `PHPUnit <https://github.com/sebastianbergmann/phpunit>`_ software tests in general, check out `PHPUnit
and `Bulat Shakirzyanov's talk on Clean Code <http://www.slideshare.net/avalanche123/clean-code-5609451>`_. <https://github.com/sebastianbergmann/phpunit>`_ and `Bulat Shakirzyanov's
talk on Clean Code
<http://www.slideshare.net/avalanche123/clean-code-5609451>`_.
PHPUnit PHPUnit
------- -------
`PHPUnit <https://github.com/sebastianbergmann/phpunit>`_ `PHPUnit <https://github.com/sebastianbergmann/phpunit>`_ is the de-facto
is the de-facto standard testing framework for PHP. It was built for standard testing framework for PHP. It was built for writing unit tests, but
writing unit tests, but it can be used for functional tests too. You write it can be used for functional tests too. You write tests by creating a new
tests by creating a new class, that extends the ``PHPUnit_Framework_TestCase``. class, that extends the ``PHPUnit_Framework_TestCase``. Your test cases are
Your test cases are methods prefixed with ``test``:: methods prefixed with ``test``::
class ContactFormTest extends PHPUnit_Framework_TestCase class ContactFormTest extends PHPUnit_Framework_TestCase
{ {
...@@ -120,8 +122,9 @@ executed before every test. ...@@ -120,8 +122,9 @@ executed before every test.
// ... // ...
} }
The WebTestCase provides a ``createClient`` method. A client acts as a browser, The WebTestCase provides a ``createClient`` method. A client acts as a
and allows you to interact with your application. Here's how it works:: browser, and allows you to interact with your application. Here's how it
works::
public function testInitialPage() public function testInitialPage()
{ {
...@@ -148,8 +151,8 @@ application. ...@@ -148,8 +151,8 @@ application.
.. note:: .. note::
You can find some documentation for it in `the client section of the testing You can find some documentation for it in `the client section of the
chapter of the Symfony2 documentation testing chapter of the Symfony2 documentation
<http://symfony.com/doc/current/book/testing.html#the-test-client>`_. <http://symfony.com/doc/current/book/testing.html#the-test-client>`_.
Crawler Crawler
...@@ -168,8 +171,9 @@ Configuration ...@@ -168,8 +171,9 @@ Configuration
------------- -------------
The suggested way to configure PHPUnit is to create a ``phpunit.xml.dist`` The suggested way to configure PHPUnit is to create a ``phpunit.xml.dist``
file, a ``tests`` folder and your tests in ``tests/YourApp/Tests/YourTest.php``. file, a ``tests`` folder and your tests in
The ``phpunit.xml.dist`` file should look like this: ``tests/YourApp/Tests/YourTest.php``. The ``phpunit.xml.dist`` file should
look like this:
.. code-block:: xml .. code-block:: xml
......
...@@ -83,14 +83,13 @@ route is matched. ...@@ -83,14 +83,13 @@ route is matched.
A route pattern consists of: A route pattern consists of:
* *Pattern*: The route pattern defines a path that points to a resource. * *Pattern*: The route pattern defines a path that points to a resource. The
The pattern can include variable parts and you are able to set pattern can include variable parts and you are able to set RegExp
RegExp requirements for them. requirements for them.
* *Method*: One of the following HTTP methods: ``GET``, ``POST``, ``PUT`` * *Method*: One of the following HTTP methods: ``GET``, ``POST``, ``PUT``
``DELETE``. This describes the interaction with the resource. Commonly ``DELETE``. This describes the interaction with the resource. Commonly only
only ``GET`` and ``POST`` are used, but it is possible to use the ``GET`` and ``POST`` are used, but it is possible to use the others as well.
others as well.
The controller is defined using a closure like this:: The controller is defined using a closure like this::
...@@ -98,22 +97,22 @@ The controller is defined using a closure like this:: ...@@ -98,22 +97,22 @@ The controller is defined using a closure like this::
// do something // do something
} }
Closures are anonymous functions that may import state from outside Closures are anonymous functions that may import state from outside of their
of their definition. This is different from globals, because the outer definition. This is different from globals, because the outer state does not
state does not have to be global. For instance, you could define a have to be global. For instance, you could define a closure in a function and
closure in a function and import local variables of that function. import local variables of that function.
.. note:: .. note::
Closures that do not import scope are referred to as lambdas. Closures that do not import scope are referred to as lambdas. Because in
Because in PHP all anonymous functions are instances of the PHP all anonymous functions are instances of the ``Closure`` class, we
``Closure`` class, we will not make a distinction here. 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.
There is also an alternate way for defining controllers using a There is also an alternate way for defining controllers using a class method.
class method. The syntax for that is ``ClassName::methodName``. The syntax for that is ``ClassName::methodName``. Static methods are also
Static methods are also possible. possible.
Example GET route Example GET route
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
...@@ -140,15 +139,14 @@ Here is an example definition of a ``GET`` route:: ...@@ -140,15 +139,14 @@ 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 statement means something different in this context. It tells the closure to
closure to import the $blogPosts variable from the outer scope. This import the $blogPosts variable from the outer scope. This allows you to use it
allows you to use it from within the closure. from within the closure.
Dynamic routing Dynamic routing
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
Now, you can create another controller for viewing individual blog Now, you can create another controller for viewing individual blog posts::
posts::
$app->get('/blog/show/{id}', function (Silex\Application $app, $id) use ($blogPosts) { $app->get('/blog/show/{id}', function (Silex\Application $app, $id) use ($blogPosts) {
if (!isset($blogPosts[$id])) { if (!isset($blogPosts[$id])) {
...@@ -161,8 +159,8 @@ posts:: ...@@ -161,8 +159,8 @@ posts::
"<p>{$post['body']}</p>"; "<p>{$post['body']}</p>";
}); });
This route definition has a variable ``{id}`` part which is passed This route definition has a variable ``{id}`` part which is passed to the
to the closure. closure.
When the post does not exist, we are using ``abort()`` to stop the request 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 early. It actually throws an exception, which we will see how to handle later
...@@ -188,19 +186,18 @@ It is pretty straightforward. ...@@ -188,19 +186,18 @@ It is pretty straightforward.
.. note:: .. note::
There is a :doc:`SwiftmailerServiceProvider <providers/swiftmailer>` included There is a :doc:`SwiftmailerServiceProvider <providers/swiftmailer>`
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 `Request
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_, <http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_,
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 Instead of returning a string we are returning an instance of `Response
`Response
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html>`_. <http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html>`_.
This allows setting an HTTP This allows setting an HTTP status code, in this case it is set to ``201
status code, in this case it is set to ``201 Created``. Created``.
.. note:: .. note::
...@@ -211,8 +208,8 @@ Other methods ...@@ -211,8 +208,8 @@ Other methods
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
You can create controllers for most HTTP methods. Just call one of these You can create controllers for most HTTP methods. Just call one of these
methods on your application: ``get``, ``post``, ``put``, ``delete``. You methods on your application: ``get``, ``post``, ``put``, ``delete``. You can
can also call ``match``, which will match all methods:: also call ``match``, which will match all methods::
$app->match('/blog', function () { $app->match('/blog', function () {
... ...
...@@ -241,7 +238,8 @@ You can match multiple methods with one controller using regex syntax:: ...@@ -241,7 +238,8 @@ You can match multiple methods with one controller using regex syntax::
Route variables Route variables
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
As it has been shown before you can define variable parts in a route like this:: As it has been shown before you can define variable parts in a route like
this::
$app->get('/blog/show/{id}', function ($id) { $app->get('/blog/show/{id}', function ($id) {
... ...
...@@ -254,7 +252,8 @@ closure arguments match the names of the variable parts:: ...@@ -254,7 +252,8 @@ closure arguments match the names of the variable parts::
... ...
}); });
While it's not suggested, you could also do this (note the switched arguments):: While it's not suggested, you could also do this (note the switched
arguments)::
$app->get('/blog/show/{postId}/{commentId}', function ($commentId, $postId) { $app->get('/blog/show/{postId}/{commentId}', function ($commentId, $postId) {
... ...
...@@ -350,10 +349,10 @@ have the value ``index``. ...@@ -350,10 +349,10 @@ have the value ``index``.
Named routes Named routes
~~~~~~~~~~~~ ~~~~~~~~~~~~
Some providers (such as ``UrlGeneratorProvider``) can make use of named routes. Some providers (such as ``UrlGeneratorProvider``) can make use of named
By default Silex will generate a route name for you, that cannot really be routes. By default Silex will generate a route name for you, that cannot
used. You can give a route a name by calling ``bind`` on the ``Controller`` really be used. You can give a route a name by calling ``bind`` on the
object that is returned by the routing methods:: ``Controller`` object that is returned by the routing methods::
$app->get('/', function () { $app->get('/', function () {
... ...
...@@ -368,15 +367,15 @@ object that is returned by the routing methods:: ...@@ -368,15 +367,15 @@ object that is returned by the routing methods::
.. note:: .. note::
It only makes sense to name routes if you use providers that make use It only makes sense to name routes if you use providers that make use of
of the ``RouteCollection``. the ``RouteCollection``.
Before, after and finish filters Before, after and finish filters
-------------------------------- --------------------------------
Silex allows you to run code before, after every request and even after the Silex allows you to run code before, after every request and even after the
response has been sent. This happens through ``before``, ``after`` and ``finish`` response has been sent. This happens through ``before``, ``after`` and
filters. All you need to do is pass a closure:: ``finish`` filters. All you need to do is pass a closure::
$app->before(function () { $app->before(function () {
// set up // set up
...@@ -390,8 +389,8 @@ filters. All you need to do is pass a closure:: ...@@ -390,8 +389,8 @@ filters. All you need to do is pass a closure::
// after response has been sent // after response has been sent
}); });
The before filter has access to the current Request, and can short-circuit The before filter has access to the current Request, and can short-circuit the
the whole rendering by returning a Response:: whole rendering by returning a Response::
$app->before(function (Request $request) { $app->before(function (Request $request) {
// redirect the user to the login screen if access to the Resource is protected // redirect the user to the login screen if access to the Resource is protected
...@@ -491,12 +490,12 @@ the defaults for new controllers. ...@@ -491,12 +490,12 @@ the defaults for new controllers.
Error handlers Error handlers
-------------- --------------
If some part of your code throws an exception you will want to display If some part of your code throws an exception you will want to display some
some kind of error page to the user. This is what error handlers do. You kind of error page to the user. This is what error handlers do. You can also
can also use them to do additional things, such as logging. use them to do additional things, such as logging.
To register an error handler, pass a closure to the ``error`` method To register an error handler, pass a closure to the ``error`` method which
which takes an ``Exception`` argument and returns a response:: takes an ``Exception`` argument and returns a response::
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
...@@ -535,9 +534,9 @@ once a response is returned, the following handlers are ignored. ...@@ -535,9 +534,9 @@ once a response is returned, the following handlers are ignored.
.. note:: .. note::
Silex ships with a provider for `Monolog <https://github.com/Seldaek/monolog>`_ Silex ships with a provider for `Monolog
which handles logging of errors. Check out the *Providers* chapter <https://github.com/Seldaek/monolog>`_ which handles logging of errors.
for details. Check out the *Providers* chapter for details.
.. tip:: .. tip::
...@@ -571,8 +570,8 @@ early:: ...@@ -571,8 +570,8 @@ early::
Redirects Redirects
--------- ---------
You can redirect to another page by returning a redirect response, which You can redirect to another page by returning a redirect response, which you
you can create by calling the ``redirect`` method:: can create by calling the ``redirect`` method::
$app->get('/', function () use ($app) { $app->get('/', function () use ($app) {
return $app->redirect('/hello'); return $app->redirect('/hello');
...@@ -672,10 +671,8 @@ JSON ...@@ -672,10 +671,8 @@ JSON
---- ----
If you want to return JSON data, you can use the ``json`` helper method. 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 Simply pass it your data, status code and headers, and it will create a JSON
JSON response for you. response for you::
.. code-block:: php
$app->get('/users/{id}', function ($id) use ($app) { $app->get('/users/{id}', function ($id) use ($app) {
$user = getUser($id); $user = getUser($id);
...@@ -691,10 +688,8 @@ JSON response for you. ...@@ -691,10 +688,8 @@ JSON response for you.
Streaming Streaming
--------- ---------
It's possible to create a streaming response, which is important in cases It's possible to create a streaming response, which is important in cases when
when you cannot buffer the data being sent. you cannot buffer the data being sent::
.. code-block:: php
$app->get('/images/{file}', function ($file) use ($app) { $app->get('/images/{file}', function ($file) use ($app) {
if (!file_exists(__DIR__.'/images/'.$file)) { if (!file_exists(__DIR__.'/images/'.$file)) {
...@@ -708,10 +703,8 @@ when you cannot buffer the data being sent. ...@@ -708,10 +703,8 @@ when you cannot buffer the data being sent.
return $app->stream($stream, 200, array('Content-Type' => 'image/png')); 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 If you need to send chunks, make sure you call ``ob_flush`` and ``flush``
every chunk. after every chunk::
.. code-block:: php
$stream = function () { $stream = function () {
$fh = fopen('http://www.example.com/', 'rb'); $fh = fopen('http://www.example.com/', 'rb');
...@@ -732,8 +725,8 @@ Escaping ...@@ -732,8 +725,8 @@ Escaping
~~~~~~~~ ~~~~~~~~
When outputting any user input (either route variables GET/POST variables When outputting any user input (either route variables GET/POST variables
obtained from the request), you will have to make sure to escape it obtained from the request), you will have to make sure to escape it correctly,
correctly, to prevent Cross-Site-Scripting attacks. to prevent Cross-Site-Scripting attacks.
* **Escaping HTML**: PHP provides the ``htmlspecialchars`` function for this. * **Escaping HTML**: PHP provides the ``htmlspecialchars`` function for this.
Silex provides a shortcut ``escape`` method:: Silex provides a shortcut ``escape`` method::
......
...@@ -48,6 +48,9 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -48,6 +48,9 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
{ {
const VERSION = '@package_version@'; const VERSION = '@package_version@';
private $providers = array();
private $booted = false;
/** /**
* Constructor. * Constructor.
*/ */
...@@ -55,6 +58,8 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -55,6 +58,8 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
{ {
$app = $this; $app = $this;
$this['logger'] = null;
$this['routes'] = $this->share(function () { $this['routes'] = $this->share(function () {
return new RouteCollection(); return new RouteCollection();
}); });
...@@ -74,13 +79,13 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -74,13 +79,13 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
$urlMatcher = new LazyUrlMatcher(function () use ($app) { $urlMatcher = new LazyUrlMatcher(function () use ($app) {
return $app['url_matcher']; return $app['url_matcher'];
}); });
$dispatcher->addSubscriber(new RouterListener($urlMatcher)); $dispatcher->addSubscriber(new RouterListener($urlMatcher, $app['logger']));
return $dispatcher; return $dispatcher;
}); });
$this['resolver'] = $this->share(function () use ($app) { $this['resolver'] = $this->share(function () use ($app) {
return new ControllerResolver($app); return new ControllerResolver($app, $app['logger']);
}); });
$this['kernel'] = $this->share(function () use ($app) { $this['kernel'] = $this->share(function () use ($app) {
...@@ -145,9 +150,22 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -145,9 +150,22 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
$this[$key] = $value; $this[$key] = $value;
} }
$this->providers[] = $provider;
$provider->register($this); $provider->register($this);
} }
public function boot()
{
if (!$this->booted) {
foreach ($this->providers as $provider) {
$provider->boot($this);
}
$this->booted = true;
}
}
/** /**
* Maps a pattern to a callable. * Maps a pattern to a callable.
* *
...@@ -437,6 +455,10 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe ...@@ -437,6 +455,10 @@ class Application extends \Pimple implements HttpKernelInterface, EventSubscribe
*/ */
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{ {
if (!$this->booted) {
$this->boot();
}
$this->beforeDispatched = false; $this->beforeDispatched = false;
$current = HttpKernelInterface::SUB_REQUEST === $type ? $this['request'] : $this['request_error']; $current = HttpKernelInterface::SUB_REQUEST === $type ? $this['request'] : $this['request_error'];
......
...@@ -119,4 +119,8 @@ class DoctrineServiceProvider implements ServiceProviderInterface ...@@ -119,4 +119,8 @@ class DoctrineServiceProvider implements ServiceProviderInterface
return $dbs[$app['dbs.default']]; return $dbs[$app['dbs.default']];
}); });
} }
public function boot(Application $app)
{
}
} }
...@@ -52,4 +52,8 @@ class FormServiceProvider implements ServiceProviderInterface ...@@ -52,4 +52,8 @@ class FormServiceProvider implements ServiceProviderInterface
return new DefaultCsrfProvider($app['form.secret']); return new DefaultCsrfProvider($app['form.secret']);
}); });
} }
public function boot(Application $app)
{
}
} }
...@@ -42,4 +42,8 @@ class HttpCacheServiceProvider implements ServiceProviderInterface ...@@ -42,4 +42,8 @@ class HttpCacheServiceProvider implements ServiceProviderInterface
$app['http_cache.options'] = array(); $app['http_cache.options'] = array();
} }
} }
public function boot(Application $app)
{
}
} }
...@@ -13,10 +13,8 @@ namespace Silex\Provider; ...@@ -13,10 +13,8 @@ namespace Silex\Provider;
use Monolog\Logger; use Monolog\Logger;
use Monolog\Handler\StreamHandler; use Monolog\Handler\StreamHandler;
use Silex\Application; use Silex\Application;
use Silex\ServiceProviderInterface; use Silex\ServiceProviderInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
...@@ -29,8 +27,16 @@ class MonologServiceProvider implements ServiceProviderInterface ...@@ -29,8 +27,16 @@ class MonologServiceProvider implements ServiceProviderInterface
{ {
public function register(Application $app) public function register(Application $app)
{ {
$app['monolog'] = $app->share(function () use ($app) { if ($bridge = class_exists('Symfony\Bridge\Monolog\Logger')) {
$log = new Logger(isset($app['monolog.name']) ? $app['monolog.name'] : 'myapp'); $app['logger'] = function () use ($app) {
return $app['monolog'];
};
}
$app['monolog'] = $app->share(function () use ($app, $bridge) {
$class = $bridge ? 'Symfony\Bridge\Monolog\Logger' : 'Monolog\Logger';
$log = new $class(isset($app['monolog.name']) ? $app['monolog.name'] : 'myapp');
$app['monolog.configure']($log); $app['monolog.configure']($log);
...@@ -50,14 +56,17 @@ class MonologServiceProvider implements ServiceProviderInterface ...@@ -50,14 +56,17 @@ class MonologServiceProvider implements ServiceProviderInterface
return Logger::DEBUG; return Logger::DEBUG;
}; };
} }
}
public function boot(Application $app)
{
$app->before(function (Request $request) use ($app) { $app->before(function (Request $request) use ($app) {
$app['monolog']->addInfo('> '.$request->getMethod().' '.$request->getRequestUri()); $app['monolog']->addInfo('> '.$request->getMethod().' '.$request->getRequestUri());
}); });
$app->error(function (\Exception $e) use ($app) { $app->error(function (\Exception $e) use ($app) {
$app['monolog']->addError($e->getMessage()); $app['monolog']->addError($e->getMessage());
}); }, 255);
$app->after(function (Request $request, Response $response) use ($app) { $app->after(function (Request $request, Response $response) use ($app) {
$app['monolog']->addInfo('< '.$response->getStatusCode()); $app['monolog']->addInfo('< '.$response->getStatusCode());
......
...@@ -49,8 +49,6 @@ class SessionServiceProvider implements ServiceProviderInterface ...@@ -49,8 +49,6 @@ class SessionServiceProvider implements ServiceProviderInterface
); );
}); });
$app['dispatcher']->addListener(KernelEvents::REQUEST, array($this, 'onKernelRequest'), 128);
if (!isset($app['session.storage.options'])) { if (!isset($app['session.storage.options'])) {
$app['session.storage.options'] = array(); $app['session.storage.options'] = array();
} }
...@@ -70,4 +68,9 @@ class SessionServiceProvider implements ServiceProviderInterface ...@@ -70,4 +68,9 @@ class SessionServiceProvider implements ServiceProviderInterface
$request->getSession()->start(); $request->getSession()->start();
} }
} }
public function boot(Application $app)
{
$app['dispatcher']->addListener(KernelEvents::REQUEST, array($this, 'onKernelRequest'), 128);
}
} }
...@@ -76,14 +76,17 @@ class SwiftmailerServiceProvider implements ServiceProviderInterface ...@@ -76,14 +76,17 @@ class SwiftmailerServiceProvider implements ServiceProviderInterface
return new \Swift_Events_SimpleEventDispatcher(); return new \Swift_Events_SimpleEventDispatcher();
}); });
$app->finish(function () use ($app) {
$app['swiftmailer.spooltransport']->getSpool()->flushQueue($app['swiftmailer.transport']);
});
if (isset($app['swiftmailer.class_path'])) { if (isset($app['swiftmailer.class_path'])) {
require_once $app['swiftmailer.class_path'].'/Swift.php'; require_once $app['swiftmailer.class_path'].'/Swift.php';
\Swift::registerAutoload($app['swiftmailer.class_path'].'/../swift_init.php'); \Swift::registerAutoload($app['swiftmailer.class_path'].'/../swift_init.php');
} }
} }
public function boot(Application $app)
{
$app->finish(function () use ($app) {
$app['swiftmailer.spooltransport']->getSpool()->flushQueue($app['swiftmailer.transport']);
});
}
} }
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex\Provider;
use Silex\Application;
use Silex\ServiceProviderInterface;
/**
* Symfony bridges Provider.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SymfonyBridgesServiceProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
$app['symfony_bridges'] = true;
}
}
...@@ -36,17 +36,9 @@ class TranslationServiceProvider implements ServiceProviderInterface ...@@ -36,17 +36,9 @@ class TranslationServiceProvider implements ServiceProviderInterface
$translator->addLoader('array', $app['translator.loader']); $translator->addLoader('array', $app['translator.loader']);
if (isset($app['translator.messages'])) { foreach ($app['translator.domains'] as $domain => $data) {
foreach ($app['translator.messages'] as $locale => $messages) { foreach ($data as $locale => $messages) {
$translator->addResource('array', $messages, $locale); $translator->addResource('array', $messages, $locale, $domain);
}
}
if (isset($app['translator.domains'])) {
foreach ($app['translator.domains'] as $domain => $data) {
foreach ($data as $locale => $messages) {
$translator->addResource('array', $messages, $locale, $domain);
}
} }
} }
...@@ -61,4 +53,8 @@ class TranslationServiceProvider implements ServiceProviderInterface ...@@ -61,4 +53,8 @@ class TranslationServiceProvider implements ServiceProviderInterface
return new MessageSelector(); return new MessageSelector();
}); });
} }
public function boot(Application $app)
{
}
} }
...@@ -45,7 +45,7 @@ class TwigServiceProvider implements ServiceProviderInterface ...@@ -45,7 +45,7 @@ class TwigServiceProvider implements ServiceProviderInterface
$twig->addExtension(new \Twig_Extension_Debug()); $twig->addExtension(new \Twig_Extension_Debug());
} }
if (isset($app['symfony_bridges'])) { if (class_exists('Symfony\Bridge\Twig\Extension\RoutingExtension')) {
if (isset($app['url_generator'])) { if (isset($app['url_generator'])) {
$twig->addExtension(new TwigRoutingExtension($app['url_generator'])); $twig->addExtension(new TwigRoutingExtension($app['url_generator']));
} }
...@@ -90,4 +90,8 @@ class TwigServiceProvider implements ServiceProviderInterface ...@@ -90,4 +90,8 @@ class TwigServiceProvider implements ServiceProviderInterface
)); ));
}); });
} }
public function boot(Application $app)
{
}
} }
...@@ -31,4 +31,8 @@ class UrlGeneratorServiceProvider implements ServiceProviderInterface ...@@ -31,4 +31,8 @@ class UrlGeneratorServiceProvider implements ServiceProviderInterface
return new UrlGenerator($app['routes'], $app['request_context']); return new UrlGenerator($app['routes'], $app['request_context']);
}); });
} }
public function boot(Application $app)
{
}
} }
...@@ -43,4 +43,8 @@ class ValidatorServiceProvider implements ServiceProviderInterface ...@@ -43,4 +43,8 @@ class ValidatorServiceProvider implements ServiceProviderInterface
return new ConstraintValidatorFactory(); return new ConstraintValidatorFactory();
}); });
} }
public function boot(Application $app)
{
}
} }
...@@ -21,7 +21,19 @@ interface ServiceProviderInterface ...@@ -21,7 +21,19 @@ interface ServiceProviderInterface
/** /**
* Registers services on the given app. * Registers services on the given app.
* *
* This method should only be used to configure services and parameters.
* It should not get services.
*
* @param Application $app An Application instance * @param Application $app An Application instance
*/ */
function register(Application $app); function register(Application $app);
/**
* Bootstraps the application.
*
* This method is called after all services are registers
* and should be used for "dynamic" configuration (whenever
* a service must be requested).
*/
function boot(Application $app);
} }
...@@ -24,7 +24,7 @@ class DoctrineServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -24,7 +24,7 @@ class DoctrineServiceProviderTest extends \PHPUnit_Framework_TestCase
public function setUp() public function setUp()
{ {
if (!is_dir(__DIR__.'/../../../../vendor/doctrine/common/lib') || !is_dir(__DIR__.'/../../../../vendor/doctrine/dbal/lib')) { if (!is_dir(__DIR__.'/../../../../vendor/doctrine/common/lib') || !is_dir(__DIR__.'/../../../../vendor/doctrine/dbal/lib')) {
$this->markTestSkipped('Doctrine Common/DBAL submodules were not installed.'); $this->markTestSkipped('Doctrine Common/DBAL dependencies were not installed.');
} }
} }
......
...@@ -12,10 +12,8 @@ ...@@ -12,10 +12,8 @@
namespace Silex\Tests\Provider; namespace Silex\Tests\Provider;
use Monolog\Handler\TestHandler; use Monolog\Handler\TestHandler;
use Silex\Application; use Silex\Application;
use Silex\Provider\MonologServiceProvider; use Silex\Provider\MonologServiceProvider;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
...@@ -28,28 +26,14 @@ class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -28,28 +26,14 @@ class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase
public function setUp() public function setUp()
{ {
if (!is_dir(__DIR__.'/../../../../vendor/monolog/monolog/src')) { if (!is_dir(__DIR__.'/../../../../vendor/monolog/monolog/src')) {
$this->markTestSkipped('Monolog submodule was not installed.'); $this->markTestSkipped('Monolog dependency was not installed.');
} }
} }
public function testRegister() public function testRequestLogging()
{ {
$app = new Application(); $app = $this->getApplication();
$app->register(new MonologServiceProvider());
$app['monolog.handler'] = $app->share(function () use ($app) {
return new TestHandler($app['monolog.level']);
});
return $app;
}
/**
* @depends testRegister
*/
public function testRequestLogging($app)
{
$app->get('/foo', function () use ($app) { $app->get('/foo', function () use ($app) {
return 'foo'; return 'foo';
}); });
...@@ -59,14 +43,15 @@ class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -59,14 +43,15 @@ class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase
$request = Request::create('/foo'); $request = Request::create('/foo');
$app->handle($request); $app->handle($request);
$this->assertTrue($app['monolog.handler']->hasInfoRecords()); $this->assertTrue($app['monolog.handler']->hasInfo('> GET /foo'));
$this->assertTrue($app['monolog.handler']->hasInfo('< 200'));
$this->assertTrue($app['monolog.handler']->hasInfo('Matched route "GET_foo" (parameters: "_controller": "{}", "_route": "GET_foo")'));
} }
/** public function testManualLogging()
* @depends testRegister
*/
public function testManualLogging($app)
{ {
$app = $this->getApplication();
$app->get('/log', function () use ($app) { $app->get('/log', function () use ($app) {
$app['monolog']->addDebug('logging a message'); $app['monolog']->addDebug('logging a message');
}); });
...@@ -76,14 +61,13 @@ class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -76,14 +61,13 @@ class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase
$request = Request::create('/log'); $request = Request::create('/log');
$app->handle($request); $app->handle($request);
$this->assertTrue($app['monolog.handler']->hasDebugRecords()); $this->assertTrue($app['monolog.handler']->hasDebug('logging a message'));
} }
/** public function testErrorLogging()
* @depends testRegister
*/
public function testErrorLogging($app)
{ {
$app = $this->getApplication();
$app->get('/error', function () { $app->get('/error', function () {
throw new \RuntimeException('very bad error'); throw new \RuntimeException('very bad error');
}); });
...@@ -97,6 +81,19 @@ class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -97,6 +81,19 @@ class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase
$request = Request::create('/error'); $request = Request::create('/error');
$app->handle($request); $app->handle($request);
$this->assertTrue($app['monolog.handler']->hasErrorRecords()); $this->assertTrue($app['monolog.handler']->hasError('very bad error'));
}
protected function getApplication()
{
$app = new Application();
$app->register(new MonologServiceProvider());
$app['monolog.handler'] = $app->share(function () use ($app) {
return new TestHandler($app['monolog.level']);
});
return $app;
} }
} }
...@@ -26,7 +26,7 @@ class TwigServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -26,7 +26,7 @@ class TwigServiceProviderTest extends \PHPUnit_Framework_TestCase
public function setUp() public function setUp()
{ {
if (!is_dir(__DIR__.'/../../../../vendor/twig/twig/lib')) { if (!is_dir(__DIR__.'/../../../../vendor/twig/twig/lib')) {
$this->markTestSkipped('Twig submodule was not installed.'); $this->markTestSkipped('Twig dependency was not installed.');
} }
} }
......
...@@ -24,7 +24,7 @@ class ValidatorServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -24,7 +24,7 @@ class ValidatorServiceProviderTest extends \PHPUnit_Framework_TestCase
public function setUp() public function setUp()
{ {
if (!is_dir(__DIR__.'/../../../../vendor/symfony/validator')) { if (!is_dir(__DIR__.'/../../../../vendor/symfony/validator')) {
$this->markTestSkipped('Validator submodule was not installed.'); $this->markTestSkipped('Validator dependency was not installed.');
} }
} }
......
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