Initial
This commit is contained in:
commit
227f2047a6
|
@ -0,0 +1,5 @@
|
||||||
|
/phpunit.xml
|
||||||
|
/vendor
|
||||||
|
/build
|
||||||
|
/composer.lock
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
language: php
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- SYMFONY_DEPRECATIONS_HELPER=weak
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.composer/cache
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then phpenv config-rm xdebug.ini; fi
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
# symfony/*
|
||||||
|
- sh -c "if [ '$TWIG_VERSION' != '2.0' ]; then sed -i 's/~1.8|~2.0/~1.8/g' composer.json; composer update; fi"
|
||||||
|
- sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.0' ]; then sed -i 's/~2\.8|^3\.0/3.0.*@dev/g' composer.json; composer update; fi"
|
||||||
|
- sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.1' ]; then sed -i 's/~2\.8|^3\.0/3.1.*@dev/g' composer.json; composer update; fi"
|
||||||
|
- sh -c "if [ '$SYMFONY_DEPS_VERSION' = '' ]; then sed -i 's/~2\.8|^3\.0/2.8.*@dev/g' composer.json; composer update; fi"
|
||||||
|
- composer install
|
||||||
|
|
||||||
|
script: phpunit
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- php: 5.5
|
||||||
|
- php: 5.6
|
||||||
|
env: TWIG_VERSION=2.0
|
||||||
|
- php: 5.6
|
||||||
|
env: SYMFONY_DEPS_VERSION=3.0
|
||||||
|
- php: 5.6
|
||||||
|
env: SYMFONY_DEPS_VERSION=3.1
|
||||||
|
- php: 7.0
|
||||||
|
- php: hhvm
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2010-2016 Fabien Potencier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,62 @@
|
||||||
|
Silex, a simple Web Framework
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Silex is a PHP micro-framework to develop websites based on `Symfony
|
||||||
|
components`_::
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
$app->get('/hello/{name}', function ($name) use ($app) {
|
||||||
|
return 'Hello '.$app->escape($name);
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->run();
|
||||||
|
|
||||||
|
Silex works with PHP 5.5.9 or later.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
The recommended way to install Silex is through `Composer`_:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require silex/silex "~2.0"
|
||||||
|
|
||||||
|
Alternatively, you can download the `silex.zip`_ file and extract it.
|
||||||
|
|
||||||
|
More Information
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Read the `documentation`_ for more information and `changelog
|
||||||
|
<doc/changelog.rst>`_ for upgrading information.
|
||||||
|
|
||||||
|
Tests
|
||||||
|
-----
|
||||||
|
|
||||||
|
To run the test suite, you need `Composer`_ and `PHPUnit`_:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer install
|
||||||
|
$ phpunit
|
||||||
|
|
||||||
|
Community
|
||||||
|
---------
|
||||||
|
|
||||||
|
Check out #silex-php on irc.freenode.net.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Silex is licensed under the MIT license.
|
||||||
|
|
||||||
|
.. _Symfony components: http://symfony.com
|
||||||
|
.. _Composer: http://getcomposer.org
|
||||||
|
.. _PHPUnit: https://phpunit.de
|
||||||
|
.. _silex.zip: http://silex.sensiolabs.org/download
|
||||||
|
.. _documentation: http://silex.sensiolabs.org/documentation
|
|
@ -0,0 +1,67 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
PHP=`which php`
|
||||||
|
GIT=`which git`
|
||||||
|
DIR=`$PHP -r "echo dirname(dirname(realpath('$0')));"`
|
||||||
|
|
||||||
|
if [ ! -d "$DIR/build" ]; then
|
||||||
|
mkdir -p $DIR/build
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $DIR/build
|
||||||
|
|
||||||
|
if [ ! -f "composer.phar" ]; then
|
||||||
|
curl -s http://getcomposer.org/installer 2>/dev/null | $PHP >/dev/null 2>/dev/null
|
||||||
|
else
|
||||||
|
$PHP composer.phar self-update >/dev/null 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
for TYPE in slim fat
|
||||||
|
do
|
||||||
|
if [ -d "$DIR/build/skeleton" ]; then
|
||||||
|
rm -rf $DIR/build/skeleton
|
||||||
|
fi
|
||||||
|
mkdir -p $DIR/build/skeleton
|
||||||
|
|
||||||
|
cd "$DIR/build/skeleton"
|
||||||
|
|
||||||
|
mkdir -p web/
|
||||||
|
COMPOSER=$TYPE"_composer.json"
|
||||||
|
cp $DIR/bin/skeleton/$COMPOSER composer.json
|
||||||
|
cp $DIR/bin/skeleton/index.php web/index.php
|
||||||
|
|
||||||
|
$PHP ../composer.phar install -q
|
||||||
|
|
||||||
|
if [ -d "$DIR/build/tmp/silex" ]; then
|
||||||
|
rm -rf $DIR/build/tmp/silex
|
||||||
|
fi
|
||||||
|
mkdir -p $DIR/build/tmp/silex
|
||||||
|
|
||||||
|
cd "$DIR/build/tmp/silex"
|
||||||
|
cp -r ../../skeleton/* .
|
||||||
|
|
||||||
|
find . -name .DS_Store | xargs rm -rf -
|
||||||
|
find . -name .git | xargs rm -rf -
|
||||||
|
find . -name phpunit.xml.* | xargs rm -rf -
|
||||||
|
find . -type d -name Tests | xargs rm -rf -
|
||||||
|
find . -type d -name test* | xargs rm -rf -
|
||||||
|
find . -type d -name doc | xargs rm -rf -
|
||||||
|
find . -type d -name ext | xargs rm -rf -
|
||||||
|
|
||||||
|
export COPY_EXTENDED_ATTRIBUTES_DISABLE=true
|
||||||
|
export COPYFILE_DISABLE=true
|
||||||
|
|
||||||
|
cd "$DIR/build/tmp"
|
||||||
|
|
||||||
|
if [ "slim" = "$TYPE" ]; then
|
||||||
|
NAME="silex"
|
||||||
|
else
|
||||||
|
NAME="silex_fat"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$DIR/build/$NAME.*"
|
||||||
|
tar zcpf "$DIR/build/$NAME.tgz" silex
|
||||||
|
zip -rq "$DIR/build/$NAME.zip" silex
|
||||||
|
rm -rf "$DIR/build/tmp"
|
||||||
|
rm -rf "$DIR/build/skeleton"
|
||||||
|
done
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"silex/silex": "~1.1",
|
||||||
|
"symfony/browser-kit": "~2.3",
|
||||||
|
"symfony/console": "~2.3",
|
||||||
|
"symfony/config": "~2.3",
|
||||||
|
"symfony/css-selector": "~2.3",
|
||||||
|
"symfony/dom-crawler": "~2.3",
|
||||||
|
"symfony/filesystem": "~2.3",
|
||||||
|
"symfony/finder": "~2.3",
|
||||||
|
"symfony/form": "~2.3",
|
||||||
|
"symfony/locale": "~2.3",
|
||||||
|
"symfony/process": "~2.3",
|
||||||
|
"symfony/security": "~2.3",
|
||||||
|
"symfony/serializer": "~2.3",
|
||||||
|
"symfony/translation": "~2.3",
|
||||||
|
"symfony/validator": "~2.3",
|
||||||
|
"symfony/monolog-bridge": "~2.3",
|
||||||
|
"symfony/twig-bridge": "~2.3",
|
||||||
|
"doctrine/dbal": ">=2.2.0,<2.4.0-dev",
|
||||||
|
"swiftmailer/swiftmailer": "5.*"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
$app->get('/hello', function () {
|
||||||
|
return 'Hello!';
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->run();
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"silex/silex": "~1.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"name": "silex/silex",
|
||||||
|
"description": "The PHP micro-framework based on the Symfony Components",
|
||||||
|
"keywords": ["microframework"],
|
||||||
|
"homepage": "http://silex.sensiolabs.org",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Igor Wiedler",
|
||||||
|
"email": "igor@wiedler.ch"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.5.9",
|
||||||
|
"pimple/pimple": "~3.0",
|
||||||
|
"symfony/event-dispatcher": "~2.8|^3.0",
|
||||||
|
"symfony/http-foundation": "~2.8|^3.0",
|
||||||
|
"symfony/http-kernel": "~2.8|^3.0",
|
||||||
|
"symfony/routing": "~2.8|^3.0",
|
||||||
|
"knplabs/knp-snappy": "^0.4.3",
|
||||||
|
"h4cc/wkhtmltopdf-amd64": "0.12.x",
|
||||||
|
"h4cc/wkhtmltoimage-amd64": "0.12.x",
|
||||||
|
"webmozart/json": "^1.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/asset": "~2.8|^3.0",
|
||||||
|
"symfony/expression-language": "~2.8|^3.0",
|
||||||
|
"symfony/security": "~2.8|^3.0",
|
||||||
|
"symfony/config": "~2.8|^3.0",
|
||||||
|
"symfony/form": "~2.8|^3.0",
|
||||||
|
"symfony/browser-kit": "~2.8|^3.0",
|
||||||
|
"symfony/css-selector": "~2.8|^3.0",
|
||||||
|
"symfony/debug": "~2.8|^3.0",
|
||||||
|
"symfony/dom-crawler": "~2.8|^3.0",
|
||||||
|
"symfony/finder": "~2.8|^3.0",
|
||||||
|
"symfony/intl": "~2.8|^3.0",
|
||||||
|
"symfony/monolog-bridge": "~2.8|^3.0",
|
||||||
|
"symfony/doctrine-bridge": "~2.8|^3.0",
|
||||||
|
"symfony/options-resolver": "~2.8|^3.0",
|
||||||
|
"symfony/phpunit-bridge": "~2.8|^3.0",
|
||||||
|
"symfony/process": "~2.8|^3.0",
|
||||||
|
"symfony/serializer": "~2.8|^3.0",
|
||||||
|
"symfony/translation": "~2.8|^3.0",
|
||||||
|
"symfony/twig-bridge": "~2.8|^3.0",
|
||||||
|
"symfony/validator": "~2.8|^3.0",
|
||||||
|
"symfony/var-dumper": "~2.8|^3.0",
|
||||||
|
"twig/twig": "~1.8|~2.0",
|
||||||
|
"doctrine/dbal": "~2.2",
|
||||||
|
"swiftmailer/swiftmailer": "~5",
|
||||||
|
"monolog/monolog": "^1.4.1",
|
||||||
|
"symfony/console": "^3.1"
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"silex/api": "v2.0.2",
|
||||||
|
"silex/providers": "v2.0.2"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Silex\\": "src/Silex",
|
||||||
|
"Sikofitt\\": "src/Sikofitt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev" : {
|
||||||
|
"psr-4": { "Silex\\Tests\\" : "tests/Silex/Tests" }
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev"
|
||||||
|
}
|
|
@ -0,0 +1,353 @@
|
||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
2.0.2 (2016-06-14)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* fixed Symfony 3.1 deprecations
|
||||||
|
|
||||||
|
2.0.1 (2016-05-27)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* fixed the silex form extension registration to allow overriding default ones
|
||||||
|
* removed support for the obsolete Locale Symfony component (uses the Intl one now)
|
||||||
|
* added support for Symfony 3.1
|
||||||
|
|
||||||
|
2.0.0 (2016-05-18)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* decoupled the exception handler from HttpKernelServiceProvider
|
||||||
|
* Switched to BCrypt as the default encoder in the security provider
|
||||||
|
* added full support for RequestMatcher
|
||||||
|
* added support for Symfony Guard
|
||||||
|
* added support for callables in CallbackResolver
|
||||||
|
* added FormTrait::namedForm()
|
||||||
|
* added support for delivery_addresses, delivery_whitelist, and sender_address
|
||||||
|
* added support to register form types / form types extensions / form types guessers as services
|
||||||
|
* added support for callable in mounts (allow nested route collection to be built easily)
|
||||||
|
* added support for conditions on routes
|
||||||
|
* added support for the Symfony VarDumper Component
|
||||||
|
* added a global Twig variable (an AppVariable instance)
|
||||||
|
* [BC BREAK] CSRF has been moved to a standalone provider (``form.secret`` is not available anymore)
|
||||||
|
* added support for the Symfony HttpFoundation Twig bridge extension
|
||||||
|
* added support for the Symfony Asset Component
|
||||||
|
* bumped minimum version of Symfony to 2.8
|
||||||
|
* bumped minimum version of PHP to 5.5.0
|
||||||
|
* Updated Pimple to 3.0
|
||||||
|
* Updated session listeners to extends HttpKernel ones
|
||||||
|
* [BC BREAK] Locale management has been moved to LocaleServiceProvider which must be registered
|
||||||
|
if you want Silex to manage your locale (must also be registered for the translation service provider)
|
||||||
|
* [BC BREAK] Provider interfaces moved to Silex\Api namespace, published as
|
||||||
|
separate package via subtree split
|
||||||
|
* [BC BREAK] ServiceProviderInterface split in to EventListenerProviderInterface
|
||||||
|
and BootableProviderInterface
|
||||||
|
* [BC BREAK] Service Provider support files moved under Silex\Provider
|
||||||
|
namespace, allowing publishing as separate package via sub-tree split
|
||||||
|
* ``monolog.exception.logger_filter`` option added to Monolog service provider
|
||||||
|
* [BC BREAK] ``$app['request']`` service removed, use ``$app['request_stack']`` instead
|
||||||
|
|
||||||
|
1.3.6 (2016-XX-XX)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* n/a
|
||||||
|
|
||||||
|
1.3.5 (2016-01-06)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* fixed typo in SecurityServiceProvider
|
||||||
|
|
||||||
|
1.3.4 (2015-09-15)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* fixed some new deprecations
|
||||||
|
* fixed translation registration for the validators
|
||||||
|
|
||||||
|
1.3.3 (2015-09-08)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* added support for Symfony 3.0 and Twig 2.0
|
||||||
|
* fixed some Form deprecations
|
||||||
|
* removed deprecated method call in the exception handler
|
||||||
|
* fixed Swiftmailer spool flushing when spool is not enabled
|
||||||
|
|
||||||
|
1.3.2 (2015-08-24)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* no changes
|
||||||
|
|
||||||
|
1.3.1 (2015-08-04)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* added missing support for the Expression constraint
|
||||||
|
* fixed the possibility to override translations for validator error messages
|
||||||
|
* fixed sub-mounts with same name clash
|
||||||
|
* fixed session logout handler when a firewall is stateless
|
||||||
|
|
||||||
|
1.3.0 (2015-06-05)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* added a `$app['user']` to get the current user (security provider)
|
||||||
|
* added view handlers
|
||||||
|
* added support for the OPTIONS HTTP method
|
||||||
|
* added caching for the Translator provider
|
||||||
|
* deprecated `$app['exception_handler']->disable()` in favor of `unset($app['exception_handler'])`
|
||||||
|
* made Silex compatible with Symfony 2.7 an 2.8 (and keep compatibility with Symfony 2.3, 2.5, and 2.6)
|
||||||
|
* removed deprecated TwigCoreExtension class (register the new HttpFragmentServiceProvider instead)
|
||||||
|
* bumped minimum version of PHP to 5.3.9
|
||||||
|
|
||||||
|
1.2.5 (2015-06-04)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* no code changes (last version of the 1.2 branch)
|
||||||
|
|
||||||
|
1.2.4 (2015-04-11)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* fixed the exception message when mounting a collection that doesn't return a ControllerCollection
|
||||||
|
* fixed Symfony dependencies (Silex 1.2 is not compatible with Symfony 2.7)
|
||||||
|
|
||||||
|
1.2.3 (2015-01-20)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* fixed remember me listener
|
||||||
|
* fixed translation files loading when they do not exist
|
||||||
|
* allowed global after middlewares to return responses like route specific ones
|
||||||
|
|
||||||
|
1.2.2 (2014-09-26)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* fixed Translator locale management
|
||||||
|
* added support for the $app argument in application middlewares (to make it consistent with route middlewares)
|
||||||
|
* added form.types to the Form provider
|
||||||
|
|
||||||
|
1.2.1 (2014-07-01)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* added support permissions in the Monolog provider
|
||||||
|
* fixed Switfmailer spool where the event dispatcher is different from the other ones
|
||||||
|
* fixed locale when changing it on the translator itself
|
||||||
|
|
||||||
|
1.2.0 (2014-03-29)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Allowed disabling the boot logic of MonologServiceProvider
|
||||||
|
* Reverted "convert attributes on the request that actually exist"
|
||||||
|
* [BC BREAK] Routes are now always added in the order of their registration (even for mounted routes)
|
||||||
|
* Added run() on Route to be able to define the controller code
|
||||||
|
* Deprecated TwigCoreExtension (register the new HttpFragmentServiceProvider instead)
|
||||||
|
* Added HttpFragmentServiceProvider
|
||||||
|
* Allowed a callback to be a method call on a service (before, after, finish, error, on Application; convert, before, after on Controller)
|
||||||
|
|
||||||
|
1.1.3 (2013-XX-XX)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Fixed translator locale management
|
||||||
|
|
||||||
|
1.1.2 (2013-10-30)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Added missing "security.hide_user_not_found" support in SecurityServiceProvider
|
||||||
|
* Fixed event listeners that are registered after the boot via the on() method
|
||||||
|
|
||||||
|
1.0.2 (2013-10-30)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Fixed SecurityServiceProvider to use null as a fake controller so that routes can be dumped
|
||||||
|
|
||||||
|
1.1.1 (2013-10-11)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Removed or replaced deprecated Symfony code
|
||||||
|
* Updated code to take advantages of 2.3 new features
|
||||||
|
* Only convert attributes on the request that actually exist.
|
||||||
|
|
||||||
|
1.1.0 (2013-07-04)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Support for any ``Psr\Log\LoggerInterface`` as opposed to the monolog-bridge
|
||||||
|
one.
|
||||||
|
* Made dispatcher proxy methods ``on``, ``before``, ``after`` and ``error``
|
||||||
|
lazy, so that they will not instantiate the dispatcher early.
|
||||||
|
* Dropped support for 2.1 and 2.2 versions of Symfony.
|
||||||
|
|
||||||
|
1.0.1 (2013-07-04)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Fixed RedirectableUrlMatcher::redirect() when Silex is configured to use a logger
|
||||||
|
* Make ``DoctrineServiceProvider`` multi-db support lazy.
|
||||||
|
|
||||||
|
1.0.0 (2013-05-03)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* **2013-04-12**: Added support for validators as services.
|
||||||
|
|
||||||
|
* **2013-04-01**: Added support for host matching with symfony 2.2::
|
||||||
|
|
||||||
|
$app->match('/', function() {
|
||||||
|
// app-specific action
|
||||||
|
})->host('example.com');
|
||||||
|
|
||||||
|
$app->match('/', function ($user) {
|
||||||
|
// user-specific action
|
||||||
|
})->host('{user}.example.com');
|
||||||
|
|
||||||
|
* **2013-03-08**: Added support for form type extensions and guessers as
|
||||||
|
services.
|
||||||
|
|
||||||
|
* **2013-03-08**: Added support for remember-me via the
|
||||||
|
``RememberMeServiceProvider``.
|
||||||
|
|
||||||
|
* **2013-02-07**: Added ``Application::sendFile()`` to ease sending
|
||||||
|
``BinaryFileResponse``.
|
||||||
|
|
||||||
|
* **2012-11-05**: Filters have been renamed to application middlewares in the
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
* **2012-11-05**: The ``before()``, ``after()``, ``error()``, and ``finish()``
|
||||||
|
listener priorities now set the priority of the underlying Symfony event
|
||||||
|
instead of a custom one before.
|
||||||
|
|
||||||
|
* **2012-11-05**: Removing the default exception handler should now be done
|
||||||
|
via its ``disable()`` method:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
unset($app['exception_handler']);
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
$app['exception_handler']->disable();
|
||||||
|
|
||||||
|
* **2012-07-15**: removed the ``monolog.configure`` service. Use the
|
||||||
|
``extend`` method instead:
|
||||||
|
|
||||||
|
Before::
|
||||||
|
|
||||||
|
$app['monolog.configure'] = $app->protect(function ($monolog) use ($app) {
|
||||||
|
// do something
|
||||||
|
});
|
||||||
|
|
||||||
|
After::
|
||||||
|
|
||||||
|
$app['monolog'] = $app->share($app->extend('monolog', function($monolog, $app) {
|
||||||
|
// do something
|
||||||
|
|
||||||
|
return $monolog;
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
* **2012-06-17**: ``ControllerCollection`` now takes a required route instance
|
||||||
|
as a constructor argument.
|
||||||
|
|
||||||
|
Before::
|
||||||
|
|
||||||
|
$controllers = new ControllerCollection();
|
||||||
|
|
||||||
|
After::
|
||||||
|
|
||||||
|
$controllers = new ControllerCollection(new Route());
|
||||||
|
|
||||||
|
// or even better
|
||||||
|
$controllers = $app['controllers_factory'];
|
||||||
|
|
||||||
|
* **2012-06-17**: added application traits for PHP 5.4
|
||||||
|
|
||||||
|
* **2012-06-16**: renamed ``request.default_locale`` to ``locale``
|
||||||
|
|
||||||
|
* **2012-06-16**: Removed the ``translator.loader`` service. See documentation
|
||||||
|
for how to use XLIFF or YAML-based translation files.
|
||||||
|
|
||||||
|
* **2012-06-15**: removed the ``twig.configure`` service. Use the ``extend``
|
||||||
|
method instead:
|
||||||
|
|
||||||
|
Before::
|
||||||
|
|
||||||
|
$app['twig.configure'] = $app->protect(function ($twig) use ($app) {
|
||||||
|
// do something
|
||||||
|
});
|
||||||
|
|
||||||
|
After::
|
||||||
|
|
||||||
|
$app['twig'] = $app->share($app->extend('twig', function($twig, $app) {
|
||||||
|
// do something
|
||||||
|
|
||||||
|
return $twig;
|
||||||
|
}));
|
||||||
|
|
||||||
|
* **2012-06-13**: Added a route ``before`` middleware
|
||||||
|
|
||||||
|
* **2012-06-13**: Renamed the route ``middleware`` to ``before``
|
||||||
|
|
||||||
|
* **2012-06-13**: Added an extension for the Symfony Security component
|
||||||
|
|
||||||
|
* **2012-05-31**: Made the ``BrowserKit``, ``CssSelector``, ``DomCrawler``,
|
||||||
|
``Finder`` and ``Process`` components optional dependencies. Projects that
|
||||||
|
depend on them (e.g. through functional tests) should add those dependencies
|
||||||
|
to their ``composer.json``.
|
||||||
|
|
||||||
|
* **2012-05-26**: added ``boot()`` to ``ServiceProviderInterface``.
|
||||||
|
|
||||||
|
* **2012-05-26**: Removed ``SymfonyBridgesServiceProvider``. It is now implicit
|
||||||
|
by checking the existence of the bridge.
|
||||||
|
|
||||||
|
* **2012-05-26**: Removed the ``translator.messages`` parameter (use
|
||||||
|
``translator.domains`` instead).
|
||||||
|
|
||||||
|
* **2012-05-24**: Removed the ``autoloader`` service (use composer instead).
|
||||||
|
The ``*.class_path`` settings on all the built-in providers have also been
|
||||||
|
removed in favor of Composer.
|
||||||
|
|
||||||
|
* **2012-05-21**: Changed error() to allow handling specific exceptions.
|
||||||
|
|
||||||
|
* **2012-05-20**: Added a way to define settings on a controller collection.
|
||||||
|
|
||||||
|
* **2012-05-20**: The Request instance is not available anymore from the
|
||||||
|
Application after it has been handled.
|
||||||
|
|
||||||
|
* **2012-04-01**: Added ``finish`` filters.
|
||||||
|
|
||||||
|
* **2012-03-20**: Added ``json`` helper::
|
||||||
|
|
||||||
|
$data = array('some' => 'data');
|
||||||
|
$response = $app->json($data);
|
||||||
|
|
||||||
|
* **2012-03-11**: Added route middlewares.
|
||||||
|
|
||||||
|
* **2012-03-02**: Switched to use Composer for dependency management.
|
||||||
|
|
||||||
|
* **2012-02-27**: Updated to Symfony 2.1 session handling.
|
||||||
|
|
||||||
|
* **2012-01-02**: Introduced support for streaming responses.
|
||||||
|
|
||||||
|
* **2011-09-22**: ``ExtensionInterface`` has been renamed to
|
||||||
|
``ServiceProviderInterface``. All built-in extensions have been renamed
|
||||||
|
accordingly (for instance, ``Silex\Extension\TwigExtension`` has been
|
||||||
|
renamed to ``Silex\Provider\TwigServiceProvider``).
|
||||||
|
|
||||||
|
* **2011-09-22**: The way reusable applications work has changed. The
|
||||||
|
``mount()`` method now takes an instance of ``ControllerCollection`` instead
|
||||||
|
of an ``Application`` one.
|
||||||
|
|
||||||
|
Before::
|
||||||
|
|
||||||
|
$app = new Application();
|
||||||
|
$app->get('/bar', function() { return 'foo'; });
|
||||||
|
|
||||||
|
return $app;
|
||||||
|
|
||||||
|
After::
|
||||||
|
|
||||||
|
$app = new ControllerCollection();
|
||||||
|
$app->get('/bar', function() { return 'foo'; });
|
||||||
|
|
||||||
|
return $app;
|
||||||
|
|
||||||
|
* **2011-08-08**: The controller method configuration is now done on the Controller itself
|
||||||
|
|
||||||
|
Before::
|
||||||
|
|
||||||
|
$app->match('/', function () { echo 'foo'; }, 'GET|POST');
|
||||||
|
|
||||||
|
After::
|
||||||
|
|
||||||
|
$app->match('/', function () { echo 'foo'; })->method('GET|POST');
|
|
@ -0,0 +1,17 @@
|
||||||
|
import sys, os
|
||||||
|
from sphinx.highlighting import lexers
|
||||||
|
from pygments.lexers.web import PhpLexer
|
||||||
|
|
||||||
|
sys.path.append(os.path.abspath('_exts'))
|
||||||
|
|
||||||
|
extensions = []
|
||||||
|
master_doc = 'index'
|
||||||
|
highlight_language = 'php'
|
||||||
|
|
||||||
|
project = u'Silex'
|
||||||
|
copyright = u'2010 Fabien Potencier'
|
||||||
|
|
||||||
|
version = '0'
|
||||||
|
release = '0.0.0'
|
||||||
|
|
||||||
|
lexers['php'] = PhpLexer(startinline=True)
|
|
@ -0,0 +1,34 @@
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
We are open to contributions to the Silex code. If you find a bug or want to
|
||||||
|
contribute a provider, just follow these steps:
|
||||||
|
|
||||||
|
* Fork `the Silex repository <https://github.com/silexphp/Silex>`_;
|
||||||
|
|
||||||
|
* Make your feature addition or bug fix;
|
||||||
|
|
||||||
|
* Add tests for it;
|
||||||
|
|
||||||
|
* Optionally, add some documentation;
|
||||||
|
|
||||||
|
* `Send a pull request
|
||||||
|
<https://help.github.com/articles/creating-a-pull-request>`_, to the correct
|
||||||
|
target branch (1.3 for bug fixes, master for new features).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Any code you contribute must be licensed under the MIT
|
||||||
|
License.
|
||||||
|
|
||||||
|
Writing Documentation
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The documentation is written in `reStructuredText
|
||||||
|
<http://docutils.sourceforge.net/rst.html>`_ and can be generated using `sphinx
|
||||||
|
<http://sphinx-doc.org>`_.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ cd doc
|
||||||
|
$ sphinx-build -b html . build
|
|
@ -0,0 +1,38 @@
|
||||||
|
Converting Errors to Exceptions
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Silex catches exceptions that are thrown from within a request/response cycle.
|
||||||
|
However, it does *not* catch PHP errors and notices. This recipe tells you how
|
||||||
|
to catch them by converting them to exceptions.
|
||||||
|
|
||||||
|
Registering the ErrorHandler
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
The ``Symfony/Debug`` package has an ``ErrorHandler`` class that solves this
|
||||||
|
problem. It converts all errors to exceptions, and exceptions are then caught
|
||||||
|
by Silex.
|
||||||
|
|
||||||
|
Register it by calling the static ``register`` method::
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
|
|
||||||
|
ErrorHandler::register();
|
||||||
|
|
||||||
|
It is recommended that you do this as early as possible.
|
||||||
|
|
||||||
|
Handling fatal errors
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
To handle fatal errors, you can additionally register a global
|
||||||
|
``ExceptionHandler``::
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
|
|
||||||
|
ExceptionHandler::register();
|
||||||
|
|
||||||
|
In production you may want to disable the debug output by passing ``false`` as
|
||||||
|
the ``$debug`` argument::
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\ExceptionHandler;
|
||||||
|
|
||||||
|
ExceptionHandler::register(false);
|
|
@ -0,0 +1,36 @@
|
||||||
|
Disabling CSRF Protection on a Form using the FormExtension
|
||||||
|
===========================================================
|
||||||
|
|
||||||
|
The *FormExtension* provides a service for building form in your application
|
||||||
|
with the Symfony Form component. When the :doc:`CSRF Service Provider
|
||||||
|
</providers/csrf>` is registered, the *FormExtension* uses the CSRF Protection
|
||||||
|
avoiding Cross-site request forgery, a method by which a malicious user
|
||||||
|
attempts to make your legitimate users unknowingly submit data that they don't
|
||||||
|
intend to submit.
|
||||||
|
|
||||||
|
You can find more details about CSRF Protection and CSRF token in the
|
||||||
|
`Symfony 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 not to use this protection. The easiest way to avoid this is to
|
||||||
|
understand that it is possible to give specific options to your form builder
|
||||||
|
through the ``createBuilder()`` function.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$form = $app['form.factory']->createBuilder('form', null, array('csrf_protection' => false));
|
||||||
|
|
||||||
|
That's it, your form could be submitted from everywhere without CSRF Protection.
|
||||||
|
|
||||||
|
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 Symfony
|
||||||
|
``getDefaultOptions()`` method in your form classes. `See more here
|
||||||
|
<http://symfony.com/doc/current/book/forms.html#book-form-creating-form-classes>`_.
|
|
@ -0,0 +1,182 @@
|
||||||
|
How to Create a Custom Authentication System with Guard
|
||||||
|
=======================================================
|
||||||
|
|
||||||
|
Whether you need to build a traditional login form, an API token
|
||||||
|
authentication system or you need to integrate with some proprietary
|
||||||
|
single-sign-on system, the Guard component can make it easy... and fun!
|
||||||
|
|
||||||
|
In this example, you'll build an API token authentication system and
|
||||||
|
learn how to work with Guard.
|
||||||
|
|
||||||
|
Step 1) Create the Authenticator Class
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Suppose you have an API where your clients will send an X-AUTH-TOKEN
|
||||||
|
header on each request. This token is composed of the username followed
|
||||||
|
by a password, separated by a colon (e.g. ``X-AUTH-TOKEN: coolguy:awesomepassword``).
|
||||||
|
Your job is to read this, find theassociated user (if any) and check
|
||||||
|
the password.
|
||||||
|
|
||||||
|
To create a custom authentication system, just create a class and make
|
||||||
|
it implement GuardAuthenticatorInterface. Or, extend the simpler
|
||||||
|
AbstractGuardAuthenticator. This requires you to implement six methods:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
|
||||||
|
class TokenAuthenticator extends AbstractGuardAuthenticator
|
||||||
|
{
|
||||||
|
private $encoderFactory;
|
||||||
|
|
||||||
|
public function __construct(EncoderFactoryInterface $encoderFactory)
|
||||||
|
{
|
||||||
|
$this->encoderFactory = $encoderFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCredentials(Request $request)
|
||||||
|
{
|
||||||
|
// Checks if the credential header is provided
|
||||||
|
if (!$token = $request->headers->get('X-AUTH-TOKEN')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the header or ignore it if the format is incorrect.
|
||||||
|
if (false === strpos(':', $token)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list($username, $secret) = explode(':', $token, 2);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'username' => $username,
|
||||||
|
'secret' => $secret,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser($credentials, UserProviderInterface $userProvider)
|
||||||
|
{
|
||||||
|
return $userProvider->loadUserByUsername($credentials['username']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkCredentials($credentials, UserInterface $user)
|
||||||
|
{
|
||||||
|
// check credentials - e.g. make sure the password is valid
|
||||||
|
// return true to cause authentication success
|
||||||
|
|
||||||
|
$encoder = $this->encoderFactory->getEncoder($user);
|
||||||
|
|
||||||
|
return $encoder->isPasswordValid(
|
||||||
|
$user->getPassword(),
|
||||||
|
$credentials['secret'],
|
||||||
|
$user->getSalt()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
|
||||||
|
{
|
||||||
|
// on success, let the request continue
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
|
||||||
|
{
|
||||||
|
$data = array(
|
||||||
|
'message' => strtr($exception->getMessageKey(), $exception->getMessageData()),
|
||||||
|
|
||||||
|
// or to translate this message
|
||||||
|
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse($data, 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when authentication is needed, but it's not sent
|
||||||
|
*/
|
||||||
|
public function start(Request $request, AuthenticationException $authException = null)
|
||||||
|
{
|
||||||
|
$data = array(
|
||||||
|
// you might translate this message
|
||||||
|
'message' => 'Authentication Required',
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse($data, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsRememberMe()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Step 2) Configure the Authenticator
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
To finish this, register the class as a service:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app['app.token_authenticator'] = function ($app) {
|
||||||
|
return new App\Security\TokenAuthenticator($app['security.encoder_factory']);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Finally, configure your `security.firewalls` key to use this authenticator:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app['security.firewalls'] => array(
|
||||||
|
'main' => array(
|
||||||
|
'guard' => array(
|
||||||
|
'authenticators' => array(
|
||||||
|
'app.token_authenticator'
|
||||||
|
),
|
||||||
|
|
||||||
|
// Using more than 1 authenticator, you must specify
|
||||||
|
// which one is used as entry point.
|
||||||
|
// 'entry_point' => 'app.token_authenticator',
|
||||||
|
),
|
||||||
|
// configure where your users come from. Hardcode them, or load them from somewhere
|
||||||
|
// http://silex.sensiolabs.org/doc/providers/security.html#defining-a-custom-user-provider
|
||||||
|
'users' => array(
|
||||||
|
'victoria' => array('ROLE_USER', 'randomsecret'),
|
||||||
|
),
|
||||||
|
// 'anonymous' => true
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
You can use many authenticators, they are executed by the order
|
||||||
|
they are configured.
|
||||||
|
|
||||||
|
You did it! You now have a fully-working API token authentication
|
||||||
|
system. If your homepage required ROLE_USER, then you could test it
|
||||||
|
under different conditions:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# test with no token
|
||||||
|
curl http://localhost:8000/
|
||||||
|
# {"message":"Authentication Required"}
|
||||||
|
|
||||||
|
# test with a bad token
|
||||||
|
curl -H "X-AUTH-TOKEN: alan" http://localhost:8000/
|
||||||
|
# {"message":"Username could not be found."}
|
||||||
|
|
||||||
|
# test with a working token
|
||||||
|
curl -H "X-AUTH-TOKEN: victoria:randomsecret" http://localhost:8000/
|
||||||
|
# the homepage controller is executed: the page loads normally
|
||||||
|
|
||||||
|
For more details read the Symfony cookbook entry on
|
||||||
|
`How to Create a Custom Authentication System with Guard <http://symfony.com/doc/current/cookbook/security/guard-authentication.html>`_.
|
|
@ -0,0 +1,40 @@
|
||||||
|
Cookbook
|
||||||
|
========
|
||||||
|
|
||||||
|
The cookbook section contains recipes for solving specific problems.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
json_request_body
|
||||||
|
session_storage
|
||||||
|
form_no_csrf
|
||||||
|
validator_yaml
|
||||||
|
sub_requests
|
||||||
|
error_handler
|
||||||
|
multiple_loggers
|
||||||
|
guard_authentication
|
||||||
|
|
||||||
|
Recipes
|
||||||
|
-------
|
||||||
|
|
||||||
|
* :doc:`Accepting a JSON Request Body <json_request_body>` A common need when
|
||||||
|
building a restful API is the ability to accept a JSON encoded entity from
|
||||||
|
the request body.
|
||||||
|
|
||||||
|
* :doc:`Using PdoSessionStorage to store Sessions in the Database
|
||||||
|
<session_storage>`.
|
||||||
|
|
||||||
|
* :doc:`Disabling the CSRF Protection on a Form using the FormExtension
|
||||||
|
<form_no_csrf>`.
|
||||||
|
|
||||||
|
* :doc:`Using YAML to configure Validation <validator_yaml>`.
|
||||||
|
|
||||||
|
* :doc:`Making sub-Requests <sub_requests>`.
|
||||||
|
|
||||||
|
* :doc:`Converting Errors to Exceptions <error_handler>`.
|
||||||
|
|
||||||
|
* :doc:`Using multiple Monolog Loggers <multiple_loggers>`.
|
||||||
|
|
||||||
|
* :doc:`How to Create a Custom Authentication System with Guard <guard_authentication>`.
|
|
@ -0,0 +1,95 @@
|
||||||
|
Accepting a JSON Request Body
|
||||||
|
=============================
|
||||||
|
|
||||||
|
A common need when building a restful API is the ability to accept a JSON
|
||||||
|
encoded entity from the request body.
|
||||||
|
|
||||||
|
An example for such an API could be a blog post creation.
|
||||||
|
|
||||||
|
Example API
|
||||||
|
-----------
|
||||||
|
|
||||||
|
In this example we will create an API for creating a blog post. The following
|
||||||
|
is a spec of how we want it to work.
|
||||||
|
|
||||||
|
Request
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
In the request we send the data for the blog post as a JSON object. We also
|
||||||
|
indicate that using the ``Content-Type`` header:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
POST /blog/posts
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
Content-Length: 57
|
||||||
|
|
||||||
|
{"title":"Hello World!","body":"This is my first post!"}
|
||||||
|
|
||||||
|
Response
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
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
|
||||||
|
JSON:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
HTTP/1.1 201 Created
|
||||||
|
Content-Type: application/json
|
||||||
|
Content-Length: 65
|
||||||
|
Connection: close
|
||||||
|
|
||||||
|
{"id":"1","title":"Hello World!","body":"This is my first post!"}
|
||||||
|
|
||||||
|
Parsing the request body
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The request body should only be parsed as JSON if the ``Content-Type`` header
|
||||||
|
begins with ``application/json``. Since we want to do this for every request,
|
||||||
|
the easiest solution is to use an application before middleware.
|
||||||
|
|
||||||
|
We simply use ``json_decode`` to parse the content of the request and then
|
||||||
|
replace the request data on the ``$request`` object::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||||
|
|
||||||
|
$app->before(function (Request $request) {
|
||||||
|
if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
|
||||||
|
$data = json_decode($request->getContent(), true);
|
||||||
|
$request->request->replace(is_array($data) ? $data : array());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Controller implementation
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Our controller will create a new blog post from the data provided and will
|
||||||
|
return the post object, including its ``id``, as JSON::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
$app->post('/blog/posts', function (Request $request) use ($app) {
|
||||||
|
$post = array(
|
||||||
|
'title' => $request->request->get('title'),
|
||||||
|
'body' => $request->request->get('body'),
|
||||||
|
);
|
||||||
|
|
||||||
|
$post['id'] = createPost($post);
|
||||||
|
|
||||||
|
return $app->json($post, 201);
|
||||||
|
});
|
||||||
|
|
||||||
|
Manual testing
|
||||||
|
--------------
|
||||||
|
|
||||||
|
In order to manually test our API, we can use the ``curl`` command line
|
||||||
|
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'
|
||||||
|
{"id":"1","title":"Hello World!","body":"This is my first post!"}
|
|
@ -0,0 +1,69 @@
|
||||||
|
Using multiple Monolog Loggers
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Having separate instances of Monolog for different parts of your system is
|
||||||
|
often desirable and allows you to configure them independently, allowing for fine
|
||||||
|
grained control of where your logging goes and in what detail.
|
||||||
|
|
||||||
|
This simple example allows you to quickly configure several monolog instances,
|
||||||
|
using the bundled handler, but each with a different channel.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app['monolog.factory'] = $app->protect(function ($name) use ($app) {
|
||||||
|
$log = new $app['monolog.logger.class']($name);
|
||||||
|
$log->pushHandler($app['monolog.handler']);
|
||||||
|
|
||||||
|
return $log;
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (array('auth', 'payments', 'stats') as $channel) {
|
||||||
|
$app['monolog.'.$channel] = function ($app) use ($channel) {
|
||||||
|
return $app['monolog.factory']($channel);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
As your application grows, or your logging needs for certain areas of the
|
||||||
|
system become apparent, it should be straightforward to then configure that
|
||||||
|
particular service separately, including your customizations.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
|
||||||
|
$app['monolog.payments'] = function ($app) {
|
||||||
|
$log = new $app['monolog.logger.class']('payments');
|
||||||
|
$handler = new StreamHandler($app['monolog.payments.logfile'], $app['monolog.payment.level']);
|
||||||
|
$log->pushHandler($handler);
|
||||||
|
|
||||||
|
return $log;
|
||||||
|
};
|
||||||
|
|
||||||
|
Alternatively, you could attempt to make the factory more complicated, and rely
|
||||||
|
on some conventions, such as checking for an array of handlers registered with
|
||||||
|
the container with the channel name, defaulting to the bundled handler.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
$app['monolog.factory'] = $app->protect(function ($name) use ($app) {
|
||||||
|
$log = new $app['monolog.logger.class']($name);
|
||||||
|
|
||||||
|
$handlers = isset($app['monolog.'.$name.'.handlers'])
|
||||||
|
? $app['monolog.'.$name.'.handlers']
|
||||||
|
: array($app['monolog.handler']);
|
||||||
|
|
||||||
|
foreach ($handlers as $handler) {
|
||||||
|
$log->pushHandler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $log;
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['monolog.payments.handlers'] = function ($app) {
|
||||||
|
return array(
|
||||||
|
new StreamHandler(__DIR__.'/../payments.log', Logger::DEBUG),
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,93 @@
|
||||||
|
Using PdoSessionStorage to store Sessions in the Database
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
By default, the :doc:`SessionServiceProvider </providers/session>` writes
|
||||||
|
session information in files using Symfony NativeFileSessionStorage. Most
|
||||||
|
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 environment.
|
||||||
|
|
||||||
|
Symfony's `NativeSessionStorage
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.html>`_
|
||||||
|
has multiple storage handlers and one of them uses PDO to store sessions,
|
||||||
|
`PdoSessionHandler
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.html>`_.
|
||||||
|
To use it, replace the ``session.storage.handler`` service in your application
|
||||||
|
like explained below.
|
||||||
|
|
||||||
|
With a dedicated PDO service
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\SessionServiceProvider());
|
||||||
|
|
||||||
|
$app['pdo.dsn'] = 'mysql:dbname=mydatabase';
|
||||||
|
$app['pdo.user'] = 'myuser';
|
||||||
|
$app['pdo.password'] = 'mypassword';
|
||||||
|
|
||||||
|
$app['session.db_options'] = array(
|
||||||
|
'db_table' => 'session',
|
||||||
|
'db_id_col' => 'session_id',
|
||||||
|
'db_data_col' => 'session_value',
|
||||||
|
'db_time_col' => 'session_time',
|
||||||
|
);
|
||||||
|
|
||||||
|
$app['pdo'] = function () use ($app) {
|
||||||
|
return new PDO(
|
||||||
|
$app['pdo.dsn'],
|
||||||
|
$app['pdo.user'],
|
||||||
|
$app['pdo.password']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['session.storage.handler'] = function () use ($app) {
|
||||||
|
return new PdoSessionHandler(
|
||||||
|
$app['pdo'],
|
||||||
|
$app['session.db_options'],
|
||||||
|
$app['session.storage.options']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Using the DoctrineServiceProvider
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
When using the :doc:`DoctrineServiceProvider </providers/doctrine>` You don't
|
||||||
|
have to make another database connection, simply pass the getWrappedConnection method.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\SessionServiceProvider());
|
||||||
|
|
||||||
|
$app['session.db_options'] = array(
|
||||||
|
'db_table' => 'session',
|
||||||
|
'db_id_col' => 'session_id',
|
||||||
|
'db_data_col' => 'session_value',
|
||||||
|
'db_lifetime_col' => 'session_lifetime',
|
||||||
|
'db_time_col' => 'session_time',
|
||||||
|
);
|
||||||
|
|
||||||
|
$app['session.storage.handler'] = function () use ($app) {
|
||||||
|
return new PdoSessionHandler(
|
||||||
|
$app['db']->getWrappedConnection(),
|
||||||
|
$app['session.db_options'],
|
||||||
|
$app['session.storage.options']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Database structure
|
||||||
|
------------------
|
||||||
|
|
||||||
|
PdoSessionStorage needs a database table with 3 columns:
|
||||||
|
|
||||||
|
* ``session_id``: ID column (VARCHAR(255) or larger)
|
||||||
|
* ``session_value``: Value column (TEXT or CLOB)
|
||||||
|
* ``session_lifetime``: Lifetime column (INTEGER)
|
||||||
|
* ``session_time``: Time column (INTEGER)
|
||||||
|
|
||||||
|
You can find examples of SQL statements to create the session table in the
|
||||||
|
`Symfony cookbook
|
||||||
|
<http://symfony.com/doc/current/cookbook/configuration/pdo_session_storage.html#example-sql-statements>`_
|
|
@ -0,0 +1,137 @@
|
||||||
|
Making sub-Requests
|
||||||
|
===================
|
||||||
|
|
||||||
|
Since Silex is based on the ``HttpKernelInterface``, it allows you to simulate
|
||||||
|
requests against your application. This means that you can embed a page within
|
||||||
|
another, it also allows you to forward a request which is essentially an
|
||||||
|
internal redirect that does not change the URL.
|
||||||
|
|
||||||
|
Basics
|
||||||
|
------
|
||||||
|
|
||||||
|
You can make a sub-request by calling the ``handle`` method on the
|
||||||
|
``Application``. This method takes three arguments:
|
||||||
|
|
||||||
|
* ``$request``: An instance of the ``Request`` class which represents the
|
||||||
|
HTTP request.
|
||||||
|
|
||||||
|
* ``$type``: Must be either ``HttpKernelInterface::MASTER_REQUEST`` or
|
||||||
|
``HttpKernelInterface::SUB_REQUEST``. Certain listeners are only executed for
|
||||||
|
the master request, so it's important that this is set to ``SUB_REQUEST``.
|
||||||
|
|
||||||
|
* ``$catch``: Catches exceptions and turns them into a response with status code
|
||||||
|
``500``. This argument defaults to ``true``. For sub-requests you will most
|
||||||
|
likely want to set it to ``false``.
|
||||||
|
|
||||||
|
By calling ``handle``, you can make a sub-request manually. Here's an example::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
$subRequest = Request::create('/');
|
||||||
|
$response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);
|
||||||
|
|
||||||
|
There's some more things that you need to keep in mind though. In most cases
|
||||||
|
you will want to forward some parts of the current master request to the
|
||||||
|
sub-request like cookies, server information, or the session.
|
||||||
|
|
||||||
|
Here is a more advanced example that forwards said information (``$request``
|
||||||
|
holds the master request)::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
$subRequest = Request::create('/', 'GET', array(), $request->cookies->all(), array(), $request->server->all());
|
||||||
|
if ($request->getSession()) {
|
||||||
|
$subRequest->setSession($request->getSession());
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);
|
||||||
|
|
||||||
|
To forward this response to the client, you can simply return it from a
|
||||||
|
controller::
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
$app->get('/foo', function (Application $app, Request $request) {
|
||||||
|
$subRequest = Request::create('/', ...);
|
||||||
|
$response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
});
|
||||||
|
|
||||||
|
If you want to embed the response as part of a larger page you can call
|
||||||
|
``Response::getContent``::
|
||||||
|
|
||||||
|
$header = ...;
|
||||||
|
$footer = ...;
|
||||||
|
$body = $response->getContent();
|
||||||
|
|
||||||
|
return $header.$body.$footer;
|
||||||
|
|
||||||
|
Rendering pages in Twig templates
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
The :doc:`TwigServiceProvider </providers/twig>` provides a ``render``
|
||||||
|
function that you can use in Twig templates. It gives you a convenient way to
|
||||||
|
embed pages.
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{{ render('/sidebar') }}
|
||||||
|
|
||||||
|
For details, refer to the :doc:`TwigServiceProvider </providers/twig>` docs.
|
||||||
|
|
||||||
|
Edge Side Includes
|
||||||
|
------------------
|
||||||
|
|
||||||
|
You can use ESI either through the :doc:`HttpCacheServiceProvider
|
||||||
|
</providers/http_cache>` or a reverse proxy cache such as Varnish. This also
|
||||||
|
allows you to embed pages, however it also gives you the benefit of caching
|
||||||
|
parts of the page.
|
||||||
|
|
||||||
|
Here is an example of how you would embed a page via ESI:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
<esi:include src="/sidebar" />
|
||||||
|
|
||||||
|
For details, refer to the :doc:`HttpCacheServiceProvider
|
||||||
|
</providers/http_cache>` docs.
|
||||||
|
|
||||||
|
Dealing with the request base URL
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
One thing to watch out for is the base URL. If your application is not
|
||||||
|
hosted at the webroot of your web server, then you may have an URL like
|
||||||
|
``http://example.org/foo/index.php/articles/42``.
|
||||||
|
|
||||||
|
In this case, ``/foo/index.php`` is your request base path. Silex accounts for
|
||||||
|
this path prefix in the routing process, it reads it from
|
||||||
|
``$request->server``. In the context of sub-requests this can lead to issues,
|
||||||
|
because if you do not prepend the base path the request could mistake a part
|
||||||
|
of the path you want to match as the base path and cut it off.
|
||||||
|
|
||||||
|
You can prevent that from happening by always prepending the base path when
|
||||||
|
constructing a request::
|
||||||
|
|
||||||
|
$url = $request->getUriForPath('/');
|
||||||
|
$subRequest = Request::create($url, 'GET', array(), $request->cookies->all(), array(), $request->server->all());
|
||||||
|
|
||||||
|
This is something to be aware of when making sub-requests by hand.
|
||||||
|
|
||||||
|
Services depending on the Request
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
The container is a concept that is global to a Silex application, since the
|
||||||
|
application object **is** the container. Any request that is run against an
|
||||||
|
application will re-use the same set of services. Since these services are
|
||||||
|
mutable, code in a master request can affect the sub-requests and vice versa.
|
||||||
|
Any services depending on the ``request`` service will store the first request
|
||||||
|
that they get (could be master or sub-request), and keep using it, even if
|
||||||
|
that request is already over.
|
||||||
|
|
||||||
|
Instead of injecting the ``request`` service, you should always inject the
|
||||||
|
``request_stack`` one instead.
|
|
@ -0,0 +1,35 @@
|
||||||
|
Using YAML to configure Validation
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Simplicity is at the heart of Silex so there is no out of the box solution to
|
||||||
|
use YAML files for validation. But this doesn't mean that this is not
|
||||||
|
possible. Let's see how to do it.
|
||||||
|
|
||||||
|
First, you need to install the YAML Component:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/yaml
|
||||||
|
|
||||||
|
Next, you need to tell the Validation Service that you are not using
|
||||||
|
``StaticMethodLoader`` to load your class metadata but a YAML file::
|
||||||
|
|
||||||
|
$app->register(new ValidatorServiceProvider());
|
||||||
|
|
||||||
|
$app['validator.mapping.class_metadata_factory'] = new Symfony\Component\Validator\Mapping\ClassMetadataFactory(
|
||||||
|
new Symfony\Component\Validator\Mapping\Loader\YamlFileLoader(__DIR__.'/validation.yml')
|
||||||
|
);
|
||||||
|
|
||||||
|
Now, we can replace the usage of the static method and move all the validation
|
||||||
|
rules to ``validation.yml``:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# validation.yml
|
||||||
|
Post:
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
- NotNull: ~
|
||||||
|
- NotBlank: ~
|
||||||
|
body:
|
||||||
|
- Min: 100
|
|
@ -0,0 +1,19 @@
|
||||||
|
The Book
|
||||||
|
========
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
intro
|
||||||
|
usage
|
||||||
|
middlewares
|
||||||
|
organizing_controllers
|
||||||
|
services
|
||||||
|
providers
|
||||||
|
testing
|
||||||
|
cookbook/index
|
||||||
|
internals
|
||||||
|
contributing
|
||||||
|
providers/index
|
||||||
|
web_servers
|
||||||
|
changelog
|
|
@ -0,0 +1,84 @@
|
||||||
|
Internals
|
||||||
|
=========
|
||||||
|
|
||||||
|
This chapter will tell you how Silex works internally.
|
||||||
|
|
||||||
|
Silex
|
||||||
|
-----
|
||||||
|
|
||||||
|
Application
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
The application is the main interface to Silex. It implements Symfony's
|
||||||
|
`HttpKernelInterface
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernelInterface.html>`_,
|
||||||
|
so you can pass a `Request
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html>`_
|
||||||
|
to the ``handle`` method and it will return a `Response
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html>`_.
|
||||||
|
|
||||||
|
It extends the ``Pimple`` service container, allowing for flexibility on the
|
||||||
|
outside as well as the inside. You could replace any service, and you are also
|
||||||
|
able to read them.
|
||||||
|
|
||||||
|
The application makes strong use of the `EventDispatcher
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/EventDispatcher/EventDispatcher
|
||||||
|
.html>`_ to hook into the Symfony `HttpKernel
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernel.html>`_
|
||||||
|
events. This allows fetching the ``Request``, converting string responses into
|
||||||
|
``Response`` objects and handling Exceptions. We also use it to dispatch some
|
||||||
|
custom events like before/after middlewares and errors.
|
||||||
|
|
||||||
|
Controller
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
The Symfony `Route
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Routing/Route.html>`_ is
|
||||||
|
actually quite powerful. Routes can be named, which allows for URL generation.
|
||||||
|
They can also have requirements for the variable parts. In order to allow
|
||||||
|
setting these through a nice interface, the ``match`` method (which is used by
|
||||||
|
``get``, ``post``, etc.) returns an instance of the ``Controller``, which
|
||||||
|
wraps a route.
|
||||||
|
|
||||||
|
ControllerCollection
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
One of the goals of exposing the `RouteCollection
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_
|
||||||
|
was to make it mutable, so providers could add stuff to it. The challenge here
|
||||||
|
is the fact that routes know nothing about their name. The name only has
|
||||||
|
meaning in context of the ``RouteCollection`` and cannot be changed.
|
||||||
|
|
||||||
|
To solve this challenge we came up with a staging area for routes. The
|
||||||
|
``ControllerCollection`` holds the controllers until ``flush`` is called, at
|
||||||
|
which point the routes are added to the ``RouteCollection``. Also, the
|
||||||
|
controllers are then frozen. This means that they can no longer be modified
|
||||||
|
and will throw an Exception if you try to do so.
|
||||||
|
|
||||||
|
Unfortunately no good way for flushing implicitly could be found, which is why
|
||||||
|
flushing is now always explicit. The Application will flush, but if you want
|
||||||
|
to read the ``ControllerCollection`` before the request takes place, you will
|
||||||
|
have to call flush yourself.
|
||||||
|
|
||||||
|
The ``Application`` provides a shortcut ``flush`` method for flushing the
|
||||||
|
``ControllerCollection``.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Instead of creating an instance of ``RouteCollection`` yourself, use the
|
||||||
|
``$app['controllers_factory']`` factory instead.
|
||||||
|
|
||||||
|
Symfony
|
||||||
|
-------
|
||||||
|
|
||||||
|
Following Symfony components are used by Silex:
|
||||||
|
|
||||||
|
* **HttpFoundation**: For ``Request`` and ``Response``.
|
||||||
|
|
||||||
|
* **HttpKernel**: Because we need a heart.
|
||||||
|
|
||||||
|
* **Routing**: For matching defined routes.
|
||||||
|
|
||||||
|
* **EventDispatcher**: For hooking into the HttpKernel.
|
||||||
|
|
||||||
|
For more information, `check out the Symfony website <http://symfony.com/>`_.
|
|
@ -0,0 +1,50 @@
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
Silex is a PHP microframework. It is built on the shoulders of `Symfony`_ and
|
||||||
|
`Pimple`_ and also inspired by `Sinatra`_.
|
||||||
|
|
||||||
|
Silex aims to be:
|
||||||
|
|
||||||
|
* *Concise*: Silex exposes an intuitive and concise API.
|
||||||
|
|
||||||
|
* *Extensible*: Silex has an extension system based around the Pimple
|
||||||
|
service-container that makes it easy to tie in third party libraries.
|
||||||
|
|
||||||
|
* *Testable*: Silex uses Symfony's HttpKernel which abstracts request and
|
||||||
|
response. This makes it very easy to test apps and the framework itself. It
|
||||||
|
also respects the HTTP specification and encourages its proper use.
|
||||||
|
|
||||||
|
In a nutshell, you define controllers and map them to routes, all in one step.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// web/index.php
|
||||||
|
require_once __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
$app->get('/hello/{name}', function ($name) use ($app) {
|
||||||
|
return 'Hello '.$app->escape($name);
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->run();
|
||||||
|
|
||||||
|
All that is needed to get access to the Framework is to include the
|
||||||
|
autoloader.
|
||||||
|
|
||||||
|
Next, a route for ``/hello/{name}`` that matches for ``GET`` requests is
|
||||||
|
defined. When the route matches, the function is executed and the return value
|
||||||
|
is sent back to the client.
|
||||||
|
|
||||||
|
Finally, the app is run. Visit ``/hello/world`` to see the result. It's really
|
||||||
|
that easy!
|
||||||
|
|
||||||
|
.. _Symfony: http://symfony.com/
|
||||||
|
.. _Pimple: http://pimple.sensiolabs.org/
|
||||||
|
.. _Sinatra: http://www.sinatrarb.com/
|
|
@ -0,0 +1,162 @@
|
||||||
|
Middleware
|
||||||
|
==========
|
||||||
|
|
||||||
|
Silex allows you to run code, that changes the default Silex behavior, at
|
||||||
|
different stages during the handling of a request through *middleware*:
|
||||||
|
|
||||||
|
* *Application middleware* is triggered independently of the current handled
|
||||||
|
request;
|
||||||
|
|
||||||
|
* *Route middleware* is triggered when its associated route is matched.
|
||||||
|
|
||||||
|
Application Middleware
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Application middleware is only run for the "master" Request.
|
||||||
|
|
||||||
|
Before Middleware
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A *before* application middleware allows you to tweak the Request before the
|
||||||
|
controller is executed::
|
||||||
|
|
||||||
|
$app->before(function (Request $request, Application $app) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
By default, the middleware is run after the routing and the security.
|
||||||
|
|
||||||
|
If you want your middleware to be run even if an exception is thrown early on
|
||||||
|
(on a 404 or 403 error for instance), then, you need to register it as an
|
||||||
|
early event::
|
||||||
|
|
||||||
|
$app->before(function (Request $request, Application $app) {
|
||||||
|
// ...
|
||||||
|
}, Application::EARLY_EVENT);
|
||||||
|
|
||||||
|
In this case, the routing and the security won't have been executed, and so you
|
||||||
|
won't have access to the locale, the current route, or the security user.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The before middleware is an event registered on the Symfony *request*
|
||||||
|
event.
|
||||||
|
|
||||||
|
After Middleware
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
An *after* application middleware allows you to tweak the Response before it
|
||||||
|
is sent to the client::
|
||||||
|
|
||||||
|
$app->after(function (Request $request, Response $response) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The after middleware is an event registered on the Symfony *response*
|
||||||
|
event.
|
||||||
|
|
||||||
|
Finish Middleware
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A *finish* application middleware allows you to execute tasks after the
|
||||||
|
Response has been sent to the client (like sending emails or logging)::
|
||||||
|
|
||||||
|
$app->finish(function (Request $request, Response $response) {
|
||||||
|
// ...
|
||||||
|
// Warning: modifications to the Request or Response will be ignored
|
||||||
|
});
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The finish middleware is an event registered on the Symfony *terminate*
|
||||||
|
event.
|
||||||
|
|
||||||
|
Route Middleware
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Route middleware is added to routes or route collections and it is only
|
||||||
|
triggered when the corresponding route is matched. You can also stack them::
|
||||||
|
|
||||||
|
$app->get('/somewhere', function () {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->before($before1)
|
||||||
|
->before($before2)
|
||||||
|
->after($after1)
|
||||||
|
->after($after2)
|
||||||
|
;
|
||||||
|
|
||||||
|
Before Middleware
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A *before* route middleware is fired just before the route callback, but after
|
||||||
|
the *before* application middleware::
|
||||||
|
|
||||||
|
$before = function (Request $request, Application $app) {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->get('/somewhere', function () {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->before($before);
|
||||||
|
|
||||||
|
After Middleware
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
An *after* route middleware is fired just after the route callback, but before
|
||||||
|
the application *after* application middleware::
|
||||||
|
|
||||||
|
$after = function (Request $request, Response $response, Application $app) {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->get('/somewhere', function () {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->after($after);
|
||||||
|
|
||||||
|
Middleware Priority
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
You can add as much middleware as you want, in which case they are triggered
|
||||||
|
in the same order as you added them.
|
||||||
|
|
||||||
|
You can explicitly control the priority of your middleware by passing an
|
||||||
|
additional argument to the registration methods::
|
||||||
|
|
||||||
|
$app->before(function (Request $request) {
|
||||||
|
// ...
|
||||||
|
}, 32);
|
||||||
|
|
||||||
|
As a convenience, two constants allow you to register an event as early as
|
||||||
|
possible or as late as possible::
|
||||||
|
|
||||||
|
$app->before(function (Request $request) {
|
||||||
|
// ...
|
||||||
|
}, Application::EARLY_EVENT);
|
||||||
|
|
||||||
|
$app->before(function (Request $request) {
|
||||||
|
// ...
|
||||||
|
}, Application::LATE_EVENT);
|
||||||
|
|
||||||
|
Short-circuiting the Controller
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
If a *before* middleware returns a ``Response`` object, the request handling is
|
||||||
|
short-circuited (the next middleware won't be run, nor the route
|
||||||
|
callback), and the Response is passed to the *after* middleware right away::
|
||||||
|
|
||||||
|
$app->before(function (Request $request) {
|
||||||
|
// redirect the user to the login screen if access to the Resource is protected
|
||||||
|
if (...) {
|
||||||
|
return new RedirectResponse('/login');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
A ``RuntimeException`` is thrown if a before middleware does not return a
|
||||||
|
Response or ``null``.
|
|
@ -0,0 +1,84 @@
|
||||||
|
Organizing Controllers
|
||||||
|
======================
|
||||||
|
|
||||||
|
When your application starts to define too many controllers, you might want to
|
||||||
|
group them logically::
|
||||||
|
|
||||||
|
// define controllers for a blog
|
||||||
|
$blog = $app['controllers_factory'];
|
||||||
|
$blog->get('/', function () {
|
||||||
|
return 'Blog home page';
|
||||||
|
});
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// define controllers for a forum
|
||||||
|
$forum = $app['controllers_factory'];
|
||||||
|
$forum->get('/', function () {
|
||||||
|
return 'Forum home page';
|
||||||
|
});
|
||||||
|
|
||||||
|
// define "global" controllers
|
||||||
|
$app->get('/', function () {
|
||||||
|
return 'Main home page';
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->mount('/blog', $blog);
|
||||||
|
$app->mount('/forum', $forum);
|
||||||
|
|
||||||
|
// define controllers for a admin
|
||||||
|
$app->mount('/admin', function ($admin) {
|
||||||
|
// recursively mount
|
||||||
|
$admin->mount('/blog', function ($user) {
|
||||||
|
$user->get('/', function () {
|
||||||
|
return 'Admin Blog home page';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
``$app['controllers_factory']`` is a factory that returns a new instance
|
||||||
|
of ``ControllerCollection`` when used.
|
||||||
|
|
||||||
|
``mount()`` prefixes all routes with the given prefix and merges them into the
|
||||||
|
main Application. So, ``/`` will map to the main home page, ``/blog/`` to the
|
||||||
|
blog home page, ``/forum/`` to the forum home page, and ``/admin/blog/`` to the
|
||||||
|
admin blog home page.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
When mounting a route collection under ``/blog``, it is not possible to
|
||||||
|
define a route for the ``/blog`` URL. The shortest possible URL is
|
||||||
|
``/blog/``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When calling ``get()``, ``match()``, or any other HTTP methods on the
|
||||||
|
Application, you are in fact calling them on a default instance of
|
||||||
|
``ControllerCollection`` (stored in ``$app['controllers']``).
|
||||||
|
|
||||||
|
Another benefit is the ability to apply settings on a set of controllers very
|
||||||
|
easily. Building on the example from the middleware section, here is how you
|
||||||
|
would secure all controllers for the backend collection::
|
||||||
|
|
||||||
|
$backend = $app['controllers_factory'];
|
||||||
|
|
||||||
|
// ensure that all controllers require logged-in users
|
||||||
|
$backend->before($mustBeLogged);
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
For a better readability, you can split each controller collection into a
|
||||||
|
separate file::
|
||||||
|
|
||||||
|
// blog.php
|
||||||
|
$blog = $app['controllers_factory'];
|
||||||
|
$blog->get('/', function () { return 'Blog home page'; });
|
||||||
|
|
||||||
|
return $blog;
|
||||||
|
|
||||||
|
// app.php
|
||||||
|
$app->mount('/blog', include 'blog.php');
|
||||||
|
|
||||||
|
Instead of requiring a file, you can also create a :ref:`Controller
|
||||||
|
provider <controller-providers>`.
|
|
@ -0,0 +1,256 @@
|
||||||
|
Providers
|
||||||
|
=========
|
||||||
|
|
||||||
|
Providers allow the developer to reuse parts of an application into another
|
||||||
|
one. Silex provides two types of providers defined by two interfaces:
|
||||||
|
``ServiceProviderInterface`` for services and ``ControllerProviderInterface``
|
||||||
|
for controllers.
|
||||||
|
|
||||||
|
Service Providers
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Loading providers
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In order to load and use a service provider, you must register it on the
|
||||||
|
application::
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
$app->register(new Acme\DatabaseServiceProvider());
|
||||||
|
|
||||||
|
You can also provide some parameters as a second argument. These will be set
|
||||||
|
**after** the provider is registered, but **before** it is booted::
|
||||||
|
|
||||||
|
$app->register(new Acme\DatabaseServiceProvider(), array(
|
||||||
|
'database.dsn' => 'mysql:host=localhost;dbname=myapp',
|
||||||
|
'database.user' => 'root',
|
||||||
|
'database.password' => 'secret_root_password',
|
||||||
|
));
|
||||||
|
|
||||||
|
Conventions
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
You need to watch out in what order you do certain things when interacting
|
||||||
|
with providers. Just keep these rules in mind:
|
||||||
|
|
||||||
|
* Overriding existing services must occur **after** the provider is
|
||||||
|
registered.
|
||||||
|
|
||||||
|
*Reason: If the service already exists, the provider will overwrite it.*
|
||||||
|
|
||||||
|
* You can set parameters any time **after** the provider is registered, but
|
||||||
|
**before** the service is accessed.
|
||||||
|
|
||||||
|
*Reason: Providers can set default values for parameters. Just like with
|
||||||
|
services, the provider will overwrite existing values.*
|
||||||
|
|
||||||
|
Included providers
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
There are a few providers that you get out of the box. All of these are within
|
||||||
|
the ``Silex\Provider`` namespace:
|
||||||
|
|
||||||
|
* :doc:`DoctrineServiceProvider <providers/doctrine>`
|
||||||
|
* :doc:`FormServiceProvider <providers/form>`
|
||||||
|
* :doc:`HttpCacheServiceProvider <providers/http_cache>`
|
||||||
|
* :doc:`MonologServiceProvider <providers/monolog>`
|
||||||
|
* :doc:`RememberMeServiceProvider <providers/remember_me>`
|
||||||
|
* :doc:`SecurityServiceProvider <providers/security>`
|
||||||
|
* :doc:`SerializerServiceProvider <providers/serializer>`
|
||||||
|
* :doc:`ServiceControllerServiceProvider <providers/service_controller>`
|
||||||
|
* :doc:`SessionServiceProvider <providers/session>`
|
||||||
|
* :doc:`SwiftmailerServiceProvider <providers/swiftmailer>`
|
||||||
|
* :doc:`TranslationServiceProvider <providers/translation>`
|
||||||
|
* :doc:`TwigServiceProvider <providers/twig>`
|
||||||
|
* :doc:`ValidatorServiceProvider <providers/validator>`
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The Silex core team maintains a `WebProfiler
|
||||||
|
<https://github.com/silexphp/Silex-WebProfiler>`_ provider that helps debug
|
||||||
|
code in the development environment thanks to the Symfony web debug toolbar
|
||||||
|
and the Symfony profiler.
|
||||||
|
|
||||||
|
Third party providers
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some service providers are developed by the community. Those third-party
|
||||||
|
providers are listed on `Silex' repository wiki
|
||||||
|
<https://github.com/silexphp/Silex/wiki/Third-Party-ServiceProviders>`_.
|
||||||
|
|
||||||
|
You are encouraged to share yours.
|
||||||
|
|
||||||
|
Creating a provider
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Providers must implement the ``Pimple\ServiceProviderInterface``::
|
||||||
|
|
||||||
|
interface ServiceProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $container);
|
||||||
|
}
|
||||||
|
|
||||||
|
This is very straight forward, just create a new class that implements the
|
||||||
|
register method. In the ``register()`` method, you can define services on the
|
||||||
|
application which then may make use of other services and parameters.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
The ``Pimple\ServiceProviderInterface`` belongs to the Pimple package, so
|
||||||
|
take care to only use the API of ``Pimple\Container`` within your
|
||||||
|
``register`` method. Not only is this a good practice due to the way Pimple
|
||||||
|
and Silex work, but may allow your provider to be used outside of Silex.
|
||||||
|
|
||||||
|
Optionally, your service provider can implement the
|
||||||
|
``Silex\Api\BootableProviderInterface``. A bootable provider must
|
||||||
|
implement the ``boot()`` method, with which you can configure the application, just
|
||||||
|
before it handles a request::
|
||||||
|
|
||||||
|
interface BootableProviderInterface
|
||||||
|
{
|
||||||
|
function boot(Application $app);
|
||||||
|
}
|
||||||
|
|
||||||
|
Another optional interface, is the ``Silex\Api\EventListenerProviderInterface``.
|
||||||
|
This interface contains the ``subscribe()`` method, which allows your provider to
|
||||||
|
subscribe event listener with Silex's EventDispatcher, just before it handles a
|
||||||
|
request::
|
||||||
|
|
||||||
|
interface EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
function subscribe(Container $app, EventDispatcherInterface $dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
Here is an example of such a provider::
|
||||||
|
|
||||||
|
namespace Acme;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Application;
|
||||||
|
use Silex\Api\BootableProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||||
|
|
||||||
|
class HelloServiceProvider implements ServiceProviderInterface, BootableProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['hello'] = $app->protect(function ($name) use ($app) {
|
||||||
|
$default = $app['hello.default_name'] ? $app['hello.default_name'] : '';
|
||||||
|
$name = $name ?: $default;
|
||||||
|
|
||||||
|
return 'Hello '.$app->escape($name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(Application $app)
|
||||||
|
{
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addListener(KernelEvents::REQUEST, function(FilterResponseEvent $event) use ($app) {
|
||||||
|
// do something
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
This class provides a ``hello`` service which is a protected closure. It takes
|
||||||
|
a ``name`` argument and will return ``hello.default_name`` if no name is
|
||||||
|
given. If the default is also missing, it will use an empty string.
|
||||||
|
|
||||||
|
You can now use this provider as follows::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
$app->register(new Acme\HelloServiceProvider(), array(
|
||||||
|
'hello.default_name' => 'Igor',
|
||||||
|
));
|
||||||
|
|
||||||
|
$app->get('/hello', function (Request $request) use ($app) {
|
||||||
|
$name = $request->get('name');
|
||||||
|
|
||||||
|
return $app['hello']($name);
|
||||||
|
});
|
||||||
|
|
||||||
|
In this example we are getting the ``name`` parameter from the query string,
|
||||||
|
so the request path would have to be ``/hello?name=Fabien``.
|
||||||
|
|
||||||
|
.. _controller-providers:
|
||||||
|
|
||||||
|
Controller Providers
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Loading providers
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In order to load and use a controller provider, you must "mount" its
|
||||||
|
controllers under a path::
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
$app->mount('/blog', new Acme\BlogControllerProvider());
|
||||||
|
|
||||||
|
All controllers defined by the provider will now be available under the
|
||||||
|
``/blog`` path.
|
||||||
|
|
||||||
|
Creating a provider
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Providers must implement the ``Silex\Api\ControllerProviderInterface``::
|
||||||
|
|
||||||
|
interface ControllerProviderInterface
|
||||||
|
{
|
||||||
|
public function connect(Application $app);
|
||||||
|
}
|
||||||
|
|
||||||
|
Here is an example of such a provider::
|
||||||
|
|
||||||
|
namespace Acme;
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
use Silex\Api\ControllerProviderInterface;
|
||||||
|
|
||||||
|
class HelloControllerProvider implements ControllerProviderInterface
|
||||||
|
{
|
||||||
|
public function connect(Application $app)
|
||||||
|
{
|
||||||
|
// creates a new controller based on the default route
|
||||||
|
$controllers = $app['controllers_factory'];
|
||||||
|
|
||||||
|
$controllers->get('/', function (Application $app) {
|
||||||
|
return $app->redirect('/hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
return $controllers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The ``connect`` method must return an instance of ``ControllerCollection``.
|
||||||
|
``ControllerCollection`` is the class where all controller related methods are
|
||||||
|
defined (like ``get``, ``post``, ``match``, ...).
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
The ``Application`` class acts in fact as a proxy for these methods.
|
||||||
|
|
||||||
|
You can use this provider as follows::
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
$app->mount('/blog', new Acme\HelloControllerProvider());
|
||||||
|
|
||||||
|
In this example, the ``/blog/`` path now references the controller defined in
|
||||||
|
the provider.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
You can also define a provider that implements both the service and the
|
||||||
|
controller provider interface and package in the same class the services
|
||||||
|
needed to make your controllers work.
|
|
@ -0,0 +1,60 @@
|
||||||
|
Asset
|
||||||
|
=====
|
||||||
|
|
||||||
|
The *AssetServiceProvider* provides a way to manage URL generation and
|
||||||
|
versioning of web assets such as CSS stylesheets, JavaScript files and image
|
||||||
|
files.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **assets.version**: Default version for assets.
|
||||||
|
|
||||||
|
* **assets.format_version** (optional): Default format for assets.
|
||||||
|
|
||||||
|
* **assets.named_packages** (optional): Named packages. Keys are the package
|
||||||
|
names and values the configuration (supported keys are ``version``,
|
||||||
|
``version_format``, ``base_urls``, and ``base_path``).
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **assets.packages**: The asset service.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\AssetServiceProvider(), array(
|
||||||
|
'assets.version' => 'v1',
|
||||||
|
'assets.version_format' => '%s?version=%s',
|
||||||
|
'assets.named_packages' => array(
|
||||||
|
'css' => array('version' => 'css2', 'base_path' => '/whatever-makes-sense'),
|
||||||
|
'images' => array('base_urls' => array('https://img.example.com')),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Symfony Asset Component as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/asset
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The AssetServiceProvider is mostly useful with the Twig provider:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{{ asset('/css/foo.png') }}
|
||||||
|
{{ asset('/css/foo.css', 'css') }}
|
||||||
|
{{ asset('/img/foo.png', 'images') }}
|
||||||
|
|
||||||
|
{{ asset_version('/css/foo.png') }}
|
||||||
|
|
||||||
|
For more information, check out the `Asset Component documentation
|
||||||
|
<https://symfony.com/doc/current/components/asset/introduction.html>`_.
|
|
@ -0,0 +1,52 @@
|
||||||
|
CSRF
|
||||||
|
====
|
||||||
|
|
||||||
|
The *CsrfServiceProvider* provides a service for building forms in your
|
||||||
|
application with the Symfony Form component.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* none
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **csrf.token_manager**: An instance of an implementation of the
|
||||||
|
`CsrfProviderInterface
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.html>`_,
|
||||||
|
defaults to a `DefaultCsrfProvider
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.html>`_.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
use Silex\Provider\CsrfServiceProvider;
|
||||||
|
|
||||||
|
$app->register(new CsrfServiceProvider());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Symfony's `Security CSRF Component
|
||||||
|
<http://symfony.com/doc/current/components/security/index.html>`_ as a
|
||||||
|
dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/security-csrf
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
When the CSRF Service Provider is registered, all forms created via the Form
|
||||||
|
Service Provider are protected against CSRF by default.
|
||||||
|
|
||||||
|
You can also use the CSRF protection even without using the Symfony Form
|
||||||
|
component. If, for example, you're doing a DELETE action, you can check the
|
||||||
|
CSRF token::
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||||
|
|
||||||
|
$app['csrf.token_manager']->isTokenValid(new CsrfToken('token_id', 'TOKEN'));
|
|
@ -0,0 +1,137 @@
|
||||||
|
Doctrine
|
||||||
|
========
|
||||||
|
|
||||||
|
The *DoctrineServiceProvider* provides integration with the `Doctrine DBAL
|
||||||
|
<http://www.doctrine-project.org/projects/dbal>`_ for easy database access
|
||||||
|
(Doctrine ORM integration is **not** supplied).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **db.options**: Array of Doctrine DBAL options.
|
||||||
|
|
||||||
|
These options are available:
|
||||||
|
|
||||||
|
* **driver**: The database driver to use, defaults to ``pdo_mysql``.
|
||||||
|
Can be any of: ``pdo_mysql``, ``pdo_sqlite``, ``pdo_pgsql``,
|
||||||
|
``pdo_oci``, ``oci8``, ``ibm_db2``, ``pdo_ibm``, ``pdo_sqlsrv``.
|
||||||
|
|
||||||
|
* **dbname**: The name of the database to connect to.
|
||||||
|
|
||||||
|
* **host**: The host of the database to connect to. Defaults to
|
||||||
|
localhost.
|
||||||
|
|
||||||
|
* **user**: The user of the database to connect to. Defaults to
|
||||||
|
root.
|
||||||
|
|
||||||
|
* **password**: The password of the database to connect to.
|
||||||
|
|
||||||
|
* **charset**: Only relevant for ``pdo_mysql``, and ``pdo_oci/oci8``,
|
||||||
|
specifies the charset used when connecting to the database.
|
||||||
|
|
||||||
|
* **path**: Only relevant for ``pdo_sqlite``, specifies the path to
|
||||||
|
the SQLite database.
|
||||||
|
|
||||||
|
* **port**: Only relevant for ``pdo_mysql``, ``pdo_pgsql``, and ``pdo_oci/oci8``,
|
||||||
|
specifies the port of the database to connect to.
|
||||||
|
|
||||||
|
These and additional options are described in detail in the `Doctrine DBAL
|
||||||
|
configuration documentation <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **db**: The database connection, instance of
|
||||||
|
``Doctrine\DBAL\Connection``.
|
||||||
|
|
||||||
|
* **db.config**: Configuration object for Doctrine. Defaults to
|
||||||
|
an empty ``Doctrine\DBAL\Configuration``.
|
||||||
|
|
||||||
|
* **db.event_manager**: Event Manager for Doctrine.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
|
||||||
|
'db.options' => array(
|
||||||
|
'driver' => 'pdo_sqlite',
|
||||||
|
'path' => __DIR__.'/app.db',
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Doctrine DBAL as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require "doctrine/dbal:~2.2"
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The Doctrine provider provides a ``db`` service. Here is a usage
|
||||||
|
example::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function ($id) use ($app) {
|
||||||
|
$sql = "SELECT * FROM posts WHERE id = ?";
|
||||||
|
$post = $app['db']->fetchAssoc($sql, array((int) $id));
|
||||||
|
|
||||||
|
return "<h1>{$post['title']}</h1>".
|
||||||
|
"<p>{$post['body']}</p>";
|
||||||
|
});
|
||||||
|
|
||||||
|
Using multiple databases
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The Doctrine provider can allow access to multiple databases. In order to
|
||||||
|
configure the data sources, replace the **db.options** with **dbs.options**.
|
||||||
|
**dbs.options** is an array of configurations where keys are connection names
|
||||||
|
and values are options::
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
|
||||||
|
'dbs.options' => array (
|
||||||
|
'mysql_read' => array(
|
||||||
|
'driver' => 'pdo_mysql',
|
||||||
|
'host' => 'mysql_read.someplace.tld',
|
||||||
|
'dbname' => 'my_database',
|
||||||
|
'user' => 'my_username',
|
||||||
|
'password' => 'my_password',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
),
|
||||||
|
'mysql_write' => array(
|
||||||
|
'driver' => 'pdo_mysql',
|
||||||
|
'host' => 'mysql_write.someplace.tld',
|
||||||
|
'dbname' => 'my_database',
|
||||||
|
'user' => 'my_username',
|
||||||
|
'password' => 'my_password',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
The first registered connection is the default and can simply be accessed as
|
||||||
|
you would if there was only one connection. Given the above configuration,
|
||||||
|
these two lines are equivalent::
|
||||||
|
|
||||||
|
$app['db']->fetchAll('SELECT * FROM table');
|
||||||
|
|
||||||
|
$app['dbs']['mysql_read']->fetchAll('SELECT * FROM table');
|
||||||
|
|
||||||
|
Using multiple connections::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function ($id) use ($app) {
|
||||||
|
$sql = "SELECT * FROM posts WHERE id = ?";
|
||||||
|
$post = $app['dbs']['mysql_read']->fetchAssoc($sql, array((int) $id));
|
||||||
|
|
||||||
|
$sql = "UPDATE posts SET value = ? WHERE id = ?";
|
||||||
|
$app['dbs']['mysql_write']->executeUpdate($sql, array('newValue', (int) $id));
|
||||||
|
|
||||||
|
return "<h1>{$post['title']}</h1>".
|
||||||
|
"<p>{$post['body']}</p>";
|
||||||
|
});
|
||||||
|
|
||||||
|
For more information, consult the `Doctrine DBAL documentation
|
||||||
|
<http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
|
|
@ -0,0 +1,204 @@
|
||||||
|
Form
|
||||||
|
====
|
||||||
|
|
||||||
|
The *FormServiceProvider* provides a service for building forms in
|
||||||
|
your application with the Symfony Form component.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* none
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **form.factory**: An instance of `FormFactory
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Form/FormFactory.html>`_,
|
||||||
|
that is used to build a form.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
use Silex\Provider\FormServiceProvider;
|
||||||
|
|
||||||
|
$app->register(new FormServiceProvider());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If you don't want to create your own form layout, it's fine: a default one
|
||||||
|
will be used. But you will have to register the :doc:`translation provider
|
||||||
|
<translation>` as the default form layout requires it::
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\TranslationServiceProvider(), array(
|
||||||
|
'translator.domains' => array(),
|
||||||
|
));
|
||||||
|
|
||||||
|
If you want to use validation with forms, do not forget to register the
|
||||||
|
:doc:`Validator provider <validator>`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Symfony Form Component as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/form
|
||||||
|
|
||||||
|
If you are going to use the validation extension with forms, you must also
|
||||||
|
add a dependency to the ``symfony/config`` and ``symfony/translation``
|
||||||
|
components:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/validator symfony/config symfony/translation
|
||||||
|
|
||||||
|
If you want to use forms in your Twig templates, you can also install the
|
||||||
|
Symfony Twig Bridge. Make sure to install, if you didn't do that already,
|
||||||
|
the Translation component in order for the bridge to work:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/twig-bridge symfony/translation
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The FormServiceProvider provides a ``form.factory`` service. Here is a usage
|
||||||
|
example::
|
||||||
|
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
|
||||||
|
$app->match('/form', function (Request $request) use ($app) {
|
||||||
|
// some default data for when the form is displayed the first time
|
||||||
|
$data = array(
|
||||||
|
'name' => 'Your name',
|
||||||
|
'email' => 'Your email',
|
||||||
|
);
|
||||||
|
|
||||||
|
$form = $app['form.factory']->createBuilder(FormType::class, $data)
|
||||||
|
->add('name')
|
||||||
|
->add('email')
|
||||||
|
->add('billing_plan', ChoiceType::class, array(
|
||||||
|
'choices' => array(1 => 'free', 2 => 'small_business', 3 => 'corporate'),
|
||||||
|
'expanded' => true,
|
||||||
|
))
|
||||||
|
->getForm();
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$data = $form->getData();
|
||||||
|
|
||||||
|
// do something with the data
|
||||||
|
|
||||||
|
// redirect somewhere
|
||||||
|
return $app->redirect('...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// display the form
|
||||||
|
return $app['twig']->render('index.twig', array('form' => $form->createView()));
|
||||||
|
});
|
||||||
|
|
||||||
|
And here is the ``index.twig`` form template (requires ``symfony/twig-bridge``):
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
<form action="#" method="post">
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
|
||||||
|
<input type="submit" name="submit" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
If you are using the validator provider, you can also add validation to your
|
||||||
|
form by adding constraints on the fields::
|
||||||
|
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\ValidatorServiceProvider());
|
||||||
|
$app->register(new Silex\Provider\TranslationServiceProvider(), array(
|
||||||
|
'translator.domains' => array(),
|
||||||
|
));
|
||||||
|
|
||||||
|
$form = $app['form.factory']->createBuilder(FormType::class)
|
||||||
|
->add('name', TextType::class, array(
|
||||||
|
'constraints' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 5)))
|
||||||
|
))
|
||||||
|
->add('email', TextType::class, array(
|
||||||
|
'constraints' => new Assert\Email()
|
||||||
|
))
|
||||||
|
->add('billing_plan', ChoiceType::class, array(
|
||||||
|
'choices' => array(1 => 'free', 2 => 'small_business', 3 => 'corporate'),
|
||||||
|
'expanded' => true,
|
||||||
|
'constraints' => new Assert\Choice(array(1, 2, 3)),
|
||||||
|
))
|
||||||
|
->getForm();
|
||||||
|
|
||||||
|
You can register form types by extending ``form.types``::
|
||||||
|
|
||||||
|
$app['your.type.service'] = function ($app) {
|
||||||
|
return new YourServiceFormType();
|
||||||
|
};
|
||||||
|
$app->extend('form.types', function ($types) use ($app) {
|
||||||
|
$types[] = new YourFormType();
|
||||||
|
$types[] = 'your.type.service';
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
}));
|
||||||
|
|
||||||
|
You can register form extensions by extending ``form.extensions``::
|
||||||
|
|
||||||
|
$app->extend('form.extensions', function ($extensions) use ($app) {
|
||||||
|
$extensions[] = new YourTopFormExtension();
|
||||||
|
|
||||||
|
return $extensions;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
You can register form type extensions by extending ``form.type.extensions``::
|
||||||
|
|
||||||
|
$app['your.type.extension.service'] = function ($app) {
|
||||||
|
return new YourServiceFormTypeExtension();
|
||||||
|
};
|
||||||
|
$app->extend('form.type.extensions', function ($extensions) use ($app) {
|
||||||
|
$extensions[] = new YourFormTypeExtension();
|
||||||
|
$extensions[] = 'your.type.extension.service';
|
||||||
|
|
||||||
|
return $extensions;
|
||||||
|
});
|
||||||
|
|
||||||
|
You can register form type guessers by extending ``form.type.guessers``::
|
||||||
|
|
||||||
|
$app['your.type.guesser.service'] = function ($app) {
|
||||||
|
return new YourServiceFormTypeGuesser();
|
||||||
|
};
|
||||||
|
$app->extend('form.type.guessers', function ($guessers) use ($app) {
|
||||||
|
$guessers[] = new YourFormTypeGuesser();
|
||||||
|
$guessers[] = 'your.type.guesser.service';
|
||||||
|
|
||||||
|
return $guessers;
|
||||||
|
});
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
CSRF protection is only available and automatically enabled when the
|
||||||
|
:doc:`CSRF Service Provider </providers/csrf>` is registered.
|
||||||
|
|
||||||
|
Traits
|
||||||
|
------
|
||||||
|
|
||||||
|
``Silex\Application\FormTrait`` adds the following shortcuts:
|
||||||
|
|
||||||
|
* **form**: Creates a FormBuilder instance.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->form($data);
|
||||||
|
|
||||||
|
For more information, consult the `Symfony Forms documentation
|
||||||
|
<http://symfony.com/doc/2.8/book/forms.html>`_.
|
|
@ -0,0 +1,128 @@
|
||||||
|
HTTP Cache
|
||||||
|
==========
|
||||||
|
|
||||||
|
The *HttpCacheServiceProvider* provides support for the Symfony Reverse
|
||||||
|
Proxy.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **http_cache.cache_dir**: The cache directory to store the HTTP cache data.
|
||||||
|
|
||||||
|
* **http_cache.options** (optional): An array of options for the `HttpCache
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpCache/HttpCache.html>`_
|
||||||
|
constructor.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **http_cache**: An instance of `HttpCache
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpCache/HttpCache.html>`_.
|
||||||
|
|
||||||
|
* **http_cache.esi**: An instance of `Esi
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpCache/Esi.html>`_,
|
||||||
|
that implements the ESI capabilities to Request and Response instances.
|
||||||
|
|
||||||
|
* **http_cache.store**: An instance of `Store
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpCache/Store.html>`_,
|
||||||
|
that implements all the logic for storing cache metadata (Request and Response
|
||||||
|
headers).
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\HttpCacheServiceProvider(), array(
|
||||||
|
'http_cache.cache_dir' => __DIR__.'/cache/',
|
||||||
|
));
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
Silex already supports any reverse proxy like Varnish out of the box by
|
||||||
|
setting Response HTTP cache headers::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
$app->get('/', function() {
|
||||||
|
return new Response('Foo', 200, array(
|
||||||
|
'Cache-Control' => 's-maxage=5',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
If you want Silex to trust the ``X-Forwarded-For*`` headers from your
|
||||||
|
reverse proxy at address $ip, you will need to whitelist it as documented
|
||||||
|
in `Trusting Proxies
|
||||||
|
<http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html>`_.
|
||||||
|
|
||||||
|
If you would be running Varnish in front of your application on the same machine::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
Request::setTrustedProxies(array('127.0.0.1', '::1'));
|
||||||
|
$app->run();
|
||||||
|
|
||||||
|
This provider allows you to use the Symfony reverse proxy natively with
|
||||||
|
Silex applications by using the ``http_cache`` service. The Symfony reverse proxy
|
||||||
|
acts much like any other proxy would, so you will want to whitelist it::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
Request::setTrustedProxies(array('127.0.0.1'));
|
||||||
|
$app['http_cache']->run();
|
||||||
|
|
||||||
|
The provider also provides ESI support::
|
||||||
|
|
||||||
|
$app->get('/', function() {
|
||||||
|
$response = new Response(<<<EOF
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
Hello
|
||||||
|
<esi:include src="/included" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
EOF
|
||||||
|
, 200, array(
|
||||||
|
'Surrogate-Control' => 'content="ESI/1.0"',
|
||||||
|
));
|
||||||
|
|
||||||
|
$response->setTtl(20);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->get('/included', function() {
|
||||||
|
$response = new Response('Foo');
|
||||||
|
$response->setTtl(5);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['http_cache']->run();
|
||||||
|
|
||||||
|
If your application doesn't use ESI, you can disable it to slightly improve the
|
||||||
|
overall performance::
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\HttpCacheServiceProvider(), array(
|
||||||
|
'http_cache.cache_dir' => __DIR__.'/cache/',
|
||||||
|
'http_cache.esi' => null,
|
||||||
|
));
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
To help you debug caching issues, set your application ``debug`` to true.
|
||||||
|
Symfony automatically adds a ``X-Symfony-Cache`` header to each response
|
||||||
|
with useful information about cache hits and misses.
|
||||||
|
|
||||||
|
If you are *not* using the Symfony Session provider, you might want to set
|
||||||
|
the PHP ``session.cache_limiter`` setting to an empty value to avoid the
|
||||||
|
default PHP behavior.
|
||||||
|
|
||||||
|
Finally, check that your Web server does not override your caching strategy.
|
||||||
|
|
||||||
|
For more information, consult the `Symfony HTTP Cache documentation
|
||||||
|
<http://symfony.com/doc/current/book/http_cache.html>`_.
|
|
@ -0,0 +1,70 @@
|
||||||
|
HTTP Fragment
|
||||||
|
=============
|
||||||
|
|
||||||
|
The *HttpFragmentServiceProvider* provides support for the Symfony fragment
|
||||||
|
sub-framework, which allows you to embed fragments of HTML in a template.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **fragment.path**: The path to use for the URL generated for ESI and
|
||||||
|
HInclude URLs (``/_fragment`` by default).
|
||||||
|
|
||||||
|
* **uri_signer.secret**: The secret to use for the URI signer service (used
|
||||||
|
for the HInclude renderer).
|
||||||
|
|
||||||
|
* **fragment.renderers.hinclude.global_template**: The content or Twig
|
||||||
|
template to use for the default content when using the HInclude renderer.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **fragment.handler**: An instance of `FragmentHandler
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/Fragment/FragmentHandler.html>`_.
|
||||||
|
|
||||||
|
* **fragment.renderers**: An array of fragment renderers (by default, the
|
||||||
|
inline, ESI, and HInclude renderers are pre-configured).
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\HttpFragmentServiceProvider());
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This section assumes that you are using Twig for your templates.
|
||||||
|
|
||||||
|
Instead of building a page out of a single request/controller/template, the
|
||||||
|
fragment framework allows you to build a page from several
|
||||||
|
controllers/sub-requests/sub-templates by using **fragments**.
|
||||||
|
|
||||||
|
Including "sub-pages" in the main page can be done with the Twig ``render()``
|
||||||
|
function:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
The main page content.
|
||||||
|
|
||||||
|
{{ render('/foo') }}
|
||||||
|
|
||||||
|
The main page content resumes here.
|
||||||
|
|
||||||
|
The ``render()`` call is replaced by the content of the ``/foo`` URL
|
||||||
|
(internally, a sub-request is handled by Silex to render the sub-page).
|
||||||
|
|
||||||
|
Instead of making internal sub-requests, you can also use the ESI (the
|
||||||
|
sub-request is handled by a reverse proxy) or the HInclude strategies (the
|
||||||
|
sub-request is handled by a web browser):
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{{ render(url('route_name')) }}
|
||||||
|
|
||||||
|
{{ render_esi(url('route_name')) }}
|
||||||
|
|
||||||
|
{{ render_hinclude(url('route_name')) }}
|
|
@ -0,0 +1,24 @@
|
||||||
|
Built-in Service Providers
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
twig
|
||||||
|
asset
|
||||||
|
monolog
|
||||||
|
session
|
||||||
|
swiftmailer
|
||||||
|
locale
|
||||||
|
translation
|
||||||
|
validator
|
||||||
|
form
|
||||||
|
csrf
|
||||||
|
http_cache
|
||||||
|
http_fragment
|
||||||
|
security
|
||||||
|
remember_me
|
||||||
|
serializer
|
||||||
|
service_controller
|
||||||
|
var_dumper
|
||||||
|
doctrine
|
|
@ -0,0 +1,24 @@
|
||||||
|
Locale
|
||||||
|
======
|
||||||
|
|
||||||
|
The *LocaleServiceProvider* manages the locale of an application.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **locale**: The locale of the user. When set before any request handling, it
|
||||||
|
defines the default locale (``en`` by default). When a request is being
|
||||||
|
handled, it is automatically set according to the ``_locale`` request
|
||||||
|
attribute of the current route.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* n/a
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\LocaleServiceProvider());
|
|
@ -0,0 +1,115 @@
|
||||||
|
Monolog
|
||||||
|
=======
|
||||||
|
|
||||||
|
The *MonologServiceProvider* provides a default logging mechanism through
|
||||||
|
Jordi Boggiano's `Monolog <https://github.com/Seldaek/monolog>`_ library.
|
||||||
|
|
||||||
|
It will log requests and errors and allow you to add logging to your
|
||||||
|
application. This allows you to debug and monitor the behaviour,
|
||||||
|
even in production.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **monolog.logfile**: File where logs are written to.
|
||||||
|
* **monolog.bubble**: (optional) Whether the messages that are handled can bubble up the stack or not.
|
||||||
|
* **monolog.permission**: (optional) File permissions default (null), nothing change.
|
||||||
|
|
||||||
|
* **monolog.level** (optional): Level of logging, defaults
|
||||||
|
to ``DEBUG``. Must be one of ``Logger::DEBUG``, ``Logger::INFO``,
|
||||||
|
``Logger::WARNING``, ``Logger::ERROR``. ``DEBUG`` will log
|
||||||
|
everything, ``INFO`` will log everything except ``DEBUG``,
|
||||||
|
etc.
|
||||||
|
|
||||||
|
In addition to the ``Logger::`` constants, it is also possible to supply the
|
||||||
|
level in string form, for example: ``"DEBUG"``, ``"INFO"``, ``"WARNING"``,
|
||||||
|
``"ERROR"``.
|
||||||
|
|
||||||
|
* **monolog.name** (optional): Name of the monolog channel,
|
||||||
|
defaults to ``myapp``.
|
||||||
|
|
||||||
|
* **monolog.exception.logger_filter** (optional): An anonymous function that
|
||||||
|
filters which exceptions should be logged.
|
||||||
|
|
||||||
|
* **monolog.use_error_handler** (optional): Whether errors and uncaught exceptions
|
||||||
|
should be handled by the Monolog ``ErrorHandler`` class and added to the log.
|
||||||
|
By default the error handler is enabled unless the application ``debug`` parameter
|
||||||
|
is set to true.
|
||||||
|
|
||||||
|
Please note that enabling the error handler may silence some errors,
|
||||||
|
ignoring the PHP ``display_errors`` configuration setting.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **monolog**: The monolog logger instance.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
$app['monolog']->addDebug('Testing the Monolog logging.');
|
||||||
|
|
||||||
|
* **monolog.listener**: An event listener to log requests, responses and errors.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\MonologServiceProvider(), array(
|
||||||
|
'monolog.logfile' => __DIR__.'/development.log',
|
||||||
|
));
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add Monolog as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require monolog/monolog
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The MonologServiceProvider provides a ``monolog`` service. You can use it to
|
||||||
|
add log entries for any logging level through ``addDebug()``, ``addInfo()``,
|
||||||
|
``addWarning()`` and ``addError()``::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
$app->post('/user', function () use ($app) {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
$app['monolog']->addInfo(sprintf("User '%s' registered.", $username));
|
||||||
|
|
||||||
|
return new Response('', 201);
|
||||||
|
});
|
||||||
|
|
||||||
|
Customization
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You can configure Monolog (like adding or changing the handlers) before using
|
||||||
|
it by extending the ``monolog`` service::
|
||||||
|
|
||||||
|
$app->extend('monolog', function($monolog, $app) {
|
||||||
|
$monolog->pushHandler(...);
|
||||||
|
|
||||||
|
return $monolog;
|
||||||
|
});
|
||||||
|
|
||||||
|
By default, all requests, responses and errors are logged by an event listener
|
||||||
|
registered as a service called `monolog.listener`. You can replace or remove
|
||||||
|
this service if you want to modify or disable the logged information.
|
||||||
|
|
||||||
|
Traits
|
||||||
|
------
|
||||||
|
|
||||||
|
``Silex\Application\MonologTrait`` adds the following shortcuts:
|
||||||
|
|
||||||
|
* **log**: Logs a message.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->log(sprintf("User '%s' registered.", $username));
|
||||||
|
|
||||||
|
For more information, check out the `Monolog documentation
|
||||||
|
<https://github.com/Seldaek/monolog>`_.
|
|
@ -0,0 +1,69 @@
|
||||||
|
Remember Me
|
||||||
|
===========
|
||||||
|
|
||||||
|
The *RememberMeServiceProvider* adds "Remember-Me" authentication to the
|
||||||
|
*SecurityServiceProvider*.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
n/a
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
n/a
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The service provider defines many other services that are used internally
|
||||||
|
but rarely need to be customized.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Before registering this service provider, you must register the
|
||||||
|
*SecurityServiceProvider*::
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\SecurityServiceProvider());
|
||||||
|
$app->register(new Silex\Provider\RememberMeServiceProvider());
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'my-firewall' => array(
|
||||||
|
'pattern' => '^/secure$',
|
||||||
|
'form' => true,
|
||||||
|
'logout' => true,
|
||||||
|
'remember_me' => array(
|
||||||
|
'key' => 'Choose_A_Unique_Random_Key',
|
||||||
|
'always_remember_me' => true,
|
||||||
|
/* Other options */
|
||||||
|
),
|
||||||
|
'users' => array( /* ... */ ),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Options
|
||||||
|
-------
|
||||||
|
|
||||||
|
* **key**: A secret key to generate tokens (you should generate a random
|
||||||
|
string).
|
||||||
|
|
||||||
|
* **name**: Cookie name (default: ``REMEMBERME``).
|
||||||
|
|
||||||
|
* **lifetime**: Cookie lifetime (default: ``31536000`` ~ 1 year).
|
||||||
|
|
||||||
|
* **path**: Cookie path (default: ``/``).
|
||||||
|
|
||||||
|
* **domain**: Cookie domain (default: ``null`` = request domain).
|
||||||
|
|
||||||
|
* **secure**: Cookie is secure (default: ``false``).
|
||||||
|
|
||||||
|
* **httponly**: Cookie is HTTP only (default: ``true``).
|
||||||
|
|
||||||
|
* **always_remember_me**: Enable remember me (default: ``false``).
|
||||||
|
|
||||||
|
* **remember_me_parameter**: Name of the request parameter enabling remember_me
|
||||||
|
on login. To add the checkbox to the login form. You can find more
|
||||||
|
information in the `Symfony cookbook
|
||||||
|
<http://symfony.com/doc/current/cookbook/security/remember_me.html>`_
|
||||||
|
(default: ``_remember_me``).
|
|
@ -0,0 +1,711 @@
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
The *SecurityServiceProvider* manages authentication and authorization for
|
||||||
|
your applications.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **security.hide_user_not_found** (optional): Defines whether to hide user not
|
||||||
|
found exception or not. Defaults to ``true``.
|
||||||
|
|
||||||
|
* **security.encoder.bcrypt.cost** (optional): Defines BCrypt password encoder cost. Defaults to 13.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **security.token_storage**: Gives access to the user token.
|
||||||
|
|
||||||
|
* **security.authorization_checker**: Allows to check authorizations for the
|
||||||
|
users.
|
||||||
|
|
||||||
|
* **security.authentication_manager**: An instance of
|
||||||
|
`AuthenticationProviderManager
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.html>`_,
|
||||||
|
responsible for authentication.
|
||||||
|
|
||||||
|
* **security.access_manager**: An instance of `AccessDecisionManager
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.html>`_,
|
||||||
|
responsible for authorization.
|
||||||
|
|
||||||
|
* **security.session_strategy**: Define the session strategy used for
|
||||||
|
authentication (default to a migration strategy).
|
||||||
|
|
||||||
|
* **security.user_checker**: Checks user flags after authentication.
|
||||||
|
|
||||||
|
* **security.last_error**: Returns the last authentication errors when given a
|
||||||
|
Request object.
|
||||||
|
|
||||||
|
* **security.encoder_factory**: Defines the encoding strategies for user
|
||||||
|
passwords (uses ``security.default_encoder``).
|
||||||
|
|
||||||
|
* **security.default_encoder**: The encoder to use by default for all users (BCrypt).
|
||||||
|
|
||||||
|
* **security.encoder.digest**: Digest password encoder.
|
||||||
|
|
||||||
|
* **security.encoder.bcrypt**: BCrypt password encoder.
|
||||||
|
|
||||||
|
* **security.encoder.pbkdf2**: Pbkdf2 password encoder.
|
||||||
|
|
||||||
|
* **user**: Returns the current user
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The service provider defines many other services that are used internally
|
||||||
|
but rarely need to be customized.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
|
||||||
|
'security.firewalls' => // see below
|
||||||
|
));
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Symfony Security Component as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/security
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
If you're using a form to authenticate users, you need to enable
|
||||||
|
``SessionServiceProvider``.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
The security features are only available after the Application has been
|
||||||
|
booted. So, if you want to use it outside of the handling of a request,
|
||||||
|
don't forget to call ``boot()`` first::
|
||||||
|
|
||||||
|
$app->boot();
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The Symfony Security component is powerful. To learn more about it, read the
|
||||||
|
`Symfony Security documentation
|
||||||
|
<http://symfony.com/doc/2.8/book/security.html>`_.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
When a security configuration does not behave as expected, enable logging
|
||||||
|
(with the Monolog extension for instance) as the Security Component logs a
|
||||||
|
lot of interesting information about what it does and why.
|
||||||
|
|
||||||
|
Below is a list of recipes that cover some common use cases.
|
||||||
|
|
||||||
|
Accessing the current User
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The current user information is stored in a token that is accessible via the
|
||||||
|
``security`` service::
|
||||||
|
|
||||||
|
$token = $app['security.token_storage']->getToken();
|
||||||
|
|
||||||
|
If there is no information about the user, the token is ``null``. If the user
|
||||||
|
is known, you can get it with a call to ``getUser()``::
|
||||||
|
|
||||||
|
if (null !== $token) {
|
||||||
|
$user = $token->getUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
The user can be a string, an object with a ``__toString()`` method, or an
|
||||||
|
instance of `UserInterface
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Security/Core/User/UserInterface.html>`_.
|
||||||
|
|
||||||
|
Securing a Path with HTTP Authentication
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The following configuration uses HTTP basic authentication to secure URLs
|
||||||
|
under ``/admin/``::
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'admin' => array(
|
||||||
|
'pattern' => '^/admin',
|
||||||
|
'http' => true,
|
||||||
|
'users' => array(
|
||||||
|
// raw password is foo
|
||||||
|
'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
The ``pattern`` is a regular expression on the URL path; the ``http`` setting
|
||||||
|
tells the security layer to use HTTP basic authentication and the ``users``
|
||||||
|
entry defines valid users.
|
||||||
|
|
||||||
|
If you want to restrict the firewall by more than the URL pattern (like the
|
||||||
|
HTTP method, the client IP, the hostname, or any Request attributes), use an
|
||||||
|
instance of a `RequestMatcher
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/RequestMatcher.html>`_
|
||||||
|
for the ``pattern`` option::
|
||||||
|
|
||||||
|
use Symfony/Component/HttpFoundation/RequestMatcher;
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'admin' => array(
|
||||||
|
'pattern' => new RequestMatcher('^/admin', 'example.com', 'POST'),
|
||||||
|
// ...
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Each user is defined with the following information:
|
||||||
|
|
||||||
|
* The role or an array of roles for the user (roles are strings beginning with
|
||||||
|
``ROLE_`` and ending with anything you want);
|
||||||
|
|
||||||
|
* The user encoded password.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
All users must at least have one role associated with them.
|
||||||
|
|
||||||
|
The default configuration of the extension enforces encoded passwords. To
|
||||||
|
generate a valid encoded password from a raw password, use the
|
||||||
|
``security.encoder_factory`` service::
|
||||||
|
|
||||||
|
// find the encoder for a UserInterface instance
|
||||||
|
$encoder = $app['security.encoder_factory']->getEncoder($user);
|
||||||
|
|
||||||
|
// compute the encoded password for foo
|
||||||
|
$password = $encoder->encodePassword('foo', $user->getSalt());
|
||||||
|
|
||||||
|
When the user is authenticated, the user stored in the token is an instance of
|
||||||
|
`User
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Security/Core/User/User.html>`_
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
If you are using php-cgi under Apache, you need to add this configuration
|
||||||
|
to make things work correctly:
|
||||||
|
|
||||||
|
.. code-block:: apache
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{HTTP:Authorization} ^(.+)$
|
||||||
|
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^(.*)$ app.php [QSA,L]
|
||||||
|
|
||||||
|
Securing a Path with a Form
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Using a form to authenticate users is very similar to the above configuration.
|
||||||
|
Instead of using the ``http`` setting, use the ``form`` one and define these
|
||||||
|
two parameters:
|
||||||
|
|
||||||
|
* **login_path**: The login path where the user is redirected when they are
|
||||||
|
accessing a secured area without being authenticated so that they can enter
|
||||||
|
their credentials;
|
||||||
|
|
||||||
|
* **check_path**: The check URL used by Symfony to validate the credentials of
|
||||||
|
the user.
|
||||||
|
|
||||||
|
Here is how to secure all URLs under ``/admin/`` with a form::
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'admin' => array(
|
||||||
|
'pattern' => '^/admin/',
|
||||||
|
'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'),
|
||||||
|
'users' => array(
|
||||||
|
'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Always keep in mind the following two golden rules:
|
||||||
|
|
||||||
|
* The ``login_path`` path must always be defined **outside** the secured area
|
||||||
|
(or if it is in the secured area, the ``anonymous`` authentication mechanism
|
||||||
|
must be enabled -- see below);
|
||||||
|
|
||||||
|
* The ``check_path`` path must always be defined **inside** the secured area.
|
||||||
|
|
||||||
|
For the login form to work, create a controller like the following::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app->get('/login', function(Request $request) use ($app) {
|
||||||
|
return $app['twig']->render('login.html', array(
|
||||||
|
'error' => $app['security.last_error']($request),
|
||||||
|
'last_username' => $app['session']->get('_security.last_username'),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
The ``error`` and ``last_username`` variables contain the last authentication
|
||||||
|
error and the last username entered by the user in case of an authentication
|
||||||
|
error.
|
||||||
|
|
||||||
|
Create the associated template:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
<form action="{{ path('admin_login_check') }}" method="post">
|
||||||
|
{{ error }}
|
||||||
|
<input type="text" name="_username" value="{{ last_username }}" />
|
||||||
|
<input type="password" name="_password" value="" />
|
||||||
|
<input type="submit" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``admin_login_check`` route is automatically defined by Silex and its
|
||||||
|
name is derived from the ``check_path`` value (all ``/`` are replaced with
|
||||||
|
``_`` and the leading ``/`` is stripped).
|
||||||
|
|
||||||
|
Defining more than one Firewall
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You are not limited to define one firewall per project.
|
||||||
|
|
||||||
|
Configuring several firewalls is useful when you want to secure different
|
||||||
|
parts of your website with different authentication strategies or for
|
||||||
|
different users (like using an HTTP basic authentication for the website API
|
||||||
|
and a form to secure your website administration area).
|
||||||
|
|
||||||
|
It's also useful when you want to secure all URLs except the login form::
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'login' => array(
|
||||||
|
'pattern' => '^/login$',
|
||||||
|
),
|
||||||
|
'secured' => array(
|
||||||
|
'pattern' => '^.*$',
|
||||||
|
'form' => array('login_path' => '/login', 'check_path' => '/login_check'),
|
||||||
|
'users' => array(
|
||||||
|
'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
The order of the firewall configurations is significant as the first one to
|
||||||
|
match wins. The above configuration first ensures that the ``/login`` URL is
|
||||||
|
not secured (no authentication settings), and then it secures all other URLs.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
You can toggle all registered authentication mechanisms for a particular
|
||||||
|
area on and off with the ``security`` flag::
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'api' => array(
|
||||||
|
'pattern' => '^/api',
|
||||||
|
'security' => $app['debug'] ? false : true,
|
||||||
|
'wsse' => true,
|
||||||
|
|
||||||
|
// ...
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Adding a Logout
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When using a form for authentication, you can let users log out if you add the
|
||||||
|
``logout`` setting, where ``logout_path`` must match the main firewall
|
||||||
|
pattern::
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'secured' => array(
|
||||||
|
'pattern' => '^/admin/',
|
||||||
|
'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'),
|
||||||
|
'logout' => array('logout_path' => '/admin/logout', 'invalidate_session' => true),
|
||||||
|
|
||||||
|
// ...
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
A route is automatically generated, based on the configured path (all ``/``
|
||||||
|
are replaced with ``_`` and the leading ``/`` is stripped):
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
<a href="{{ path('admin_logout') }}">Logout</a>
|
||||||
|
|
||||||
|
Allowing Anonymous Users
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When securing only some parts of your website, the user information are not
|
||||||
|
available in non-secured areas. To make the user accessible in such areas,
|
||||||
|
enabled the ``anonymous`` authentication mechanism::
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'unsecured' => array(
|
||||||
|
'anonymous' => true,
|
||||||
|
|
||||||
|
// ...
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
When enabling the anonymous setting, a user will always be accessible from the
|
||||||
|
security context; if the user is not authenticated, it returns the ``anon.``
|
||||||
|
string.
|
||||||
|
|
||||||
|
Checking User Roles
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To check if a user is granted some role, use the ``isGranted()`` method on the
|
||||||
|
security context::
|
||||||
|
|
||||||
|
if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
You can check roles in Twig templates too:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% if is_granted('ROLE_ADMIN') %}
|
||||||
|
<a href="/secured?_switch_user=fabien">Switch to Fabien</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
You can check if a user is "fully authenticated" (not an anonymous user for
|
||||||
|
instance) with the special ``IS_AUTHENTICATED_FULLY`` role:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
|
||||||
|
<a href="{{ path('logout') }}">Logout</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ path('login') }}">Login</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
Of course you will need to define a ``login`` route for this to work.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Don't use the ``getRoles()`` method to check user roles.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
``isGranted()`` throws an exception when no authentication information is
|
||||||
|
available (which is the case on non-secured area).
|
||||||
|
|
||||||
|
Impersonating a User
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you want to be able to switch to another user (without knowing the user
|
||||||
|
credentials), enable the ``switch_user`` authentication strategy::
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'unsecured' => array(
|
||||||
|
'switch_user' => array('parameter' => '_switch_user', 'role' => 'ROLE_ALLOWED_TO_SWITCH'),
|
||||||
|
|
||||||
|
// ...
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Switching to another user is now a matter of adding the ``_switch_user`` query
|
||||||
|
parameter to any URL when logged in as a user who has the
|
||||||
|
``ROLE_ALLOWED_TO_SWITCH`` role:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% if is_granted('ROLE_ALLOWED_TO_SWITCH') %}
|
||||||
|
<a href="?_switch_user=fabien">Switch to user Fabien</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
You can check that you are impersonating a user by checking the special
|
||||||
|
``ROLE_PREVIOUS_ADMIN``. This is useful for instance to allow the user to
|
||||||
|
switch back to their primary account:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
|
||||||
|
You are an admin but you've switched to another user,
|
||||||
|
<a href="?_switch_user=_exit"> exit</a> the switch.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
Defining a Role Hierarchy
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Defining a role hierarchy allows to automatically grant users some additional
|
||||||
|
roles::
|
||||||
|
|
||||||
|
$app['security.role_hierarchy'] = array(
|
||||||
|
'ROLE_ADMIN' => array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'),
|
||||||
|
);
|
||||||
|
|
||||||
|
With this configuration, all users with the ``ROLE_ADMIN`` role also
|
||||||
|
automatically have the ``ROLE_USER`` and ``ROLE_ALLOWED_TO_SWITCH`` roles.
|
||||||
|
|
||||||
|
Defining Access Rules
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Roles are a great way to adapt the behavior of your website depending on
|
||||||
|
groups of users, but they can also be used to further secure some areas by
|
||||||
|
defining access rules::
|
||||||
|
|
||||||
|
$app['security.access_rules'] = array(
|
||||||
|
array('^/admin', 'ROLE_ADMIN', 'https'),
|
||||||
|
array('^.*$', 'ROLE_USER'),
|
||||||
|
);
|
||||||
|
|
||||||
|
With the above configuration, users must have the ``ROLE_ADMIN`` to access the
|
||||||
|
``/admin`` section of the website, and ``ROLE_USER`` for everything else.
|
||||||
|
Furthermore, the admin section can only be accessible via HTTPS (if that's not
|
||||||
|
the case, the user will be automatically redirected).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The first argument can also be a `RequestMatcher
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/RequestMatcher.html>`_
|
||||||
|
instance.
|
||||||
|
|
||||||
|
Defining a custom User Provider
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Using an array of users is simple and useful when securing an admin section of
|
||||||
|
a personal website, but you can override this default mechanism with you own.
|
||||||
|
|
||||||
|
The ``users`` setting can be defined as a service that returns an instance of
|
||||||
|
`UserProviderInterface
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Security/Core/User/UserProviderInterface.html>`_::
|
||||||
|
|
||||||
|
'users' => function () use ($app) {
|
||||||
|
return new UserProvider($app['db']);
|
||||||
|
},
|
||||||
|
|
||||||
|
Here is a simple example of a user provider, where Doctrine DBAL is used to
|
||||||
|
store the users::
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\User;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
|
||||||
|
class UserProvider implements UserProviderInterface
|
||||||
|
{
|
||||||
|
private $conn;
|
||||||
|
|
||||||
|
public function __construct(Connection $conn)
|
||||||
|
{
|
||||||
|
$this->conn = $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadUserByUsername($username)
|
||||||
|
{
|
||||||
|
$stmt = $this->conn->executeQuery('SELECT * FROM users WHERE username = ?', array(strtolower($username)));
|
||||||
|
|
||||||
|
if (!$user = $stmt->fetch()) {
|
||||||
|
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new User($user['username'], $user['password'], explode(',', $user['roles']), true, true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshUser(UserInterface $user)
|
||||||
|
{
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->loadUserByUsername($user->getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsClass($class)
|
||||||
|
{
|
||||||
|
return $class === 'Symfony\Component\Security\Core\User\User';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
In this example, instances of the default ``User`` class are created for the
|
||||||
|
users, but you can define your own class; the only requirement is that the
|
||||||
|
class must implement `UserInterface
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Security/Core/User/UserInterface.html>`_
|
||||||
|
|
||||||
|
And here is the code that you can use to create the database schema and some
|
||||||
|
sample users::
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Table;
|
||||||
|
|
||||||
|
$schema = $app['db']->getSchemaManager();
|
||||||
|
if (!$schema->tablesExist('users')) {
|
||||||
|
$users = new Table('users');
|
||||||
|
$users->addColumn('id', 'integer', array('unsigned' => true, 'autoincrement' => true));
|
||||||
|
$users->setPrimaryKey(array('id'));
|
||||||
|
$users->addColumn('username', 'string', array('length' => 32));
|
||||||
|
$users->addUniqueIndex(array('username'));
|
||||||
|
$users->addColumn('password', 'string', array('length' => 255));
|
||||||
|
$users->addColumn('roles', 'string', array('length' => 255));
|
||||||
|
|
||||||
|
$schema->createTable($users);
|
||||||
|
|
||||||
|
$app['db']->insert('users', array(
|
||||||
|
'username' => 'fabien',
|
||||||
|
'password' => '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a',
|
||||||
|
'roles' => 'ROLE_USER'
|
||||||
|
));
|
||||||
|
|
||||||
|
$app['db']->insert('users', array(
|
||||||
|
'username' => 'admin',
|
||||||
|
'password' => '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a',
|
||||||
|
'roles' => 'ROLE_ADMIN'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
If you are using the Doctrine ORM, the Symfony bridge for Doctrine
|
||||||
|
provides a user provider class that is able to load users from your
|
||||||
|
entities.
|
||||||
|
|
||||||
|
Defining a custom Encoder
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
By default, Silex uses the ``BCrypt`` algorithm to encode passwords.
|
||||||
|
Additionally, the password is encoded multiple times.
|
||||||
|
You can change these defaults by overriding ``security.default_encoder``
|
||||||
|
service to return one of the predefined encoders:
|
||||||
|
|
||||||
|
* **security.encoder.digest**: Digest password encoder.
|
||||||
|
|
||||||
|
* **security.encoder.bcrypt**: BCrypt password encoder.
|
||||||
|
|
||||||
|
* **security.encoder.pbkdf2**: Pbkdf2 password encoder.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app['security.default_encoder'] = function ($app) {
|
||||||
|
return $app['security.encoder.pbkdf2'];
|
||||||
|
};
|
||||||
|
|
||||||
|
Or you can define you own, fully customizable encoder::
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder;
|
||||||
|
|
||||||
|
$app['security.default_encoder'] = function ($app) {
|
||||||
|
// Plain text (e.g. for debugging)
|
||||||
|
return new PlaintextPasswordEncoder();
|
||||||
|
};
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
You can change the default BCrypt encoding cost by overriding ``security.encoder.bcrypt.cost``
|
||||||
|
|
||||||
|
Defining a custom Authentication Provider
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The Symfony Security component provides a lot of ready-to-use authentication
|
||||||
|
providers (form, HTTP, X509, remember me, ...), but you can add new ones
|
||||||
|
easily. To register a new authentication provider, create a service named
|
||||||
|
``security.authentication_listener.factory.XXX`` where ``XXX`` is the name you want to
|
||||||
|
use in your configuration::
|
||||||
|
|
||||||
|
$app['security.authentication_listener.factory.wsse'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
// define the authentication provider object
|
||||||
|
$app['security.authentication_provider.'.$name.'.wsse'] = function () use ($app) {
|
||||||
|
return new WsseProvider($app['security.user_provider.default'], __DIR__.'/security_cache');
|
||||||
|
};
|
||||||
|
|
||||||
|
// define the authentication listener object
|
||||||
|
$app['security.authentication_listener.'.$name.'.wsse'] = function () use ($app) {
|
||||||
|
return new WsseListener($app['security.token_storage'], $app['security.authentication_manager']);
|
||||||
|
};
|
||||||
|
|
||||||
|
return array(
|
||||||
|
// the authentication provider id
|
||||||
|
'security.authentication_provider.'.$name.'.wsse',
|
||||||
|
// the authentication listener id
|
||||||
|
'security.authentication_listener.'.$name.'.wsse',
|
||||||
|
// the entry point id
|
||||||
|
null,
|
||||||
|
// the position of the listener in the stack
|
||||||
|
'pre_auth'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
You can now use it in your configuration like any other built-in
|
||||||
|
authentication provider::
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
|
||||||
|
'security.firewalls' => array(
|
||||||
|
'default' => array(
|
||||||
|
'wsse' => true,
|
||||||
|
|
||||||
|
// ...
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
Instead of ``true``, you can also define an array of options that customize
|
||||||
|
the behavior of your authentication factory; it will be passed as the second
|
||||||
|
argument of your authentication factory (see above).
|
||||||
|
|
||||||
|
This example uses the authentication provider classes as described in the
|
||||||
|
Symfony `cookbook`_.
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The Guard component simplifies the creation of custom authentication
|
||||||
|
providers. :doc:`How to Create a Custom Authentication System with Guard
|
||||||
|
</cookbook/guard_authentication>`
|
||||||
|
|
||||||
|
Stateless Authentication
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
By default, a session cookie is created to persist the security context of
|
||||||
|
the user. However, if you use certificates, HTTP authentication, WSSE and so
|
||||||
|
on, the credentials are sent for each request. In that case, you can turn off
|
||||||
|
persistence by activating the ``stateless`` authentication flag::
|
||||||
|
|
||||||
|
$app['security.firewalls'] = array(
|
||||||
|
'default' => array(
|
||||||
|
'stateless' => true,
|
||||||
|
'wsse' => true,
|
||||||
|
|
||||||
|
// ...
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Traits
|
||||||
|
------
|
||||||
|
|
||||||
|
``Silex\Application\SecurityTrait`` adds the following shortcuts:
|
||||||
|
|
||||||
|
* **encodePassword**: Encode a given password.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$user = $app->user();
|
||||||
|
|
||||||
|
$encoded = $app->encodePassword($user, 'foo');
|
||||||
|
|
||||||
|
``Silex\Route\SecurityTrait`` adds the following methods to the controllers:
|
||||||
|
|
||||||
|
* **secure**: Secures a controller for the given roles.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->get('/', function () {
|
||||||
|
// do something but only for admins
|
||||||
|
})->secure('ROLE_ADMIN');
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
The ``Silex\Route\SecurityTrait`` must be used with a user defined
|
||||||
|
``Route`` class, not the application.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
use Silex\Route;
|
||||||
|
|
||||||
|
class MyRoute extends Route
|
||||||
|
{
|
||||||
|
use Route\SecurityTrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app['route_class'] = 'MyRoute';
|
||||||
|
|
||||||
|
|
||||||
|
.. _cookbook: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
|
|
@ -0,0 +1,73 @@
|
||||||
|
Serializer
|
||||||
|
==========
|
||||||
|
|
||||||
|
The *SerializerServiceProvider* provides a service for serializing objects.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **serializer**: An instance of `Symfony\\Component\\Serializer\\Serializer
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Serializer/Serializer.html>`_.
|
||||||
|
|
||||||
|
* **serializer.encoders**: `Symfony\\Component\\Serializer\\Encoder\\JsonEncoder
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Serializer/Encoder/JsonEncoder.html>`_
|
||||||
|
and `Symfony\\Component\\Serializer\\Encoder\\XmlEncoder
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Serializer/Encoder/XmlEncoder.html>`_.
|
||||||
|
|
||||||
|
* **serializer.normalizers**: `Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Serializer/Normalizer/CustomNormalizer.html>`_
|
||||||
|
and `Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.html>`_.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\SerializerServiceProvider());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Symfony's `Serializer Component
|
||||||
|
<http://symfony.com/doc/current/components/serializer.html>`_ as a
|
||||||
|
dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/serializer
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The ``SerializerServiceProvider`` provider provides a ``serializer`` service::
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
use Silex\Provider\SerializerServiceProvider;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
$app = new Application();
|
||||||
|
|
||||||
|
$app->register(new SerializerServiceProvider());
|
||||||
|
|
||||||
|
// only accept content types supported by the serializer via the assert method.
|
||||||
|
$app->get("/pages/{id}.{_format}", function (Request $request, $id) use ($app) {
|
||||||
|
// assume a page_repository service exists that returns Page objects. The
|
||||||
|
// object returned has getters and setters exposing the state.
|
||||||
|
$page = $app['page_repository']->find($id);
|
||||||
|
$format = $request->getRequestFormat();
|
||||||
|
|
||||||
|
if (!$page instanceof Page) {
|
||||||
|
$app->abort("No page found for id: $id");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response($app['serializer']->serialize($page, $format), 200, array(
|
||||||
|
"Content-Type" => $request->getMimeType($format)
|
||||||
|
));
|
||||||
|
})->assert("_format", "xml|json")
|
||||||
|
->assert("id", "\d+");
|
|
@ -0,0 +1,142 @@
|
||||||
|
Service Controllers
|
||||||
|
===================
|
||||||
|
|
||||||
|
As your Silex application grows, you may wish to begin organizing your
|
||||||
|
controllers in a more formal fashion. Silex can use controller classes out of
|
||||||
|
the box, but with a bit of work, your controllers can be created as services,
|
||||||
|
giving you the full power of dependency injection and lazy loading.
|
||||||
|
|
||||||
|
.. ::todo Link above to controller classes cookbook
|
||||||
|
|
||||||
|
Why would I want to do this?
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
- Dependency Injection over Service Location
|
||||||
|
|
||||||
|
Using this method, you can inject the actual dependencies required by your
|
||||||
|
controller and gain total inversion of control, while still maintaining the
|
||||||
|
lazy loading of your controllers and its dependencies. Because your
|
||||||
|
dependencies are clearly defined, they are easily mocked, allowing you to test
|
||||||
|
your controllers in isolation.
|
||||||
|
|
||||||
|
- Framework Independence
|
||||||
|
|
||||||
|
Using this method, your controllers start to become more independent of the
|
||||||
|
framework you are using. Carefully crafted, your controllers will become
|
||||||
|
reusable with multiple frameworks. By keeping careful control of your
|
||||||
|
dependencies, your controllers could easily become compatible with Silex,
|
||||||
|
Symfony (full stack) and Drupal, to name just a few.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
There are currently no parameters for the ``ServiceControllerServiceProvider``.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
There are no extra services provided, the ``ServiceControllerServiceProvider``
|
||||||
|
simply extends the existing **resolver** service.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
In this slightly contrived example of a blog API, we're going to change the
|
||||||
|
``/posts.json`` route to use a controller, that is defined as a service.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
use Demo\Repository\PostRepository;
|
||||||
|
|
||||||
|
$app = new Application();
|
||||||
|
|
||||||
|
$app['posts.repository'] = function() {
|
||||||
|
return new PostRepository;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->get('/posts.json', function() use ($app) {
|
||||||
|
return $app->json($app['posts.repository']->findAll());
|
||||||
|
});
|
||||||
|
|
||||||
|
Rewriting your controller as a service is pretty simple, create a Plain Ol' PHP
|
||||||
|
Object with your ``PostRepository`` as a dependency, along with an
|
||||||
|
``indexJsonAction`` method to handle the request. Although not shown in the
|
||||||
|
example below, you can use type hinting and parameter naming to get the
|
||||||
|
parameters you need, just like with standard Silex routes.
|
||||||
|
|
||||||
|
If you are a TDD/BDD fan (and you should be), you may notice that this
|
||||||
|
controller has well defined responsibilities and dependencies, and is easily
|
||||||
|
tested/specced. You may also notice that the only external dependency is on
|
||||||
|
``Symfony\Component\HttpFoundation\JsonResponse``, meaning this controller could
|
||||||
|
easily be used in a Symfony (full stack) application, or potentially with other
|
||||||
|
applications or frameworks that know how to handle a `Symfony/HttpFoundation
|
||||||
|
<http://symfony.com/doc/master/components/http_foundation/introduction.html>`_
|
||||||
|
``Response`` object.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
namespace Demo\Controller;
|
||||||
|
|
||||||
|
use Demo\Repository\PostRepository;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
|
||||||
|
class PostController
|
||||||
|
{
|
||||||
|
protected $repo;
|
||||||
|
|
||||||
|
public function __construct(PostRepository $repo)
|
||||||
|
{
|
||||||
|
$this->repo = $repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function indexJsonAction()
|
||||||
|
{
|
||||||
|
return new JsonResponse($this->repo->findAll());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
And lastly, define your controller as a service in the application, along with
|
||||||
|
your route. The syntax in the route definition is the name of the service,
|
||||||
|
followed by a single colon (:), followed by the method name.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app['posts.controller'] = function() use ($app) {
|
||||||
|
return new PostController($app['posts.repository']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->get('/posts.json', "posts.controller:indexJsonAction");
|
||||||
|
|
||||||
|
In addition to using classes for service controllers, you can define any
|
||||||
|
callable as a service in the application to be used for a route.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
namespace Demo\Controller;
|
||||||
|
|
||||||
|
use Demo\Repository\PostRepository;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
|
||||||
|
function postIndexJson(PostRepository $repo) {
|
||||||
|
return function() use ($repo) {
|
||||||
|
return new JsonResponse($repo->findAll());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
And when defining your route, the code would look like the following:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app['posts.controller'] = function($app) {
|
||||||
|
return Demo\Controller\postIndexJson($app['posts.repository']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->get('/posts.json', 'posts.controller');
|
|
@ -0,0 +1,103 @@
|
||||||
|
Session
|
||||||
|
=======
|
||||||
|
|
||||||
|
The *SessionServiceProvider* provides a service for storing data persistently
|
||||||
|
between requests.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **session.storage.save_path** (optional): The path for the
|
||||||
|
``NativeFileSessionHandler``, defaults to the value of
|
||||||
|
``sys_get_temp_dir()``.
|
||||||
|
|
||||||
|
* **session.storage.options**: An array of options that is passed to the
|
||||||
|
constructor of the ``session.storage`` service.
|
||||||
|
|
||||||
|
In case of the default `NativeSessionStorage
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.html>`_,
|
||||||
|
the most useful options are:
|
||||||
|
|
||||||
|
* **name**: The cookie name (_SESS by default)
|
||||||
|
* **id**: The session id (null by default)
|
||||||
|
* **cookie_lifetime**: Cookie lifetime
|
||||||
|
* **cookie_path**: Cookie path
|
||||||
|
* **cookie_domain**: Cookie domain
|
||||||
|
* **cookie_secure**: Cookie secure (HTTPS)
|
||||||
|
* **cookie_httponly**: Whether the cookie is http only
|
||||||
|
|
||||||
|
However, all of these are optional. Default Sessions life time is 1800
|
||||||
|
seconds (30 minutes). To override this, set the ``lifetime`` option.
|
||||||
|
|
||||||
|
For a full list of available options, read the `PHP
|
||||||
|
<http://php.net/session.configuration>`_ official documentation.
|
||||||
|
|
||||||
|
* **session.test**: Whether to simulate sessions or not (useful when writing
|
||||||
|
functional tests).
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **session**: An instance of Symfony's `Session
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Session/Session.html>`_.
|
||||||
|
|
||||||
|
* **session.storage**: A service that is used for persistence of the session
|
||||||
|
data.
|
||||||
|
|
||||||
|
* **session.storage.handler**: A service that is used by the
|
||||||
|
``session.storage`` for data access. Defaults to a `NativeFileSessionHandler
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.html>`_
|
||||||
|
storage handler.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\SessionServiceProvider());
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The Session provider provides a ``session`` service. Here is an example that
|
||||||
|
authenticates a user and creates a session for them::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
$app->get('/login', function (Request $request) use ($app) {
|
||||||
|
$username = $request->server->get('PHP_AUTH_USER', false);
|
||||||
|
$password = $request->server->get('PHP_AUTH_PW');
|
||||||
|
|
||||||
|
if ('igor' === $username && 'password' === $password) {
|
||||||
|
$app['session']->set('user', array('username' => $username));
|
||||||
|
return $app->redirect('/account');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = new Response();
|
||||||
|
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', 'site_login'));
|
||||||
|
$response->setStatusCode(401, 'Please sign in.');
|
||||||
|
return $response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->get('/account', function () use ($app) {
|
||||||
|
if (null === $user = $app['session']->get('user')) {
|
||||||
|
return $app->redirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Welcome {$user['username']}!";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Custom Session Configurations
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
If your system is using a custom session configuration (such as a redis handler
|
||||||
|
from a PHP extension) then you need to disable the NativeFileSessionHandler by
|
||||||
|
setting ``session.storage.handler`` to null. You will have to configure the
|
||||||
|
``session.save_path`` ini setting yourself in that case.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app['session.storage.handler'] = null;
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
Swiftmailer
|
||||||
|
===========
|
||||||
|
|
||||||
|
The *SwiftmailerServiceProvider* provides a service for sending email through
|
||||||
|
the `Swift Mailer <http://swiftmailer.org>`_ library.
|
||||||
|
|
||||||
|
You can use the ``mailer`` service to send messages easily. By default, it
|
||||||
|
will attempt to send emails through SMTP.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **swiftmailer.use_spool**: A boolean to specify whether or not to use the
|
||||||
|
memory spool, defaults to true.
|
||||||
|
|
||||||
|
* **swiftmailer.options**: An array of options for the default SMTP-based
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
The following options can be set:
|
||||||
|
|
||||||
|
* **host**: SMTP hostname, defaults to 'localhost'.
|
||||||
|
* **port**: SMTP port, defaults to 25.
|
||||||
|
* **username**: SMTP username, defaults to an empty string.
|
||||||
|
* **password**: SMTP password, defaults to an empty string.
|
||||||
|
* **encryption**: SMTP encryption, defaults to null. Valid values are 'tls', 'ssl', or null (indicating no encryption).
|
||||||
|
* **auth_mode**: SMTP authentication mode, defaults to null. Valid values are 'plain', 'login', 'cram-md5', or null.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
$app['swiftmailer.options'] = array(
|
||||||
|
'host' => 'host',
|
||||||
|
'port' => '25',
|
||||||
|
'username' => 'username',
|
||||||
|
'password' => 'password',
|
||||||
|
'encryption' => null,
|
||||||
|
'auth_mode' => null
|
||||||
|
);
|
||||||
|
|
||||||
|
* **swiftmailer.sender_address**: If set, all messages will be delivered with
|
||||||
|
this address as the "return path" address.
|
||||||
|
|
||||||
|
* **swiftmailer.delivery_addresses**: If not empty, all email messages will be
|
||||||
|
sent to those addresses instead of being sent to their actual recipients. This
|
||||||
|
is often useful when developing.
|
||||||
|
|
||||||
|
* **swiftmailer.delivery_whitelist**: Used in combination with
|
||||||
|
``delivery_addresses``. If set, emails matching any of these patterns will be
|
||||||
|
delivered like normal, as well as being sent to ``delivery_addresses``.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **mailer**: The mailer instance.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
$message = \Swift_Message::newInstance();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
$app['mailer']->send($message);
|
||||||
|
|
||||||
|
* **swiftmailer.transport**: The transport used for e-mail
|
||||||
|
delivery. Defaults to a ``Swift_Transport_EsmtpTransport``.
|
||||||
|
|
||||||
|
* **swiftmailer.transport.buffer**: StreamBuffer used by
|
||||||
|
the transport.
|
||||||
|
|
||||||
|
* **swiftmailer.transport.authhandler**: Authentication
|
||||||
|
handler used by the transport. Will try the following
|
||||||
|
by default: CRAM-MD5, login, plaintext.
|
||||||
|
|
||||||
|
* **swiftmailer.transport.eventdispatcher**: Internal event
|
||||||
|
dispatcher used by Swiftmailer.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\SwiftmailerServiceProvider());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add SwiftMailer as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require swiftmailer/swiftmailer
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The Swiftmailer provider provides a ``mailer`` service::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app->post('/feedback', function (Request $request) use ($app) {
|
||||||
|
$message = \Swift_Message::newInstance()
|
||||||
|
->setSubject('[YourSite] Feedback')
|
||||||
|
->setFrom(array('noreply@yoursite.com'))
|
||||||
|
->setTo(array('feedback@yoursite.com'))
|
||||||
|
->setBody($request->get('message'));
|
||||||
|
|
||||||
|
$app['mailer']->send($message);
|
||||||
|
|
||||||
|
return new Response('Thank you for your feedback!', 201);
|
||||||
|
});
|
||||||
|
|
||||||
|
Usage in commands
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
By default, the Swiftmailer provider sends the emails using the ``KernelEvents::TERMINATE``
|
||||||
|
event, which is fired after the response has been sent. However, as this event
|
||||||
|
isn't fired for console commands, your emails won't be sent.
|
||||||
|
|
||||||
|
For that reason, if you send emails using a command console, it is recommended
|
||||||
|
that you disable the use of the memory spool (before accessing ``$app['mailer']``)::
|
||||||
|
|
||||||
|
$app['swiftmailer.use_spool'] = false;
|
||||||
|
|
||||||
|
Alternatively, you can just make sure to flush the message spool by hand before
|
||||||
|
ending the command execution. To do so, use the following code::
|
||||||
|
|
||||||
|
$app['swiftmailer.spooltransport']
|
||||||
|
->getSpool()
|
||||||
|
->flushQueue($app['swiftmailer.transport'])
|
||||||
|
;
|
||||||
|
|
||||||
|
Traits
|
||||||
|
------
|
||||||
|
|
||||||
|
``Silex\Application\SwiftmailerTrait`` adds the following shortcuts:
|
||||||
|
|
||||||
|
* **mail**: Sends an email.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->mail(\Swift_Message::newInstance()
|
||||||
|
->setSubject('[YourSite] Feedback')
|
||||||
|
->setFrom(array('noreply@yoursite.com'))
|
||||||
|
->setTo(array('feedback@yoursite.com'))
|
||||||
|
->setBody($request->get('message')));
|
||||||
|
|
||||||
|
For more information, check out the `Swift Mailer documentation
|
||||||
|
<http://swiftmailer.org>`_.
|
|
@ -0,0 +1,193 @@
|
||||||
|
Translation
|
||||||
|
===========
|
||||||
|
|
||||||
|
The *TranslationServiceProvider* provides a service for translating your
|
||||||
|
application into different languages.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **translator.domains** (optional): A mapping of domains/locales/messages.
|
||||||
|
This parameter contains the translation data for all languages and domains.
|
||||||
|
|
||||||
|
* **locale** (optional): The locale for the translator. You will most likely
|
||||||
|
want to set this based on some request parameter. Defaults to ``en``.
|
||||||
|
|
||||||
|
* **locale_fallbacks** (optional): Fallback locales for the translator. It will
|
||||||
|
be used when the current locale has no messages set. Defaults to ``en``.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **translator**: An instance of `Translator
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Translation/Translator.html>`_,
|
||||||
|
that is used for translation.
|
||||||
|
|
||||||
|
* **translator.loader**: An instance of an implementation of the translation
|
||||||
|
`LoaderInterface
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Translation/Loader/LoaderInterface.html>`_,
|
||||||
|
defaults to an `ArrayLoader
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Translation/Loader/ArrayLoader.html>`_.
|
||||||
|
|
||||||
|
* **translator.message_selector**: An instance of `MessageSelector
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Translation/MessageSelector.html>`_.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\LocaleServiceProvider());
|
||||||
|
$app->register(new Silex\Provider\TranslationServiceProvider(), array(
|
||||||
|
'locale_fallbacks' => array('en'),
|
||||||
|
));
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Symfony Translation Component as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/translation
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The Translation provider provides a ``translator`` service and makes use of
|
||||||
|
the ``translator.domains`` parameter::
|
||||||
|
|
||||||
|
$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.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->get('/{_locale}/{message}/{name}', function ($message, $name) use ($app) {
|
||||||
|
return $app['translator']->trans($message, array('%name%' => $name));
|
||||||
|
});
|
||||||
|
|
||||||
|
The above example will result in following routes:
|
||||||
|
|
||||||
|
* ``/en/hello/igor`` will return ``Hello igor``.
|
||||||
|
|
||||||
|
* ``/de/hello/igor`` will return ``Hallo igor``.
|
||||||
|
|
||||||
|
* ``/fr/hello/igor`` will return ``Bonjour igor``.
|
||||||
|
|
||||||
|
* ``/it/hello/igor`` will return ``Hello igor`` (because of the fallback).
|
||||||
|
|
||||||
|
Using Resources
|
||||||
|
---------------
|
||||||
|
|
||||||
|
When translations are stored in a file, you can load them as follows::
|
||||||
|
|
||||||
|
$app = new Application();
|
||||||
|
|
||||||
|
$app->register(new TranslationServiceProvider());
|
||||||
|
$app->extend('translator.resources', function ($resources, $app) {
|
||||||
|
$resources = array_merge($resources, array(
|
||||||
|
array('array', array('This value should be a valid number.' => 'Cette valeur doit être un nombre.'), 'fr', 'validators'),
|
||||||
|
));
|
||||||
|
|
||||||
|
return $resources;
|
||||||
|
});
|
||||||
|
|
||||||
|
Traits
|
||||||
|
------
|
||||||
|
|
||||||
|
``Silex\Application\TranslationTrait`` adds the following shortcuts:
|
||||||
|
|
||||||
|
* **trans**: Translates the given message.
|
||||||
|
|
||||||
|
* **transChoice**: Translates the given choice message by choosing a
|
||||||
|
translation according to a number.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->trans('Hello World');
|
||||||
|
|
||||||
|
$app->transChoice('Hello World');
|
||||||
|
|
||||||
|
Recipes
|
||||||
|
-------
|
||||||
|
|
||||||
|
YAML-based language files
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Having your translations in PHP files can be inconvenient. This recipe will
|
||||||
|
show you how to load translations from external YAML files.
|
||||||
|
|
||||||
|
First, add the Symfony ``Config`` and ``Yaml`` components as dependencies:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/config symfony/yaml
|
||||||
|
|
||||||
|
Next, you have to create the language mappings in YAML files. A naming you can
|
||||||
|
use is ``locales/en.yml``. Just do the mapping in this file as follows:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
hello: Hello %name%
|
||||||
|
goodbye: Goodbye %name%
|
||||||
|
|
||||||
|
Then, register the ``YamlFileLoader`` on the ``translator`` and add all your
|
||||||
|
translation files::
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\Loader\YamlFileLoader;
|
||||||
|
|
||||||
|
$app->extend('translator', function($translator, $app) {
|
||||||
|
$translator->addLoader('yaml', new YamlFileLoader());
|
||||||
|
|
||||||
|
$translator->addResource('yaml', __DIR__.'/locales/en.yml', 'en');
|
||||||
|
$translator->addResource('yaml', __DIR__.'/locales/de.yml', 'de');
|
||||||
|
$translator->addResource('yaml', __DIR__.'/locales/fr.yml', 'fr');
|
||||||
|
|
||||||
|
return $translator;
|
||||||
|
});
|
||||||
|
|
||||||
|
XLIFF-based language files
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Just as you would do with YAML translation files, you first need to add the
|
||||||
|
Symfony ``Config`` component as a dependency (see above for details).
|
||||||
|
|
||||||
|
Then, similarly, create XLIFF files in your locales directory and add them to
|
||||||
|
the translator::
|
||||||
|
|
||||||
|
$translator->addResource('xliff', __DIR__.'/locales/en.xlf', 'en');
|
||||||
|
$translator->addResource('xliff', __DIR__.'/locales/de.xlf', 'de');
|
||||||
|
$translator->addResource('xliff', __DIR__.'/locales/fr.xlf', 'fr');
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The XLIFF loader is already pre-configured by the extension.
|
||||||
|
|
||||||
|
Accessing translations in Twig templates
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Once loaded, the translation service provider is available from within Twig
|
||||||
|
templates when using the Twig bridge provided by Symfony (see
|
||||||
|
:doc:`TwigServiceProvider </providers/twig>`):
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{{ 'translation_key'|trans }}
|
||||||
|
{{ 'translation_key'|transchoice }}
|
||||||
|
{% trans %}translation_key{% endtrans %}
|
|
@ -0,0 +1,200 @@
|
||||||
|
Twig
|
||||||
|
====
|
||||||
|
|
||||||
|
The *TwigServiceProvider* provides integration with the `Twig
|
||||||
|
<http://twig.sensiolabs.org/>`_ template engine.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **twig.path** (optional): Path to the directory containing twig template
|
||||||
|
files (it can also be an array of paths).
|
||||||
|
|
||||||
|
* **twig.templates** (optional): An associative array of template names to
|
||||||
|
template contents. Use this if you want to define your templates inline.
|
||||||
|
|
||||||
|
* **twig.options** (optional): An associative array of twig
|
||||||
|
options. Check out the `twig documentation <http://twig.sensiolabs.org/doc/api.html#environment-options>`_
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
* **twig.form.templates** (optional): An array of templates used to render
|
||||||
|
forms (only available when the ``FormServiceProvider`` is enabled). The
|
||||||
|
default theme is ``form_div_layout.html.twig``, but you can use the other
|
||||||
|
built-in themes: ``form_table_layout.html.twig``,
|
||||||
|
``bootstrap_3_layout.html.twig``, and
|
||||||
|
``bootstrap_3_horizontal_layout.html.twig``.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **twig**: The ``Twig_Environment`` instance. The main way of
|
||||||
|
interacting with Twig.
|
||||||
|
|
||||||
|
* **twig.loader**: The loader for Twig templates which uses the ``twig.path``
|
||||||
|
and the ``twig.templates`` options. You can also replace the loader
|
||||||
|
completely.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\TwigServiceProvider(), array(
|
||||||
|
'twig.path' => __DIR__.'/views',
|
||||||
|
));
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add Twig as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require twig/twig
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The Twig provider provides a ``twig`` service that can render templates::
|
||||||
|
|
||||||
|
$app->get('/hello/{name}', function ($name) use ($app) {
|
||||||
|
return $app['twig']->render('hello.twig', array(
|
||||||
|
'name' => $name,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
Symfony Components Integration
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
Symfony provides a Twig bridge that provides additional integration between
|
||||||
|
some Symfony components and Twig. Add it as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/twig-bridge
|
||||||
|
|
||||||
|
When present, the ``TwigServiceProvider`` will provide you with the following
|
||||||
|
additional capabilities.
|
||||||
|
|
||||||
|
* Access to the ``path()`` and ``url()`` functions. You can find more
|
||||||
|
information in the `Symfony Routing documentation
|
||||||
|
<http://symfony.com/doc/current/book/routing.html#generating-urls-from-a-template>`_:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{{ path('homepage') }}
|
||||||
|
{{ url('homepage') }} {# generates the absolute url http://example.org/ #}
|
||||||
|
{{ path('hello', {name: 'Fabien'}) }}
|
||||||
|
{{ url('hello', {name: 'Fabien'}) }} {# generates the absolute url http://example.org/hello/Fabien #}
|
||||||
|
|
||||||
|
* Access to the ``absolute_url()`` and ``relative_path()`` Twig functions.
|
||||||
|
|
||||||
|
Translations Support
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
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 `Symfony Translation documentation
|
||||||
|
<http://symfony.com/doc/current/book/translation.html#twig-templates>`_.
|
||||||
|
|
||||||
|
Form Support
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
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 `Symfony
|
||||||
|
Forms reference
|
||||||
|
<http://symfony.com/doc/current/reference/forms/twig_reference.html>`_.
|
||||||
|
|
||||||
|
Security Support
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you are using the ``SecurityServiceProvider``, you will have access to the
|
||||||
|
``is_granted()`` function in templates. You can find more information in the
|
||||||
|
`Symfony Security documentation
|
||||||
|
<http://symfony.com/doc/current/book/security.html#access-control-in-templates>`_.
|
||||||
|
|
||||||
|
Global Variable
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When the Twig bridge is available, the ``global`` variable refers to an
|
||||||
|
instance of `AppVariable <http://api.symfony.com/master/Symfony/Bridge/Twig/AppVariable.html>`_.
|
||||||
|
It gives access to the following methods:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{# The current Request #}
|
||||||
|
{{ global.request }}
|
||||||
|
|
||||||
|
{# The current User (when security is enabled) #}
|
||||||
|
{{ global.user }}
|
||||||
|
|
||||||
|
{# The current Session #}
|
||||||
|
{{ global.session }}
|
||||||
|
|
||||||
|
{# The debug flag #}
|
||||||
|
{{ global.debug }}
|
||||||
|
|
||||||
|
Rendering a Controller
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A ``render`` function is also registered to help you render another controller
|
||||||
|
from a template (available when the :doc:`HttpFragment Service Provider
|
||||||
|
</providers/http_fragment>` is registered):
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{{ render(url('sidebar')) }}
|
||||||
|
|
||||||
|
{# or you can reference a controller directly without defining a route for it #}
|
||||||
|
{{ render(controller(controller)) }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You must prepend the ``app.request.baseUrl`` to render calls to ensure
|
||||||
|
that the render works when deployed into a sub-directory of the docroot.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Read the Twig `reference`_ for Symfony document to learn more about the
|
||||||
|
various Twig functions.
|
||||||
|
|
||||||
|
Traits
|
||||||
|
------
|
||||||
|
|
||||||
|
``Silex\Application\TwigTrait`` adds the following shortcuts:
|
||||||
|
|
||||||
|
* **render**: Renders a view with the given parameters and returns a Response
|
||||||
|
object.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
return $app->render('index.html', ['name' => 'Fabien']);
|
||||||
|
|
||||||
|
$response = new Response();
|
||||||
|
$response->setTtl(10);
|
||||||
|
|
||||||
|
return $app->render('index.html', ['name' => 'Fabien'], $response);
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
// stream a view
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
|
return $app->render('index.html', ['name' => 'Fabien'], new StreamedResponse());
|
||||||
|
|
||||||
|
Customization
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You can configure the Twig environment before using it by extending the
|
||||||
|
``twig`` service::
|
||||||
|
|
||||||
|
$app->extend('twig', function($twig, $app) {
|
||||||
|
$twig->addGlobal('pi', 3.14);
|
||||||
|
$twig->addFilter('levenshtein', new \Twig_Filter_Function('levenshtein'));
|
||||||
|
|
||||||
|
return $twig;
|
||||||
|
});
|
||||||
|
|
||||||
|
For more information, check out the `official Twig documentation
|
||||||
|
<http://twig.sensiolabs.org>`_.
|
||||||
|
|
||||||
|
.. _reference: https://symfony.com/doc/current/reference/twig_reference.html#controller
|
|
@ -0,0 +1,217 @@
|
||||||
|
Validator
|
||||||
|
=========
|
||||||
|
|
||||||
|
The *ValidatorServiceProvider* provides a service for validating data. It is
|
||||||
|
most useful when used with the *FormServiceProvider*, but can also be used
|
||||||
|
standalone.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **validator.validator_service_ids**: An array of service names representing
|
||||||
|
validators.
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **validator**: An instance of `Validator
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Validator/ValidatorInterface.html>`_.
|
||||||
|
|
||||||
|
* **validator.mapping.class_metadata_factory**: Factory for metadata loaders,
|
||||||
|
which can read validation constraint information from classes. Defaults to
|
||||||
|
StaticMethodLoader--ClassMetadataFactory.
|
||||||
|
|
||||||
|
This means you can define a static ``loadValidatorMetadata`` method on your
|
||||||
|
data class, which takes a ClassMetadata argument. Then you can set
|
||||||
|
constraints on this ClassMetadata instance.
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\ValidatorServiceProvider());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Symfony Validator Component as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/validator
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The Validator provider provides a ``validator`` service.
|
||||||
|
|
||||||
|
Validating Values
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can validate values directly using the ``validate`` validator
|
||||||
|
method::
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
$app->get('/validate/{email}', function ($email) use ($app) {
|
||||||
|
$errors = $app['validator']->validate($email, new Assert\Email());
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
return (string) $errors;
|
||||||
|
} else {
|
||||||
|
return 'The email is valid';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Validating Associative Arrays
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Validating associative arrays is like validating simple values, with a
|
||||||
|
collection of constraints::
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
$book = array(
|
||||||
|
'title' => 'My Book',
|
||||||
|
'author' => array(
|
||||||
|
'first_name' => 'Fabien',
|
||||||
|
'last_name' => 'Potencier',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$constraint = new Assert\Collection(array(
|
||||||
|
'title' => new Assert\Length(array('min' => 10)),
|
||||||
|
'author' => new Assert\Collection(array(
|
||||||
|
'first_name' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 10))),
|
||||||
|
'last_name' => new Assert\Length(array('min' => 10)),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
$errors = $app['validator']->validate($book, $constraint);
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
echo $error->getPropertyPath().' '.$error->getMessage()."\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo 'The book is valid';
|
||||||
|
}
|
||||||
|
|
||||||
|
Validating Objects
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you want to add validations to a class, you can define the constraint for
|
||||||
|
the class properties and getters, and then call the ``validate`` method::
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
class Book
|
||||||
|
{
|
||||||
|
public $title;
|
||||||
|
public $author;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Author
|
||||||
|
{
|
||||||
|
public $first_name;
|
||||||
|
public $last_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$author = new Author();
|
||||||
|
$author->first_name = 'Fabien';
|
||||||
|
$author->last_name = 'Potencier';
|
||||||
|
|
||||||
|
$book = new Book();
|
||||||
|
$book->title = 'My Book';
|
||||||
|
$book->author = $author;
|
||||||
|
|
||||||
|
$metadata = $app['validator.mapping.class_metadata_factory']->getMetadataFor('Author');
|
||||||
|
$metadata->addPropertyConstraint('first_name', new Assert\NotBlank());
|
||||||
|
$metadata->addPropertyConstraint('first_name', new Assert\Length(array('min' => 10)));
|
||||||
|
$metadata->addPropertyConstraint('last_name', new Assert\Length(array('min' => 10)));
|
||||||
|
|
||||||
|
$metadata = $app['validator.mapping.class_metadata_factory']->getMetadataFor('Book');
|
||||||
|
$metadata->addPropertyConstraint('title', new Assert\Length(array('min' => 10)));
|
||||||
|
$metadata->addPropertyConstraint('author', new Assert\Valid());
|
||||||
|
|
||||||
|
$errors = $app['validator']->validate($book);
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
echo $error->getPropertyPath().' '.$error->getMessage()."\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo 'The author is valid';
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also declare the class constraint by adding a static
|
||||||
|
``loadValidatorMetadata`` method to your classes::
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
class Book
|
||||||
|
{
|
||||||
|
public $title;
|
||||||
|
public $author;
|
||||||
|
|
||||||
|
static public function loadValidatorMetadata(ClassMetadata $metadata)
|
||||||
|
{
|
||||||
|
$metadata->addPropertyConstraint('title', new Assert\Length(array('min' => 10)));
|
||||||
|
$metadata->addPropertyConstraint('author', new Assert\Valid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Author
|
||||||
|
{
|
||||||
|
public $first_name;
|
||||||
|
public $last_name;
|
||||||
|
|
||||||
|
static public function loadValidatorMetadata(ClassMetadata $metadata)
|
||||||
|
{
|
||||||
|
$metadata->addPropertyConstraint('first_name', new Assert\NotBlank());
|
||||||
|
$metadata->addPropertyConstraint('first_name', new Assert\Length(array('min' => 10)));
|
||||||
|
$metadata->addPropertyConstraint('last_name', new Assert\Length(array('min' => 10)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$app->get('/validate/{email}', function ($email) use ($app) {
|
||||||
|
$author = new Author();
|
||||||
|
$author->first_name = 'Fabien';
|
||||||
|
$author->last_name = 'Potencier';
|
||||||
|
|
||||||
|
$book = new Book();
|
||||||
|
$book->title = 'My Book';
|
||||||
|
$book->author = $author;
|
||||||
|
|
||||||
|
$errors = $app['validator']->validate($book);
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
echo $error->getPropertyPath().' '.$error->getMessage()."\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo 'The author is valid';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Use ``addGetterConstraint()`` to add constraints on getter methods and
|
||||||
|
``addConstraint()`` to add constraints on the class itself.
|
||||||
|
|
||||||
|
Translation
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
To be able to translate the error messages, you can use the translator
|
||||||
|
provider and register the messages under the ``validators`` domain::
|
||||||
|
|
||||||
|
$app['translator.domains'] = array(
|
||||||
|
'validators' => array(
|
||||||
|
'fr' => array(
|
||||||
|
'This value should be a valid number.' => 'Cette valeur doit être un nombre.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
For more information, consult the `Symfony Validation documentation
|
||||||
|
<http://symfony.com/doc/master/book/validation.html>`_.
|
|
@ -0,0 +1,44 @@
|
||||||
|
Var Dumper
|
||||||
|
==========
|
||||||
|
|
||||||
|
The *VarDumperServiceProvider* provides a mechanism that allows exploring then
|
||||||
|
dumping any PHP variable.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
* **var_dumper.dump_destination**: A stream URL where dumps should be written
|
||||||
|
to (defaults to ``null``).
|
||||||
|
|
||||||
|
Services
|
||||||
|
--------
|
||||||
|
|
||||||
|
* n/a
|
||||||
|
|
||||||
|
Registering
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->register(new Silex\Provider\VarDumperServiceProvider());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Add the Symfony VarDumper Component as a dependency:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require symfony/var-dumper
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
Adding the VarDumper component as a Composer dependency gives you access to the
|
||||||
|
``dump()`` PHP function anywhere in your code.
|
||||||
|
|
||||||
|
If you are using Twig, it also provides a ``dump()`` Twig function and a
|
||||||
|
``dump`` Twig tag.
|
||||||
|
|
||||||
|
The VarDumperServiceProvider is also useful when used with the Silex
|
||||||
|
WebProfiler as the dumps are made available in the web debug toolbar and in the
|
||||||
|
web profiler.
|
|
@ -0,0 +1,264 @@
|
||||||
|
Services
|
||||||
|
========
|
||||||
|
|
||||||
|
Silex is not only a framework, it is also a service container. It does this by
|
||||||
|
extending `Pimple <http://pimple.sensiolabs.org>`_ which provides a very simple
|
||||||
|
service container.
|
||||||
|
|
||||||
|
Dependency Injection
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You can skip this if you already know what Dependency Injection is.
|
||||||
|
|
||||||
|
Dependency Injection is a design pattern where you pass dependencies to
|
||||||
|
services instead of creating them from within the service or relying on
|
||||||
|
globals. This generally leads to code that is decoupled, re-usable, flexible
|
||||||
|
and testable.
|
||||||
|
|
||||||
|
Here is an example of a class that takes a ``User`` object and stores it as a
|
||||||
|
file in JSON format::
|
||||||
|
|
||||||
|
class JsonUserPersister
|
||||||
|
{
|
||||||
|
private $basePath;
|
||||||
|
|
||||||
|
public function __construct($basePath)
|
||||||
|
{
|
||||||
|
$this->basePath = $basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function persist(User $user)
|
||||||
|
{
|
||||||
|
$data = $user->getAttributes();
|
||||||
|
$json = json_encode($data);
|
||||||
|
$filename = $this->basePath.'/'.$user->id.'.json';
|
||||||
|
file_put_contents($filename, $json, LOCK_EX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
In this simple example the dependency is the ``basePath`` property. It is
|
||||||
|
passed to the constructor. This means you can create several independent
|
||||||
|
instances with different base paths. Of course dependencies do not have to be
|
||||||
|
simple strings. More often they are in fact other services.
|
||||||
|
|
||||||
|
A service container is responsible for creating and storing services. It can
|
||||||
|
recursively create dependencies of the requested services and inject them. It
|
||||||
|
does so lazily, which means a service is only created when you actually need it.
|
||||||
|
|
||||||
|
Pimple
|
||||||
|
------
|
||||||
|
|
||||||
|
Pimple makes strong use of closures and implements the ArrayAccess interface.
|
||||||
|
|
||||||
|
We will start off by creating a new instance of Pimple -- and because
|
||||||
|
``Silex\Application`` extends ``Pimple\Container`` all of this applies to Silex
|
||||||
|
as well::
|
||||||
|
|
||||||
|
$container = new Pimple\Container();
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
You can set parameters (which are usually strings) by setting an array key on
|
||||||
|
the container::
|
||||||
|
|
||||||
|
$app['some_parameter'] = 'value';
|
||||||
|
|
||||||
|
The array key can be any value. By convention dots are used for namespacing::
|
||||||
|
|
||||||
|
$app['asset.host'] = 'http://cdn.mysite.com/';
|
||||||
|
|
||||||
|
Reading parameter values is possible with the same syntax::
|
||||||
|
|
||||||
|
echo $app['some_parameter'];
|
||||||
|
|
||||||
|
Service definitions
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Defining services is no different than defining parameters. You just set an
|
||||||
|
array key on the container to be a closure. However, when you retrieve the
|
||||||
|
service, the closure is executed. This allows for lazy service creation::
|
||||||
|
|
||||||
|
$app['some_service'] = function () {
|
||||||
|
return new Service();
|
||||||
|
};
|
||||||
|
|
||||||
|
And to retrieve the service, use::
|
||||||
|
|
||||||
|
$service = $app['some_service'];
|
||||||
|
|
||||||
|
On first invocation, this will create the service; the same instance will then
|
||||||
|
be returned on any subsequent access.
|
||||||
|
|
||||||
|
Factory services
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you want a different instance to be returned for each service access, wrap
|
||||||
|
the service definition with the ``factory()`` method::
|
||||||
|
|
||||||
|
$app['some_service'] = $app->factory(function () {
|
||||||
|
return new Service();
|
||||||
|
});
|
||||||
|
|
||||||
|
Every time you call ``$app['some_service']``, a new instance of the service is
|
||||||
|
created.
|
||||||
|
|
||||||
|
Access container from closure
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In many cases you will want to access the service container from within a
|
||||||
|
service definition closure. For example when fetching services the current
|
||||||
|
service depends on.
|
||||||
|
|
||||||
|
Because of this, the container is passed to the closure as an argument::
|
||||||
|
|
||||||
|
$app['some_service'] = function ($app) {
|
||||||
|
return new Service($app['some_other_service'], $app['some_service.config']);
|
||||||
|
};
|
||||||
|
|
||||||
|
Here you can see an example of Dependency Injection. ``some_service`` depends
|
||||||
|
on ``some_other_service`` and takes ``some_service.config`` as configuration
|
||||||
|
options. The dependency is only created when ``some_service`` is accessed, and
|
||||||
|
it is possible to replace either of the dependencies by simply overriding
|
||||||
|
those definitions.
|
||||||
|
|
||||||
|
Going back to our initial example, here's how we could use the container
|
||||||
|
to manage its dependencies::
|
||||||
|
|
||||||
|
$app['user.persist_path'] = '/tmp/users';
|
||||||
|
$app['user.persister'] = function ($app) {
|
||||||
|
return new JsonUserPersister($app['user.persist_path']);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Protected closures
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Because the container sees closures as factories for services, it will always
|
||||||
|
execute them when reading them.
|
||||||
|
|
||||||
|
In some cases you will however want to store a closure as a parameter, so that
|
||||||
|
you can fetch it and execute it yourself -- with your own arguments.
|
||||||
|
|
||||||
|
This is why Pimple allows you to protect your closures from being executed, by
|
||||||
|
using the ``protect`` method::
|
||||||
|
|
||||||
|
$app['closure_parameter'] = $app->protect(function ($a, $b) {
|
||||||
|
return $a + $b;
|
||||||
|
});
|
||||||
|
|
||||||
|
// will not execute the closure
|
||||||
|
$add = $app['closure_parameter'];
|
||||||
|
|
||||||
|
// calling it now
|
||||||
|
echo $add(2, 3);
|
||||||
|
|
||||||
|
Note that protected closures do not get access to the container.
|
||||||
|
|
||||||
|
Core services
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Silex defines a range of services.
|
||||||
|
|
||||||
|
* **request_stack**: Controls the lifecycle of requests, an instance of
|
||||||
|
`RequestStack <http://api.symfony.com/master/Symfony/Component/HttpFoundation/RequestStack.html>` _.
|
||||||
|
It gives you access to ``GET``, ``POST`` parameters and lots more!
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
$id = $app['request_stack']->getCurrentRequest()->get('id');
|
||||||
|
|
||||||
|
A request is only available when a request is being served; you can only
|
||||||
|
access it from within a controller, an application before/after middlewares,
|
||||||
|
or an error handler.
|
||||||
|
|
||||||
|
* **routes**: The `RouteCollection
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_
|
||||||
|
that is used internally. You can add, modify, read routes.
|
||||||
|
|
||||||
|
* **url_generator**: An instance of `UrlGenerator
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Routing/Generator/UrlGenerator.html>`_,
|
||||||
|
using the `RouteCollection
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/Routing/RouteCollection.html>`_
|
||||||
|
that is provided through the ``routes`` service. It has a ``generate``
|
||||||
|
method, which takes the route name as an argument, followed by an array of
|
||||||
|
route parameters.
|
||||||
|
|
||||||
|
* **controllers**: The ``Silex\ControllerCollection`` that is used internally.
|
||||||
|
Check the :doc:`Internals chapter <internals>` for more information.
|
||||||
|
|
||||||
|
* **dispatcher**: The `EventDispatcher
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/EventDispatcher/EventDispatcher.html>`_
|
||||||
|
that is used internally. It is the core of the Symfony system and is used
|
||||||
|
quite a bit by Silex.
|
||||||
|
|
||||||
|
* **resolver**: The `ControllerResolver
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/Controller/ControllerResolver.html>`_
|
||||||
|
that is used internally. It takes care of executing the controller with the
|
||||||
|
right arguments.
|
||||||
|
|
||||||
|
* **kernel**: The `HttpKernel
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpKernel/HttpKernel.html>`_
|
||||||
|
that is used internally. The HttpKernel is the heart of Symfony, it takes a
|
||||||
|
Request as input and returns a Response as output.
|
||||||
|
|
||||||
|
* **request_context**: The request context is a simplified representation of
|
||||||
|
the request that is used by the router and the URL generator.
|
||||||
|
|
||||||
|
* **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 does not return a Response. Disable it with
|
||||||
|
``unset($app['exception_handler'])``.
|
||||||
|
|
||||||
|
* **logger**: A `LoggerInterface <https://github.com/php-fig/log/blob/master/Psr/Log/LoggerInterface.php>`_ instance. By default, logging is
|
||||||
|
disabled as the value is set to ``null``. To enable logging you can either use
|
||||||
|
the :doc:`MonologServiceProvider <providers/monolog>` or define your own ``logger`` service that
|
||||||
|
conforms to the PSR logger interface.
|
||||||
|
|
||||||
|
Core traits
|
||||||
|
-----------
|
||||||
|
|
||||||
|
* ``Silex\Application\UrlGeneratorTrait`` adds the following shortcuts:
|
||||||
|
|
||||||
|
* **path**: Generates a path.
|
||||||
|
|
||||||
|
* **url**: Generates an absolute URL.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$app->path('homepage');
|
||||||
|
$app->url('homepage');
|
||||||
|
|
||||||
|
Core parameters
|
||||||
|
---------------
|
||||||
|
|
||||||
|
* **request.http_port** (optional): Allows you to override the default port
|
||||||
|
for non-HTTPS URLs. If the current request is HTTP, it will always use the
|
||||||
|
current port.
|
||||||
|
|
||||||
|
Defaults to 80.
|
||||||
|
|
||||||
|
This parameter can be used when generating URLs.
|
||||||
|
|
||||||
|
* **request.https_port** (optional): Allows you to override the default port
|
||||||
|
for HTTPS URLs. If the current request is HTTPS, it will always use the
|
||||||
|
current port.
|
||||||
|
|
||||||
|
Defaults to 443.
|
||||||
|
|
||||||
|
This parameter can be used when generating URLs.
|
||||||
|
|
||||||
|
* **debug** (optional): Returns whether or not the application is running in
|
||||||
|
debug mode.
|
||||||
|
|
||||||
|
Defaults to false.
|
||||||
|
|
||||||
|
* **charset** (optional): The charset to use for Responses.
|
||||||
|
|
||||||
|
Defaults to UTF-8.
|
|
@ -0,0 +1,221 @@
|
||||||
|
Testing
|
||||||
|
=======
|
||||||
|
|
||||||
|
Because Silex is built on top of Symfony, it is very easy to write functional
|
||||||
|
tests for your application. Functional tests are automated software tests that
|
||||||
|
ensure that your code is working correctly. They go through the user interface,
|
||||||
|
using a fake browser, and mimic the actions a user would do.
|
||||||
|
|
||||||
|
Why
|
||||||
|
---
|
||||||
|
|
||||||
|
If you are not familiar with software tests, you may be wondering why you would
|
||||||
|
need this. Every time you make a change to your application, you have to 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
|
||||||
|
test your application in usually under a second by running a single command.
|
||||||
|
|
||||||
|
For more information on functional testing, unit testing, and automated
|
||||||
|
software tests in general, check out `PHPUnit
|
||||||
|
<https://github.com/sebastianbergmann/phpunit>`_ and `Bulat Shakirzyanov's talk
|
||||||
|
on Clean Code <http://www.slideshare.net/avalanche123/clean-code-5609451>`_.
|
||||||
|
|
||||||
|
PHPUnit
|
||||||
|
-------
|
||||||
|
|
||||||
|
`PHPUnit <https://github.com/sebastianbergmann/phpunit>`_ is the de-facto
|
||||||
|
standard testing framework for PHP. It was built for writing unit tests, but it
|
||||||
|
can be used for functional tests too. You write tests by creating a new class,
|
||||||
|
that extends the ``PHPUnit_Framework_TestCase``. Your test cases are methods
|
||||||
|
prefixed with ``test``::
|
||||||
|
|
||||||
|
class ContactFormTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testInitialPage()
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
In your test cases, you do assertions on the state of what you are testing. In
|
||||||
|
this case we are testing a contact form, so we would want to assert that the
|
||||||
|
page loaded correctly and contains our form::
|
||||||
|
|
||||||
|
public function testInitialPage()
|
||||||
|
{
|
||||||
|
$statusCode = ...
|
||||||
|
$pageContent = ...
|
||||||
|
|
||||||
|
$this->assertEquals(200, $statusCode);
|
||||||
|
$this->assertContains('Contact us', $pageContent);
|
||||||
|
$this->assertContains('<form', $pageContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Here you see some of the available assertions. There is a full list available
|
||||||
|
in the `Writing Tests for PHPUnit
|
||||||
|
<https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html>`_
|
||||||
|
section of the PHPUnit documentation.
|
||||||
|
|
||||||
|
WebTestCase
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Symfony provides a WebTestCase class that can be used to write functional
|
||||||
|
tests. The Silex version of this class is ``Silex\WebTestCase``, and you can
|
||||||
|
use it by making your test extend it::
|
||||||
|
|
||||||
|
use Silex\WebTestCase;
|
||||||
|
|
||||||
|
class ContactFormTest extends WebTestCase
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
If you need to override the ``setUp()`` method, don't forget to call the
|
||||||
|
parent (``parent::setUp()``) to call the Silex default setup.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If you want to use the Symfony ``WebTestCase`` class you will need to
|
||||||
|
explicitly install its dependencies for your project:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require --dev symfony/browser-kit symfony/css-selector
|
||||||
|
|
||||||
|
For your WebTestCase, you will have to implement a ``createApplication``
|
||||||
|
method, which returns your application instance::
|
||||||
|
|
||||||
|
public function createApplication()
|
||||||
|
{
|
||||||
|
// app.php must return an Application instance
|
||||||
|
return require __DIR__.'/path/to/app.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
Make sure you do **not** use ``require_once`` here, as this method will be
|
||||||
|
executed before every test.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
By default, the application behaves in the same way as when using it from a
|
||||||
|
browser. But when an error occurs, it is sometimes easier to get raw
|
||||||
|
exceptions instead of HTML pages. It is rather simple if you tweak the
|
||||||
|
application configuration in the ``createApplication()`` method like
|
||||||
|
follows::
|
||||||
|
|
||||||
|
public function createApplication()
|
||||||
|
{
|
||||||
|
$app = require __DIR__.'/path/to/app.php';
|
||||||
|
$app['debug'] = true;
|
||||||
|
unset($app['exception_handler']);
|
||||||
|
|
||||||
|
return $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
If your application use sessions, set ``session.test`` to ``true`` to
|
||||||
|
simulate sessions::
|
||||||
|
|
||||||
|
public function createApplication()
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
$app['session.test'] = true;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
The WebTestCase provides a ``createClient`` method. A client acts as a browser,
|
||||||
|
and allows you to interact with your application. Here's how it works::
|
||||||
|
|
||||||
|
public function testInitialPage()
|
||||||
|
{
|
||||||
|
$client = $this->createClient();
|
||||||
|
$crawler = $client->request('GET', '/');
|
||||||
|
|
||||||
|
$this->assertTrue($client->getResponse()->isOk());
|
||||||
|
$this->assertCount(1, $crawler->filter('h1:contains("Contact us")'));
|
||||||
|
$this->assertCount(1, $crawler->filter('form'));
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
There are several things going on here. You have both a ``Client`` and a
|
||||||
|
``Crawler``.
|
||||||
|
|
||||||
|
You can also access the application through ``$this->app``.
|
||||||
|
|
||||||
|
Client
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
The client represents a browser. It holds your browsing history, cookies and
|
||||||
|
more. The ``request`` method allows you to make a request to a page on your
|
||||||
|
application.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You can find some documentation for it in `the client section of the
|
||||||
|
testing chapter of the Symfony documentation
|
||||||
|
<http://symfony.com/doc/current/book/testing.html#the-test-client>`_.
|
||||||
|
|
||||||
|
Crawler
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
The crawler allows you to inspect the content of a page. You can filter it
|
||||||
|
using CSS expressions and lots more.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You can find some documentation for it in `the crawler section of the testing
|
||||||
|
chapter of the Symfony documentation
|
||||||
|
<http://symfony.com/doc/current/book/testing.html#the-test-client>`_.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
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``. The ``phpunit.xml.dist`` file should
|
||||||
|
look like this:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
syntaxCheck="false"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="YourApp Test Suite">
|
||||||
|
<directory>./tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
</phpunit>
|
||||||
|
|
||||||
|
Your ``tests/YourApp/Tests/YourTest.php`` should look like this::
|
||||||
|
|
||||||
|
namespace YourApp\Tests;
|
||||||
|
|
||||||
|
use Silex\WebTestCase;
|
||||||
|
|
||||||
|
class YourTest extends WebTestCase
|
||||||
|
{
|
||||||
|
public function createApplication()
|
||||||
|
{
|
||||||
|
return require __DIR__.'/../../../app.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFooBar()
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Now, when running ``phpunit`` on the command line, tests should run.
|
|
@ -0,0 +1,805 @@
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you want to get started fast, `download`_ Silex as an archive and extract
|
||||||
|
it, you should have the following directory structure:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
├── composer.json
|
||||||
|
├── composer.lock
|
||||||
|
├── vendor
|
||||||
|
│ └── ...
|
||||||
|
└── web
|
||||||
|
└── index.php
|
||||||
|
|
||||||
|
If you want more flexibility, use Composer_ instead:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
composer require silex/silex:~2.0
|
||||||
|
|
||||||
|
Web Server
|
||||||
|
----------
|
||||||
|
|
||||||
|
All examples in the documentation rely on a well-configured web server; read
|
||||||
|
the :doc:`webserver documentation<web_servers>` to check yours.
|
||||||
|
|
||||||
|
Bootstrap
|
||||||
|
---------
|
||||||
|
|
||||||
|
To bootstrap Silex, all you need to do is require the ``vendor/autoload.php``
|
||||||
|
file and create an instance of ``Silex\Application``. After your controller
|
||||||
|
definitions, call the ``run`` method on your application::
|
||||||
|
|
||||||
|
// web/index.php
|
||||||
|
require_once __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
$app = new Silex\Application();
|
||||||
|
|
||||||
|
// ... definitions
|
||||||
|
|
||||||
|
$app->run();
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
When developing a website, you might want to turn on the debug mode to
|
||||||
|
ease debugging::
|
||||||
|
|
||||||
|
$app['debug'] = true;
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
If your application is hosted behind a reverse proxy at address ``$ip``,
|
||||||
|
and you want Silex to trust the ``X-Forwarded-For*`` headers, you will
|
||||||
|
need to run your application like this::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
Request::setTrustedProxies(array($ip));
|
||||||
|
$app->run();
|
||||||
|
|
||||||
|
Routing
|
||||||
|
-------
|
||||||
|
|
||||||
|
In Silex you define a route and the controller that is called when that
|
||||||
|
route is matched. A route pattern consists of:
|
||||||
|
|
||||||
|
* *Pattern*: The route pattern defines a path that points to a resource. The
|
||||||
|
pattern can include variable parts and you are able to set RegExp
|
||||||
|
requirements for them.
|
||||||
|
|
||||||
|
* *Method*: One of the following HTTP methods: ``GET``, ``POST``, ``PUT``,
|
||||||
|
``DELETE``, ``PATCH``, or ``OPTIONS``. This describes the interaction with
|
||||||
|
the resource.
|
||||||
|
|
||||||
|
The controller is defined using a closure like this::
|
||||||
|
|
||||||
|
function () {
|
||||||
|
// ... do something
|
||||||
|
}
|
||||||
|
|
||||||
|
The return value of the closure becomes the content of the page.
|
||||||
|
|
||||||
|
Example GET Route
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Here is an example definition of a ``GET`` route::
|
||||||
|
|
||||||
|
$blogPosts = array(
|
||||||
|
1 => array(
|
||||||
|
'date' => '2011-03-29',
|
||||||
|
'author' => 'igorw',
|
||||||
|
'title' => 'Using Silex',
|
||||||
|
'body' => '...',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->get('/blog', function () use ($blogPosts) {
|
||||||
|
$output = '';
|
||||||
|
foreach ($blogPosts as $post) {
|
||||||
|
$output .= $post['title'];
|
||||||
|
$output .= '<br />';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
});
|
||||||
|
|
||||||
|
Visiting ``/blog`` will return a list of blog post titles. The ``use``
|
||||||
|
statement means something different in this context. It tells the closure to
|
||||||
|
import the ``$blogPosts`` variable from the outer scope. This allows you to use
|
||||||
|
it from within the closure.
|
||||||
|
|
||||||
|
Dynamic Routing
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Now, you can create another controller for viewing individual blog posts::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function (Silex\Application $app, $id) use ($blogPosts) {
|
||||||
|
if (!isset($blogPosts[$id])) {
|
||||||
|
$app->abort(404, "Post $id does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$post = $blogPosts[$id];
|
||||||
|
|
||||||
|
return "<h1>{$post['title']}</h1>".
|
||||||
|
"<p>{$post['body']}</p>";
|
||||||
|
});
|
||||||
|
|
||||||
|
This route definition has a variable ``{id}`` part which is passed to the
|
||||||
|
closure.
|
||||||
|
|
||||||
|
The current ``Application`` is automatically injected by Silex to the Closure
|
||||||
|
thanks to the type hinting.
|
||||||
|
|
||||||
|
When the post does not exist, you are using ``abort()`` to stop the request
|
||||||
|
early. It actually throws an exception, which you will see how to handle later
|
||||||
|
on.
|
||||||
|
|
||||||
|
Example POST Route
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
POST routes signify the creation of a resource. An example for this is a
|
||||||
|
feedback form. You will use the ``mail`` function to send an e-mail::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
$app->post('/feedback', function (Request $request) {
|
||||||
|
$message = $request->get('message');
|
||||||
|
mail('feedback@yoursite.com', '[YourSite] Feedback', $message);
|
||||||
|
|
||||||
|
return new Response('Thank you for your feedback!', 201);
|
||||||
|
});
|
||||||
|
|
||||||
|
It is pretty straightforward.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
There is a :doc:`SwiftmailerServiceProvider <providers/swiftmailer>`
|
||||||
|
included that you can use instead of ``mail()``.
|
||||||
|
|
||||||
|
The current ``request`` is automatically injected by Silex to the Closure
|
||||||
|
thanks to the type hinting. It is an instance of
|
||||||
|
Request_, so you can fetch variables using the request ``get`` method.
|
||||||
|
|
||||||
|
Instead of returning a string you are returning an instance of Response_.
|
||||||
|
This allows setting an HTTP status code, in this case it is set to
|
||||||
|
``201 Created``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Silex always uses a ``Response`` internally, it converts strings to
|
||||||
|
responses with status code ``200``.
|
||||||
|
|
||||||
|
Other methods
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can create controllers for most HTTP methods. Just call one of these
|
||||||
|
methods on your application: ``get``, ``post``, ``put``, ``delete``, ``patch``, ``options``::
|
||||||
|
|
||||||
|
$app->put('/blog/{id}', function ($id) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->delete('/blog/{id}', function ($id) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->patch('/blog/{id}', function ($id) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Forms in most web browsers do not directly support the use of other HTTP
|
||||||
|
methods. To use methods other than GET and POST you can utilize a special
|
||||||
|
form field with a name of ``_method``. The form's ``method`` attribute must
|
||||||
|
be set to POST when using this field:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<form action="/my/target/route/" method="post">
|
||||||
|
<!-- ... -->
|
||||||
|
<input type="hidden" id="_method" name="_method" value="PUT" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
You need to explicitly enable this method override::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
Request::enableHttpMethodParameterOverride();
|
||||||
|
$app->run();
|
||||||
|
|
||||||
|
You can also call ``match``, which will match all methods. This can be
|
||||||
|
restricted via the ``method`` method::
|
||||||
|
|
||||||
|
$app->match('/blog', function () {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->match('/blog', function () {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->method('PATCH');
|
||||||
|
|
||||||
|
$app->match('/blog', function () {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->method('PUT|POST');
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The order in which the routes are defined is significant. The first
|
||||||
|
matching route will be used, so place more generic routes at the bottom.
|
||||||
|
|
||||||
|
Route Variables
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
As it has been shown before you can define variable parts in a route like
|
||||||
|
this::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function ($id) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
It is also possible to have more than one variable part, just make sure the
|
||||||
|
closure arguments match the names of the variable parts::
|
||||||
|
|
||||||
|
$app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
While it's not recommended, you could also do this (note the switched
|
||||||
|
arguments)::
|
||||||
|
|
||||||
|
$app->get('/blog/{postId}/{commentId}', function ($commentId, $postId) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
You can also ask for the current Request and Application objects::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function (Application $app, Request $request, $id) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Note for the Application and Request objects, Silex does the injection
|
||||||
|
based on the type hinting and not on the variable name::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function (Application $foo, Request $bar, $id) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
Route Variable Converters
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Before injecting the route variables into the controller, you can apply some
|
||||||
|
converters::
|
||||||
|
|
||||||
|
$app->get('/user/{id}', function ($id) {
|
||||||
|
// ...
|
||||||
|
})->convert('id', function ($id) { return (int) $id; });
|
||||||
|
|
||||||
|
This is useful when you want to convert route variables to objects as it
|
||||||
|
allows to reuse the conversion code across different controllers::
|
||||||
|
|
||||||
|
$userProvider = function ($id) {
|
||||||
|
return new User($id);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->get('/user/{user}', function (User $user) {
|
||||||
|
// ...
|
||||||
|
})->convert('user', $userProvider);
|
||||||
|
|
||||||
|
$app->get('/user/{user}/edit', function (User $user) {
|
||||||
|
// ...
|
||||||
|
})->convert('user', $userProvider);
|
||||||
|
|
||||||
|
The converter callback also receives the ``Request`` as its second argument::
|
||||||
|
|
||||||
|
$callback = function ($post, Request $request) {
|
||||||
|
return new Post($request->attributes->get('slug'));
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->get('/blog/{id}/{slug}', function (Post $post) {
|
||||||
|
// ...
|
||||||
|
})->convert('post', $callback);
|
||||||
|
|
||||||
|
A converter can also be defined as a service. For example, here is a user
|
||||||
|
converter based on Doctrine ObjectManager::
|
||||||
|
|
||||||
|
use Doctrine\Common\Persistence\ObjectManager;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
|
class UserConverter
|
||||||
|
{
|
||||||
|
private $om;
|
||||||
|
|
||||||
|
public function __construct(ObjectManager $om)
|
||||||
|
{
|
||||||
|
$this->om = $om;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convert($id)
|
||||||
|
{
|
||||||
|
if (null === $user = $this->om->find('User', (int) $id)) {
|
||||||
|
throw new NotFoundHttpException(sprintf('User %d does not exist', $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The service will now be registered in the application, and the
|
||||||
|
``convert()`` method will be used as converter (using the syntax
|
||||||
|
``service_name:method_name``)::
|
||||||
|
|
||||||
|
$app['converter.user'] = function () {
|
||||||
|
return new UserConverter();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->get('/user/{user}', function (User $user) {
|
||||||
|
// ...
|
||||||
|
})->convert('user', 'converter.user:convert');
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In some cases you may want to only match certain expressions. You can define
|
||||||
|
requirements using regular expressions by calling ``assert`` on the
|
||||||
|
``Controller`` object, which is returned by the routing methods.
|
||||||
|
|
||||||
|
The following will make sure the ``id`` argument is a positive integer, since
|
||||||
|
``\d+`` matches any amount of digits::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function ($id) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->assert('id', '\d+');
|
||||||
|
|
||||||
|
You can also chain these calls::
|
||||||
|
|
||||||
|
$app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->assert('postId', '\d+')
|
||||||
|
->assert('commentId', '\d+');
|
||||||
|
|
||||||
|
Conditions
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Besides restricting route matching based on the HTTP method or parameter
|
||||||
|
requirements, you can set conditions on any part of the request by calling
|
||||||
|
``when`` on the ``Controller`` object, which is returned by the routing
|
||||||
|
methods::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function ($id) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->when("request.headers.get('User-Agent') matches '/firefox/i'");
|
||||||
|
|
||||||
|
The ``when`` argument is a Symfony Expression_ , which means that you need to
|
||||||
|
add ``symfony/expression-language`` as a dependency of your project.
|
||||||
|
|
||||||
|
Default Values
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can define a default value for any route variable by calling ``value`` on
|
||||||
|
the ``Controller`` object::
|
||||||
|
|
||||||
|
$app->get('/{pageName}', function ($pageName) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->value('pageName', 'index');
|
||||||
|
|
||||||
|
This will allow matching ``/``, in which case the ``pageName`` variable will
|
||||||
|
have the value ``index``.
|
||||||
|
|
||||||
|
Named Routes
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some providers can make use of named routes. By default Silex will generate an
|
||||||
|
internal route name for you but you can give an explicit route name by calling
|
||||||
|
``bind``::
|
||||||
|
|
||||||
|
$app->get('/', function () {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->bind('homepage');
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function ($id) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
->bind('blog_post');
|
||||||
|
|
||||||
|
Controllers as Classes
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Instead of anonymous functions, you can also define your controllers as
|
||||||
|
methods. By using the ``ControllerClass::methodName`` syntax, you can tell
|
||||||
|
Silex to lazily create the controller object for you::
|
||||||
|
|
||||||
|
$app->get('/', 'Acme\\Foo::bar');
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
namespace Acme
|
||||||
|
{
|
||||||
|
class Foo
|
||||||
|
{
|
||||||
|
public function bar(Request $request, Application $app)
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
This will load the ``Acme\Foo`` class on demand, create an instance and call
|
||||||
|
the ``bar`` method to get the response. You can use ``Request`` and
|
||||||
|
``Silex\Application`` type hints to get ``$request`` and ``$app`` injected.
|
||||||
|
|
||||||
|
It is also possible to :doc:`define your controllers as services
|
||||||
|
<providers/service_controller>`.
|
||||||
|
|
||||||
|
Global Configuration
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
If a controller setting must be applied to **all** controllers (a converter, a
|
||||||
|
middleware, a requirement, or a default value), configure it on
|
||||||
|
``$app['controllers']``, which holds all application controllers::
|
||||||
|
|
||||||
|
$app['controllers']
|
||||||
|
->value('id', '1')
|
||||||
|
->assert('id', '\d+')
|
||||||
|
->requireHttps()
|
||||||
|
->method('get')
|
||||||
|
->convert('id', function () { /* ... */ })
|
||||||
|
->before(function () { /* ... */ })
|
||||||
|
->when('request.isSecure() == true')
|
||||||
|
;
|
||||||
|
|
||||||
|
These settings are applied to already registered controllers and they become
|
||||||
|
the defaults for new controllers.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The global configuration does not apply to controller providers you might
|
||||||
|
mount as they have their own global configuration (read the
|
||||||
|
:doc:`dedicated chapter<organizing_controllers>` for more information).
|
||||||
|
|
||||||
|
Error Handlers
|
||||||
|
--------------
|
||||||
|
|
||||||
|
When an exception is thrown, error handlers allow you to display a custom
|
||||||
|
error page to the user. They can also be used to do additional things, such as
|
||||||
|
logging.
|
||||||
|
|
||||||
|
To register an error handler, pass a closure to the ``error`` method which
|
||||||
|
takes an ``Exception`` argument and returns a response::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app->error(function (\Exception $e, Request $request, $code) {
|
||||||
|
return new Response('We are sorry, but something went terribly wrong.');
|
||||||
|
});
|
||||||
|
|
||||||
|
You can also check for specific errors by using the ``$code`` argument, and
|
||||||
|
handle them differently::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app->error(function (\Exception $e, Request $request, $code) {
|
||||||
|
switch ($code) {
|
||||||
|
case 404:
|
||||||
|
$message = 'The requested page could not be found.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$message = 'We are sorry, but something went terribly wrong.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response($message);
|
||||||
|
});
|
||||||
|
|
||||||
|
You can restrict an error handler to only handle some Exception classes by
|
||||||
|
setting a more specific type hint for the Closure argument::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app->error(function (\LogicException $e, Request $request, $code) {
|
||||||
|
// this handler will only handle \LogicException exceptions
|
||||||
|
// and exceptions that extend \LogicException
|
||||||
|
});
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
As Silex ensures that the Response status code is set to the most
|
||||||
|
appropriate one depending on the exception, setting the status on the
|
||||||
|
response won't work. If you want to overwrite the status code, set the
|
||||||
|
``X-Status-Code`` header::
|
||||||
|
|
||||||
|
return new Response('Error', 404 /* ignored */, array('X-Status-Code' => 200));
|
||||||
|
|
||||||
|
If you want to use a separate error handler for logging, make sure you register
|
||||||
|
it with a higher priority than response error handlers, because once a response
|
||||||
|
is returned, the following handlers are ignored.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Silex ships with a provider for Monolog_ which handles logging of errors.
|
||||||
|
Check out the *Providers* :doc:`chapter <providers/monolog>` for details.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Silex comes with a default error handler that displays a detailed error
|
||||||
|
message with the stack trace when **debug** is true, and a simple error
|
||||||
|
message otherwise. Error handlers registered via the ``error()`` method
|
||||||
|
always take precedence but you can keep the nice error messages when debug
|
||||||
|
is turned on like this::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app->error(function (\Exception $e, Request $request, $code) use ($app) {
|
||||||
|
if ($app['debug']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... logic to handle the error and return a Response
|
||||||
|
});
|
||||||
|
|
||||||
|
The error handlers are also called when you use ``abort`` to abort a request
|
||||||
|
early::
|
||||||
|
|
||||||
|
$app->get('/blog/{id}', function (Silex\Application $app, $id) use ($blogPosts) {
|
||||||
|
if (!isset($blogPosts[$id])) {
|
||||||
|
$app->abort(404, "Post $id does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(...);
|
||||||
|
});
|
||||||
|
|
||||||
|
You can convert errors to ``Exceptions``, check out the cookbook :doc:`chapter <cookbook/error_handler>` for details.
|
||||||
|
|
||||||
|
View Handlers
|
||||||
|
-------------
|
||||||
|
|
||||||
|
View Handlers allow you to intercept a controller result that is not a
|
||||||
|
``Response`` and transform it before it gets returned to the kernel.
|
||||||
|
|
||||||
|
To register a view handler, pass a callable (or string that can be resolved to a
|
||||||
|
callable) to the ``view()`` method. The callable should accept some sort of result
|
||||||
|
from the controller::
|
||||||
|
|
||||||
|
$app->view(function (array $controllerResult) use ($app) {
|
||||||
|
return $app->json($controllerResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
View Handlers also receive the ``Request`` as their second argument,
|
||||||
|
making them a good candidate for basic content negotiation::
|
||||||
|
|
||||||
|
$app->view(function (array $controllerResult, Request $request) use ($app) {
|
||||||
|
$acceptHeader = $request->headers->get('Accept');
|
||||||
|
$bestFormat = $app['negotiator']->getBestFormat($acceptHeader, array('json', 'xml'));
|
||||||
|
|
||||||
|
if ('json' === $bestFormat) {
|
||||||
|
return new JsonResponse($controllerResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('xml' === $bestFormat) {
|
||||||
|
return $app['serializer.xml']->renderResponse($controllerResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $controllerResult;
|
||||||
|
});
|
||||||
|
|
||||||
|
View Handlers will be examined in the order they are added to the application
|
||||||
|
and Silex will use type hints to determine if a view handler should be used for
|
||||||
|
the current result, continuously using the return value of the last view handler
|
||||||
|
as the input for the next.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You must ensure that Silex receives a ``Response`` or a string as the result of
|
||||||
|
the last view handler (or controller) to be run.
|
||||||
|
|
||||||
|
Redirects
|
||||||
|
---------
|
||||||
|
|
||||||
|
You can redirect to another page by returning a ``RedirectResponse`` response,
|
||||||
|
which you can create by calling the ``redirect`` method::
|
||||||
|
|
||||||
|
$app->get('/', function () use ($app) {
|
||||||
|
return $app->redirect('/hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
This will redirect from ``/`` to ``/hello``.
|
||||||
|
|
||||||
|
Forwards
|
||||||
|
--------
|
||||||
|
|
||||||
|
When you want to delegate the rendering to another controller, without a
|
||||||
|
round-trip to the browser (as for a redirect), use an internal sub-request::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
$app->get('/', function () use ($app) {
|
||||||
|
// forward to /hello
|
||||||
|
$subRequest = Request::create('/hello', 'GET');
|
||||||
|
|
||||||
|
return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
|
||||||
|
});
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
You can also generate the URI via the built-in URL generator::
|
||||||
|
|
||||||
|
$request = Request::create($app['url_generator']->generate('hello'), 'GET');
|
||||||
|
|
||||||
|
There's some more things that you need to keep in mind though. In most cases you
|
||||||
|
will want to forward some parts of the current master request to the sub-request.
|
||||||
|
That includes: Cookies, server information, session.
|
||||||
|
Read more on :doc:`how to make sub-requests <cookbook/sub_requests>`.
|
||||||
|
|
||||||
|
JSON
|
||||||
|
----
|
||||||
|
|
||||||
|
If you want to return JSON data, you can use the ``json`` helper method.
|
||||||
|
Simply pass it your data, status code and headers, and it will create a JSON
|
||||||
|
response for you::
|
||||||
|
|
||||||
|
$app->get('/users/{id}', function ($id) use ($app) {
|
||||||
|
$user = getUser($id);
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
$error = array('message' => 'The user was not found.');
|
||||||
|
|
||||||
|
return $app->json($error, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $app->json($user);
|
||||||
|
});
|
||||||
|
|
||||||
|
Streaming
|
||||||
|
---------
|
||||||
|
|
||||||
|
It's possible to stream a response, which is important in cases when you don't
|
||||||
|
want to buffer the data being sent::
|
||||||
|
|
||||||
|
$app->get('/images/{file}', function ($file) use ($app) {
|
||||||
|
if (!file_exists(__DIR__.'/images/'.$file)) {
|
||||||
|
return $app->abort(404, 'The image was not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stream = function () use ($file) {
|
||||||
|
readfile($file);
|
||||||
|
};
|
||||||
|
|
||||||
|
return $app->stream($stream, 200, array('Content-Type' => 'image/png'));
|
||||||
|
});
|
||||||
|
|
||||||
|
If you need to send chunks, make sure you call ``ob_flush`` and ``flush``
|
||||||
|
after every chunk::
|
||||||
|
|
||||||
|
$stream = function () {
|
||||||
|
$fh = fopen('http://www.example.com/', 'rb');
|
||||||
|
while (!feof($fh)) {
|
||||||
|
echo fread($fh, 1024);
|
||||||
|
ob_flush();
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
fclose($fh);
|
||||||
|
};
|
||||||
|
|
||||||
|
Sending a file
|
||||||
|
--------------
|
||||||
|
|
||||||
|
If you want to return a file, you can use the ``sendFile`` helper method.
|
||||||
|
It eases returning files that would otherwise not be publicly available. Simply
|
||||||
|
pass it your file path, status code, headers and the content disposition and it
|
||||||
|
will create a ``BinaryFileResponse`` response for you::
|
||||||
|
|
||||||
|
$app->get('/files/{path}', function ($path) use ($app) {
|
||||||
|
if (!file_exists('/base/path/' . $path)) {
|
||||||
|
$app->abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $app->sendFile('/base/path/' . $path);
|
||||||
|
});
|
||||||
|
|
||||||
|
To further customize the response before returning it, check the API doc for
|
||||||
|
`Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||||
|
<http://api.symfony.com/master/Symfony/Component/HttpFoundation/BinaryFileResponse.html>`_::
|
||||||
|
|
||||||
|
return $app
|
||||||
|
->sendFile('/base/path/' . $path)
|
||||||
|
->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'pic.jpg')
|
||||||
|
;
|
||||||
|
|
||||||
|
Traits
|
||||||
|
------
|
||||||
|
|
||||||
|
Silex comes with PHP traits that define shortcut methods.
|
||||||
|
|
||||||
|
Almost all built-in service providers have some corresponding PHP traits. To
|
||||||
|
use them, define your own Application class and include the traits you want::
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
|
||||||
|
class MyApplication extends Application
|
||||||
|
{
|
||||||
|
use Application\TwigTrait;
|
||||||
|
use Application\SecurityTrait;
|
||||||
|
use Application\FormTrait;
|
||||||
|
use Application\UrlGeneratorTrait;
|
||||||
|
use Application\SwiftmailerTrait;
|
||||||
|
use Application\MonologTrait;
|
||||||
|
use Application\TranslationTrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also define your own Route class and use some traits::
|
||||||
|
|
||||||
|
use Silex\Route;
|
||||||
|
|
||||||
|
class MyRoute extends Route
|
||||||
|
{
|
||||||
|
use Route\SecurityTrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
To use your newly defined route, override the ``$app['route_class']``
|
||||||
|
setting::
|
||||||
|
|
||||||
|
$app['route_class'] = 'MyRoute';
|
||||||
|
|
||||||
|
Read each provider chapter to learn more about the added methods.
|
||||||
|
|
||||||
|
Security
|
||||||
|
--------
|
||||||
|
|
||||||
|
Make sure to protect your application against attacks.
|
||||||
|
|
||||||
|
Escaping
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
When outputting any user input, make sure to escape it correctly to prevent
|
||||||
|
Cross-Site-Scripting attacks.
|
||||||
|
|
||||||
|
* **Escaping HTML**: PHP provides the ``htmlspecialchars`` function for this.
|
||||||
|
Silex provides a shortcut ``escape`` method::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app->get('/name', function (Request $request, Silex\Application $app) {
|
||||||
|
$name = $request->get('name');
|
||||||
|
|
||||||
|
return "You provided the name {$app->escape($name)}.";
|
||||||
|
});
|
||||||
|
|
||||||
|
If you use the Twig template engine, you should use its escaping or even
|
||||||
|
auto-escaping mechanisms. Check out the *Providers* :doc:`chapter <providers/twig>` for details.
|
||||||
|
|
||||||
|
* **Escaping JSON**: If you want to provide data in JSON format you should
|
||||||
|
use the Silex ``json`` function::
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
$app->get('/name.json', function (Request $request, Silex\Application $app) {
|
||||||
|
$name = $request->get('name');
|
||||||
|
|
||||||
|
return $app->json(array('name' => $name));
|
||||||
|
});
|
||||||
|
|
||||||
|
.. _download: http://silex.sensiolabs.org/download
|
||||||
|
.. _Composer: http://getcomposer.org/
|
||||||
|
.. _Request: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html
|
||||||
|
.. _Response: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html
|
||||||
|
.. _Monolog: https://github.com/Seldaek/monolog
|
||||||
|
.. _Expression: https://symfony.com/doc/current/book/routing.html#completely-customized-route-matching-with-conditions
|
|
@ -0,0 +1,165 @@
|
||||||
|
Webserver Configuration
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Apache
|
||||||
|
------
|
||||||
|
|
||||||
|
If you are using Apache, make sure ``mod_rewrite`` is enabled and use the
|
||||||
|
following ``.htaccess`` file:
|
||||||
|
|
||||||
|
.. code-block:: apache
|
||||||
|
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
Options -MultiViews
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
#RewriteBase /path/to/app
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^ index.php [QSA,L]
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If your site is not at the webroot level you will have to uncomment the
|
||||||
|
``RewriteBase`` statement and adjust the path to point to your directory,
|
||||||
|
relative from the webroot.
|
||||||
|
|
||||||
|
Alternatively, if you use Apache 2.2.16 or higher, you can use the
|
||||||
|
`FallbackResource directive`_ to make your .htaccess even easier:
|
||||||
|
|
||||||
|
.. code-block:: apache
|
||||||
|
|
||||||
|
FallbackResource index.php
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If your site is not at the webroot level you will have to adjust the path to
|
||||||
|
point to your directory, relative from the webroot.
|
||||||
|
|
||||||
|
nginx
|
||||||
|
-----
|
||||||
|
|
||||||
|
The **minimum configuration** to get your application running under Nginx is:
|
||||||
|
|
||||||
|
.. code-block:: nginx
|
||||||
|
|
||||||
|
server {
|
||||||
|
server_name domain.tld www.domain.tld;
|
||||||
|
root /var/www/project/web;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# try to serve file directly, fallback to front controller
|
||||||
|
try_files $uri /index.php$is_args$args;
|
||||||
|
}
|
||||||
|
|
||||||
|
# If you have 2 front controllers for dev|prod use the following line instead
|
||||||
|
# location ~ ^/(index|index_dev)\.php(/|$) {
|
||||||
|
location ~ ^/index\.php(/|$) {
|
||||||
|
# the ubuntu default
|
||||||
|
fastcgi_pass unix:/var/run/php5-fpm.sock;
|
||||||
|
# for running on centos
|
||||||
|
#fastcgi_pass unix:/var/run/php-fpm/www.sock;
|
||||||
|
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.*)$;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param HTTPS off;
|
||||||
|
|
||||||
|
# Prevents URIs that include the front controller. This will 404:
|
||||||
|
# http://domain.tld/index.php/some-path
|
||||||
|
# Enable the internal directive to disable URIs like this
|
||||||
|
# internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#return 404 for all php files as we do have a front controller
|
||||||
|
location ~ \.php$ {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log /var/log/nginx/project_error.log;
|
||||||
|
access_log /var/log/nginx/project_access.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIS
|
||||||
|
---
|
||||||
|
|
||||||
|
If you are using the Internet Information Services from Windows, you can use
|
||||||
|
this sample ``web.config`` file:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<configuration>
|
||||||
|
<system.webServer>
|
||||||
|
<defaultDocument>
|
||||||
|
<files>
|
||||||
|
<clear />
|
||||||
|
<add value="index.php" />
|
||||||
|
</files>
|
||||||
|
</defaultDocument>
|
||||||
|
<rewrite>
|
||||||
|
<rules>
|
||||||
|
<rule name="Silex Front Controller" stopProcessing="true">
|
||||||
|
<match url="^(.*)$" ignoreCase="false" />
|
||||||
|
<conditions logicalGrouping="MatchAll">
|
||||||
|
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
|
||||||
|
</conditions>
|
||||||
|
<action type="Rewrite" url="index.php" appendQueryString="true" />
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</rewrite>
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
Lighttpd
|
||||||
|
--------
|
||||||
|
|
||||||
|
If you are using lighttpd, use this sample ``simple-vhost`` as a starting
|
||||||
|
point:
|
||||||
|
|
||||||
|
.. code-block:: lighttpd
|
||||||
|
|
||||||
|
server.document-root = "/path/to/app"
|
||||||
|
|
||||||
|
url.rewrite-once = (
|
||||||
|
# configure some static files
|
||||||
|
"^/assets/.+" => "$0",
|
||||||
|
"^/favicon\.ico$" => "$0",
|
||||||
|
|
||||||
|
"^(/[^\?]*)(\?.*)?" => "/index.php$1$2"
|
||||||
|
)
|
||||||
|
|
||||||
|
.. _FallbackResource directive: http://www.adayinthelifeof.nl/2012/01/21/apaches-fallbackresource-your-new-htaccess-command/
|
||||||
|
|
||||||
|
PHP
|
||||||
|
---
|
||||||
|
|
||||||
|
PHP ships with a built-in webserver for development. This server allows you to
|
||||||
|
run silex without any configuration. However, in order to serve static files,
|
||||||
|
you'll have to make sure your front controller returns false in that case::
|
||||||
|
|
||||||
|
// web/index.php
|
||||||
|
|
||||||
|
$filename = __DIR__.preg_replace('#(\?.*)$#', '', $_SERVER['REQUEST_URI']);
|
||||||
|
if (php_sapi_name() === 'cli-server' && is_file($filename)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$app = require __DIR__.'/../src/app.php';
|
||||||
|
$app->run();
|
||||||
|
|
||||||
|
|
||||||
|
Assuming your front controller is at ``web/index.php``, you can start the
|
||||||
|
server from the command-line with this command:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ php -S localhost:8080 -t web web/index.php
|
||||||
|
|
||||||
|
Now the application should be running at ``http://localhost:8080``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This server is for development only. It is **not** recommended to use it
|
||||||
|
in production.
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
syntaxCheck="false"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Silex Test Suite">
|
||||||
|
<directory>./tests/Silex/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory>./src</directory>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?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\Api;
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for bootable service providers.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
interface BootableProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstraps the application.
|
||||||
|
*
|
||||||
|
* This method is called after all services are registered
|
||||||
|
* and should be used for "dynamic" configuration (whenever
|
||||||
|
* a service must be requested).
|
||||||
|
*
|
||||||
|
* @param Application $app
|
||||||
|
*/
|
||||||
|
public function boot(Application $app);
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?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\Api;
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
use Silex\ControllerCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for controller providers.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
interface ControllerProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns routes to connect to the given application.
|
||||||
|
*
|
||||||
|
* @param Application $app An Application instance
|
||||||
|
*
|
||||||
|
* @return ControllerCollection A ControllerCollection instance
|
||||||
|
*/
|
||||||
|
public function connect(Application $app);
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?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\Api;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Pimple\Container;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for event listener providers.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
interface EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher);
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2010-2015 Fabien Potencier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"name": "silex/api",
|
||||||
|
"description": "The Silex interfaces",
|
||||||
|
"keywords": ["microframework"],
|
||||||
|
"homepage": "http://silex.sensiolabs.org",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Igor Wiedler",
|
||||||
|
"email": "igor@wiedler.ch"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.5.9",
|
||||||
|
"pimple/pimple": "~3.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"symfony/event-dispatcher": "For EventListenerProviderInterface",
|
||||||
|
"silex/silex": "For BootableProviderInterface and ControllerProviderInterface"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": { "Silex\\Api\\": "" }
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0.x-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
|
||||||
|
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpKernel Argument Resolver for Silex.
|
||||||
|
*
|
||||||
|
* @author Romain Neutron <imprec@gmail.com>
|
||||||
|
*/
|
||||||
|
class AppArgumentValueResolver implements ArgumentValueResolverInterface
|
||||||
|
{
|
||||||
|
private $app;
|
||||||
|
|
||||||
|
public function __construct(Application $app)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function supports(Request $request, ArgumentMetadata $argument)
|
||||||
|
{
|
||||||
|
return $argument->getType() === Application::class || (null !== $argument->getType() && in_array(Application::class, class_parents($argument->getType()), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function resolve(Request $request, ArgumentMetadata $argument)
|
||||||
|
{
|
||||||
|
yield $this->app;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,506 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
use Symfony\Component\HttpKernel\TerminableInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Silex\Api\BootableProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Silex\Api\ControllerProviderInterface;
|
||||||
|
use Silex\Provider\ExceptionHandlerServiceProvider;
|
||||||
|
use Silex\Provider\RoutingServiceProvider;
|
||||||
|
use Silex\Provider\HttpKernelServiceProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Silex framework class.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Application extends Container implements HttpKernelInterface, TerminableInterface
|
||||||
|
{
|
||||||
|
const VERSION = '2.0.2';
|
||||||
|
|
||||||
|
const EARLY_EVENT = 512;
|
||||||
|
const LATE_EVENT = -512;
|
||||||
|
|
||||||
|
protected $providers = array();
|
||||||
|
protected $booted = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new Application.
|
||||||
|
*
|
||||||
|
* Objects and parameters can be passed as argument to the constructor.
|
||||||
|
*
|
||||||
|
* @param array $values The parameters or objects.
|
||||||
|
*/
|
||||||
|
public function __construct(array $values = array())
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this['request.http_port'] = 80;
|
||||||
|
$this['request.https_port'] = 443;
|
||||||
|
$this['debug'] = false;
|
||||||
|
$this['charset'] = 'UTF-8';
|
||||||
|
$this['logger'] = null;
|
||||||
|
|
||||||
|
$this->register(new HttpKernelServiceProvider());
|
||||||
|
$this->register(new RoutingServiceProvider());
|
||||||
|
$this->register(new ExceptionHandlerServiceProvider());
|
||||||
|
|
||||||
|
foreach ($values as $key => $value) {
|
||||||
|
$this[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a service provider.
|
||||||
|
*
|
||||||
|
* @param ServiceProviderInterface $provider A ServiceProviderInterface instance
|
||||||
|
* @param array $values An array of values that customizes the provider
|
||||||
|
*
|
||||||
|
* @return Application
|
||||||
|
*/
|
||||||
|
public function register(ServiceProviderInterface $provider, array $values = array())
|
||||||
|
{
|
||||||
|
$this->providers[] = $provider;
|
||||||
|
|
||||||
|
parent::register($provider, $values);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boots all service providers.
|
||||||
|
*
|
||||||
|
* This method is automatically called by handle(), but you can use it
|
||||||
|
* to boot all service providers when not handling a request.
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
if ($this->booted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->booted = true;
|
||||||
|
|
||||||
|
foreach ($this->providers as $provider) {
|
||||||
|
if ($provider instanceof EventListenerProviderInterface) {
|
||||||
|
$provider->subscribe($this, $this['dispatcher']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($provider instanceof BootableProviderInterface) {
|
||||||
|
$provider->boot($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a pattern to a callable.
|
||||||
|
*
|
||||||
|
* You can optionally specify HTTP methods that should be matched.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function match($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this['controllers']->match($pattern, $to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a GET request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function get($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this['controllers']->get($pattern, $to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a POST request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function post($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this['controllers']->post($pattern, $to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a PUT request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function put($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this['controllers']->put($pattern, $to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a DELETE request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function delete($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this['controllers']->delete($pattern, $to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps an OPTIONS request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function options($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this['controllers']->options($pattern, $to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a PATCH request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function patch($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this['controllers']->patch($pattern, $to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an event listener that listens on the specified events.
|
||||||
|
*
|
||||||
|
* @param string $eventName The event to listen on
|
||||||
|
* @param callable $callback The listener
|
||||||
|
* @param int $priority The higher this value, the earlier an event
|
||||||
|
* listener will be triggered in the chain (defaults to 0)
|
||||||
|
*/
|
||||||
|
public function on($eventName, $callback, $priority = 0)
|
||||||
|
{
|
||||||
|
if ($this->booted) {
|
||||||
|
$this['dispatcher']->addListener($eventName, $this['callback_resolver']->resolveCallback($callback), $priority);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->extend('dispatcher', function (EventDispatcherInterface $dispatcher, $app) use ($callback, $priority, $eventName) {
|
||||||
|
$dispatcher->addListener($eventName, $app['callback_resolver']->resolveCallback($callback), $priority);
|
||||||
|
|
||||||
|
return $dispatcher;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a before filter.
|
||||||
|
*
|
||||||
|
* Before filters are run before any route has been matched.
|
||||||
|
*
|
||||||
|
* @param mixed $callback Before filter callback
|
||||||
|
* @param int $priority The higher this value, the earlier an event
|
||||||
|
* listener will be triggered in the chain (defaults to 0)
|
||||||
|
*/
|
||||||
|
public function before($callback, $priority = 0)
|
||||||
|
{
|
||||||
|
$app = $this;
|
||||||
|
|
||||||
|
$this->on(KernelEvents::REQUEST, function (GetResponseEvent $event) use ($callback, $app) {
|
||||||
|
if (!$event->isMasterRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $app);
|
||||||
|
|
||||||
|
if ($ret instanceof Response) {
|
||||||
|
$event->setResponse($ret);
|
||||||
|
}
|
||||||
|
}, $priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an after filter.
|
||||||
|
*
|
||||||
|
* After filters are run after the controller has been executed.
|
||||||
|
*
|
||||||
|
* @param mixed $callback After filter callback
|
||||||
|
* @param int $priority The higher this value, the earlier an event
|
||||||
|
* listener will be triggered in the chain (defaults to 0)
|
||||||
|
*/
|
||||||
|
public function after($callback, $priority = 0)
|
||||||
|
{
|
||||||
|
$app = $this;
|
||||||
|
|
||||||
|
$this->on(KernelEvents::RESPONSE, function (FilterResponseEvent $event) use ($callback, $app) {
|
||||||
|
if (!$event->isMasterRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $event->getResponse(), $app);
|
||||||
|
if ($response instanceof Response) {
|
||||||
|
$event->setResponse($response);
|
||||||
|
} elseif (null !== $response) {
|
||||||
|
throw new \RuntimeException('An after middleware returned an invalid response value. Must return null or an instance of Response.');
|
||||||
|
}
|
||||||
|
}, $priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a finish filter.
|
||||||
|
*
|
||||||
|
* Finish filters are run after the response has been sent.
|
||||||
|
*
|
||||||
|
* @param mixed $callback Finish filter callback
|
||||||
|
* @param int $priority The higher this value, the earlier an event
|
||||||
|
* listener will be triggered in the chain (defaults to 0)
|
||||||
|
*/
|
||||||
|
public function finish($callback, $priority = 0)
|
||||||
|
{
|
||||||
|
$app = $this;
|
||||||
|
|
||||||
|
$this->on(KernelEvents::TERMINATE, function (PostResponseEvent $event) use ($callback, $app) {
|
||||||
|
call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $event->getResponse(), $app);
|
||||||
|
}, $priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts the current request by sending a proper HTTP error.
|
||||||
|
*
|
||||||
|
* @param int $statusCode The HTTP status code
|
||||||
|
* @param string $message The status message
|
||||||
|
* @param array $headers An array of HTTP headers
|
||||||
|
*/
|
||||||
|
public function abort($statusCode, $message = '', array $headers = array())
|
||||||
|
{
|
||||||
|
throw new HttpException($statusCode, $message, null, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an error handler.
|
||||||
|
*
|
||||||
|
* Error handlers are simple callables which take a single Exception
|
||||||
|
* as an argument. If a controller throws an exception, an error handler
|
||||||
|
* can return a specific response.
|
||||||
|
*
|
||||||
|
* When an exception occurs, all handlers will be called, until one returns
|
||||||
|
* something (a string or a Response object), at which point that will be
|
||||||
|
* returned to the client.
|
||||||
|
*
|
||||||
|
* For this reason you should add logging handlers before output handlers.
|
||||||
|
*
|
||||||
|
* @param mixed $callback Error handler callback, takes an Exception argument
|
||||||
|
* @param int $priority The higher this value, the earlier an event
|
||||||
|
* listener will be triggered in the chain (defaults to -8)
|
||||||
|
*/
|
||||||
|
public function error($callback, $priority = -8)
|
||||||
|
{
|
||||||
|
$this->on(KernelEvents::EXCEPTION, new ExceptionListenerWrapper($this, $callback), $priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a view handler.
|
||||||
|
*
|
||||||
|
* View handlers are simple callables which take a controller result and the
|
||||||
|
* request as arguments, whenever a controller returns a value that is not
|
||||||
|
* an instance of Response. When this occurs, all suitable handlers will be
|
||||||
|
* called, until one returns a Response object.
|
||||||
|
*
|
||||||
|
* @param mixed $callback View handler callback
|
||||||
|
* @param int $priority The higher this value, the earlier an event
|
||||||
|
* listener will be triggered in the chain (defaults to 0)
|
||||||
|
*/
|
||||||
|
public function view($callback, $priority = 0)
|
||||||
|
{
|
||||||
|
$this->on(KernelEvents::VIEW, new ViewListenerWrapper($this, $callback), $priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the controller collection.
|
||||||
|
*/
|
||||||
|
public function flush()
|
||||||
|
{
|
||||||
|
$this['routes']->addCollection($this['controllers']->flush());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects the user to another URL.
|
||||||
|
*
|
||||||
|
* @param string $url The URL to redirect to
|
||||||
|
* @param int $status The status code (302 by default)
|
||||||
|
*
|
||||||
|
* @return RedirectResponse
|
||||||
|
*/
|
||||||
|
public function redirect($url, $status = 302)
|
||||||
|
{
|
||||||
|
return new RedirectResponse($url, $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a streaming response.
|
||||||
|
*
|
||||||
|
* @param mixed $callback A valid PHP callback
|
||||||
|
* @param int $status The response status code
|
||||||
|
* @param array $headers An array of response headers
|
||||||
|
*
|
||||||
|
* @return StreamedResponse
|
||||||
|
*/
|
||||||
|
public function stream($callback = null, $status = 200, array $headers = array())
|
||||||
|
{
|
||||||
|
return new StreamedResponse($callback, $status, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a text for HTML.
|
||||||
|
*
|
||||||
|
* @param string $text The input text to be escaped
|
||||||
|
* @param int $flags The flags (@see htmlspecialchars)
|
||||||
|
* @param string $charset The charset
|
||||||
|
* @param bool $doubleEncode Whether to try to avoid double escaping or not
|
||||||
|
*
|
||||||
|
* @return string Escaped text
|
||||||
|
*/
|
||||||
|
public function escape($text, $flags = ENT_COMPAT, $charset = null, $doubleEncode = true)
|
||||||
|
{
|
||||||
|
return htmlspecialchars($text, $flags, $charset ?: $this['charset'], $doubleEncode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert some data into a JSON response.
|
||||||
|
*
|
||||||
|
* @param mixed $data The response data
|
||||||
|
* @param int $status The response status code
|
||||||
|
* @param array $headers An array of response headers
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function json($data = array(), $status = 200, array $headers = array())
|
||||||
|
{
|
||||||
|
return new JsonResponse($data, $status, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a file.
|
||||||
|
*
|
||||||
|
* @param \SplFileInfo|string $file The file to stream
|
||||||
|
* @param int $status The response status code
|
||||||
|
* @param array $headers An array of response headers
|
||||||
|
* @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename
|
||||||
|
*
|
||||||
|
* @return BinaryFileResponse
|
||||||
|
*/
|
||||||
|
public function sendFile($file, $status = 200, array $headers = array(), $contentDisposition = null)
|
||||||
|
{
|
||||||
|
return new BinaryFileResponse($file, $status, $headers, true, $contentDisposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mounts controllers under the given route prefix.
|
||||||
|
*
|
||||||
|
* @param string $prefix The route prefix
|
||||||
|
* @param ControllerCollection|callable|ControllerProviderInterface $controllers A ControllerCollection, a callable, or a ControllerProviderInterface instance
|
||||||
|
*
|
||||||
|
* @return Application
|
||||||
|
*
|
||||||
|
* @throws \LogicException
|
||||||
|
*/
|
||||||
|
public function mount($prefix, $controllers)
|
||||||
|
{
|
||||||
|
if ($controllers instanceof ControllerProviderInterface) {
|
||||||
|
$connectedControllers = $controllers->connect($this);
|
||||||
|
|
||||||
|
if (!$connectedControllers instanceof ControllerCollection) {
|
||||||
|
throw new \LogicException(sprintf('The method "%s::connect" must return a "ControllerCollection" instance. Got: "%s"', get_class($controllers), is_object($connectedControllers) ? get_class($connectedControllers) : gettype($connectedControllers)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$controllers = $connectedControllers;
|
||||||
|
} elseif (!$controllers instanceof ControllerCollection && !is_callable($controllers)) {
|
||||||
|
throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this['controllers']->mount($prefix, $controllers);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the request and delivers the response.
|
||||||
|
*
|
||||||
|
* @param Request|null $request Request to process
|
||||||
|
*/
|
||||||
|
public function run(Request $request = null)
|
||||||
|
{
|
||||||
|
if (null === $request) {
|
||||||
|
$request = Request::createFromGlobals();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->handle($request);
|
||||||
|
$response->send();
|
||||||
|
$this->terminate($request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* If you call this method directly instead of run(), you must call the
|
||||||
|
* terminate() method yourself if you want the finish filters to be run.
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
|
||||||
|
{
|
||||||
|
if (!$this->booted) {
|
||||||
|
$this->boot();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->flush();
|
||||||
|
|
||||||
|
return $this['kernel']->handle($request, $type, $catch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function terminate(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$this['kernel']->terminate($request, $response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?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\Application;
|
||||||
|
|
||||||
|
use Symfony\Component\Form;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
|
use Symfony\Component\Form\FormBuilder;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver\FormTypeInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form trait.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author David Berlioz <berliozdavid@gmail.com>
|
||||||
|
*/
|
||||||
|
trait FormTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates and returns a form builder instance.
|
||||||
|
*
|
||||||
|
* @param mixed $data The initial data for the form
|
||||||
|
* @param array $options Options for the form
|
||||||
|
* @param string|FormTypeInterface $type Type of the form
|
||||||
|
*
|
||||||
|
* @return FormBuilder
|
||||||
|
*/
|
||||||
|
public function form($data = null, array $options = array(), $type = null)
|
||||||
|
{
|
||||||
|
return $this['form.factory']->createBuilder($type ?: FormType::class, $data, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a named form builder instance.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param mixed $data The initial data for the form
|
||||||
|
* @param array $options Options for the form
|
||||||
|
* @param string|FormTypeInterface $type Type of the form
|
||||||
|
*
|
||||||
|
* @return FormBuilder
|
||||||
|
*/
|
||||||
|
public function namedForm($name, $data = null, array $options = array(), $type = null)
|
||||||
|
{
|
||||||
|
return $this['form.factory']->createNamedBuilder($name, $type ?: FormType::class, $data, $options);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?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\Application;
|
||||||
|
|
||||||
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monolog trait.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
trait MonologTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Adds a log record.
|
||||||
|
*
|
||||||
|
* @param string $message The log message
|
||||||
|
* @param array $context The log context
|
||||||
|
* @param int $level The logging level
|
||||||
|
*
|
||||||
|
* @return bool Whether the record has been processed
|
||||||
|
*/
|
||||||
|
public function log($message, array $context = array(), $level = Logger::INFO)
|
||||||
|
{
|
||||||
|
return $this['monolog']->addRecord($level, $message, $context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?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\Application;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security trait.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
trait SecurityTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Encodes the raw password.
|
||||||
|
*
|
||||||
|
* @param UserInterface $user A UserInterface instance
|
||||||
|
* @param string $password The password to encode
|
||||||
|
*
|
||||||
|
* @return string The encoded password
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException when no password encoder could be found for the user
|
||||||
|
*/
|
||||||
|
public function encodePassword(UserInterface $user, $password)
|
||||||
|
{
|
||||||
|
return $this['security.encoder_factory']->getEncoder($user)->encodePassword($password, $user->getSalt());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the attributes are granted against the current authentication token and optionally supplied object.
|
||||||
|
*
|
||||||
|
* @param mixed $attributes
|
||||||
|
* @param mixed $object
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token.
|
||||||
|
*/
|
||||||
|
public function isGranted($attributes, $object = null)
|
||||||
|
{
|
||||||
|
return $this['security.authorization_checker']->isGranted($attributes, $object);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?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\Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swiftmailer trait.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
trait SwiftmailerTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sends an email.
|
||||||
|
*
|
||||||
|
* @param \Swift_Message $message A \Swift_Message instance
|
||||||
|
* @param array $failedRecipients An array of failures by-reference
|
||||||
|
*
|
||||||
|
* @return int The number of sent messages
|
||||||
|
*/
|
||||||
|
public function mail(\Swift_Message $message, &$failedRecipients = null)
|
||||||
|
{
|
||||||
|
return $this['mailer']->send($message, $failedRecipients);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?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\Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation trait.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
trait TranslationTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Translates the given message.
|
||||||
|
*
|
||||||
|
* @param string $id The message id
|
||||||
|
* @param array $parameters An array of parameters for the message
|
||||||
|
* @param string $domain The domain for the message
|
||||||
|
* @param string $locale The locale
|
||||||
|
*
|
||||||
|
* @return string The translated string
|
||||||
|
*/
|
||||||
|
public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null)
|
||||||
|
{
|
||||||
|
return $this['translator']->trans($id, $parameters, $domain, $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given choice message by choosing a translation according to a number.
|
||||||
|
*
|
||||||
|
* @param string $id The message id
|
||||||
|
* @param int $number The number to use to find the indice of the message
|
||||||
|
* @param array $parameters An array of parameters for the message
|
||||||
|
* @param string $domain The domain for the message
|
||||||
|
* @param string $locale The locale
|
||||||
|
*
|
||||||
|
* @return string The translated string
|
||||||
|
*/
|
||||||
|
public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null)
|
||||||
|
{
|
||||||
|
return $this['translator']->transChoice($id, $number, $parameters, $domain, $locale);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?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\Application;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Twig trait.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
trait TwigTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Renders a view and returns a Response.
|
||||||
|
*
|
||||||
|
* To stream a view, pass an instance of StreamedResponse as a third argument.
|
||||||
|
*
|
||||||
|
* @param string $view The view name
|
||||||
|
* @param array $parameters An array of parameters to pass to the view
|
||||||
|
* @param Response $response A Response instance
|
||||||
|
*
|
||||||
|
* @return Response A Response instance
|
||||||
|
*/
|
||||||
|
public function render($view, array $parameters = array(), Response $response = null)
|
||||||
|
{
|
||||||
|
$twig = $this['twig'];
|
||||||
|
|
||||||
|
if ($response instanceof StreamedResponse) {
|
||||||
|
$response->setCallback(function () use ($twig, $view, $parameters) {
|
||||||
|
$twig->display($view, $parameters);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (null === $response) {
|
||||||
|
$response = new Response();
|
||||||
|
}
|
||||||
|
$response->setContent($twig->render($view, $parameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a view.
|
||||||
|
*
|
||||||
|
* @param string $view The view name
|
||||||
|
* @param array $parameters An array of parameters to pass to the view
|
||||||
|
*
|
||||||
|
* @return string The rendered view
|
||||||
|
*/
|
||||||
|
public function renderView($view, array $parameters = array())
|
||||||
|
{
|
||||||
|
return $this['twig']->render($view, $parameters);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?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\Application;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UrlGenerator trait.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
trait UrlGeneratorTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Generates a path from the given parameters.
|
||||||
|
*
|
||||||
|
* @param string $route The name of the route
|
||||||
|
* @param mixed $parameters An array of parameters
|
||||||
|
*
|
||||||
|
* @return string The generated path
|
||||||
|
*/
|
||||||
|
public function path($route, $parameters = array())
|
||||||
|
{
|
||||||
|
return $this['url_generator']->generate($route, $parameters, UrlGeneratorInterface::ABSOLUTE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an absolute URL from the given parameters.
|
||||||
|
*
|
||||||
|
* @param string $route The name of the route
|
||||||
|
* @param mixed $parameters An array of parameters
|
||||||
|
*
|
||||||
|
* @return string The generated URL
|
||||||
|
*/
|
||||||
|
public function url($route, $parameters = array())
|
||||||
|
{
|
||||||
|
return $this['url_generator']->generate($route, $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
|
||||||
|
class CallbackResolver
|
||||||
|
{
|
||||||
|
const SERVICE_PATTERN = "/[A-Za-z0-9\._\-]+:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/";
|
||||||
|
|
||||||
|
private $app;
|
||||||
|
|
||||||
|
public function __construct(Container $app)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the string is a valid service method representation.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isValid($name)
|
||||||
|
{
|
||||||
|
return is_string($name) && (preg_match(static::SERVICE_PATTERN, $name) || isset($this->app[$name]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a callable given its string representation.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return callable
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException In case the method does not exist.
|
||||||
|
*/
|
||||||
|
public function convertCallback($name)
|
||||||
|
{
|
||||||
|
if (preg_match(static::SERVICE_PATTERN, $name)) {
|
||||||
|
list($service, $method) = explode(':', $name, 2);
|
||||||
|
$callback = array($this->app[$service], $method);
|
||||||
|
} else {
|
||||||
|
$service = $name;
|
||||||
|
$callback = $this->app[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_callable($callback)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Service "%s" is not callable.', $service));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a callable given its string representation if it is a valid service method.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return string|callable A callable value or the string passed in
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException In case the method does not exist.
|
||||||
|
*/
|
||||||
|
public function resolveCallback($name)
|
||||||
|
{
|
||||||
|
return $this->isValid($name) ? $this->convertCallback($name) : $name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Silex\Exception\ControllerFrozenException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for a controller, mapped to a route.
|
||||||
|
*
|
||||||
|
* __call() forwards method-calls to Route, but returns instance of Controller
|
||||||
|
* listing Route's methods below, so that IDEs know they are valid
|
||||||
|
*
|
||||||
|
* @method Controller assert(string $variable, string $regexp)
|
||||||
|
* @method Controller value(string $variable, mixed $default)
|
||||||
|
* @method Controller convert(string $variable, mixed $callback)
|
||||||
|
* @method Controller method(string $method)
|
||||||
|
* @method Controller requireHttp()
|
||||||
|
* @method Controller requireHttps()
|
||||||
|
* @method Controller before(mixed $callback)
|
||||||
|
* @method Controller after(mixed $callback)
|
||||||
|
* @method Controller when(string $condition)
|
||||||
|
*
|
||||||
|
* @author Igor Wiedler <igor@wiedler.ch>
|
||||||
|
*/
|
||||||
|
class Controller
|
||||||
|
{
|
||||||
|
private $route;
|
||||||
|
private $routeName;
|
||||||
|
private $isFrozen = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param Route $route
|
||||||
|
*/
|
||||||
|
public function __construct(Route $route)
|
||||||
|
{
|
||||||
|
$this->route = $route;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the controller's route.
|
||||||
|
*
|
||||||
|
* @return Route
|
||||||
|
*/
|
||||||
|
public function getRoute()
|
||||||
|
{
|
||||||
|
return $this->route;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the controller's route name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getRouteName()
|
||||||
|
{
|
||||||
|
return $this->routeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the controller's route.
|
||||||
|
*
|
||||||
|
* @param string $routeName
|
||||||
|
*
|
||||||
|
* @return Controller $this The current Controller instance
|
||||||
|
*/
|
||||||
|
public function bind($routeName)
|
||||||
|
{
|
||||||
|
if ($this->isFrozen) {
|
||||||
|
throw new ControllerFrozenException(sprintf('Calling %s on frozen %s instance.', __METHOD__, __CLASS__));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->routeName = $routeName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call($method, $arguments)
|
||||||
|
{
|
||||||
|
if (!method_exists($this->route, $method)) {
|
||||||
|
throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', get_class($this->route), $method));
|
||||||
|
}
|
||||||
|
|
||||||
|
call_user_func_array(array($this->route, $method), $arguments);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freezes the controller.
|
||||||
|
*
|
||||||
|
* Once the controller is frozen, you can no longer change the route name
|
||||||
|
*/
|
||||||
|
public function freeze()
|
||||||
|
{
|
||||||
|
$this->isFrozen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateRouteName($prefix)
|
||||||
|
{
|
||||||
|
$methods = implode('_', $this->route->getMethods()).'_';
|
||||||
|
|
||||||
|
$routeName = $methods.$prefix.$this->route->getPath();
|
||||||
|
$routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName);
|
||||||
|
$routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);
|
||||||
|
|
||||||
|
// Collapse consecutive underscores down into a single underscore.
|
||||||
|
$routeName = preg_replace('/_+/', '_', $routeName);
|
||||||
|
|
||||||
|
return $routeName;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds Silex controllers.
|
||||||
|
*
|
||||||
|
* It acts as a staging area for routes. You are able to set the route name
|
||||||
|
* until flush() is called, at which point all controllers are frozen and
|
||||||
|
* converted to a RouteCollection.
|
||||||
|
*
|
||||||
|
* __call() forwards method-calls to Route, but returns instance of ControllerCollection
|
||||||
|
* listing Route's methods below, so that IDEs know they are valid
|
||||||
|
*
|
||||||
|
* @method ControllerCollection assert(string $variable, string $regexp)
|
||||||
|
* @method ControllerCollection value(string $variable, mixed $default)
|
||||||
|
* @method ControllerCollection convert(string $variable, mixed $callback)
|
||||||
|
* @method ControllerCollection method(string $method)
|
||||||
|
* @method ControllerCollection requireHttp()
|
||||||
|
* @method ControllerCollection requireHttps()
|
||||||
|
* @method ControllerCollection before(mixed $callback)
|
||||||
|
* @method ControllerCollection after(mixed $callback)
|
||||||
|
* @method ControllerCollection when(string $condition)
|
||||||
|
*
|
||||||
|
* @author Igor Wiedler <igor@wiedler.ch>
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ControllerCollection
|
||||||
|
{
|
||||||
|
protected $controllers = array();
|
||||||
|
protected $defaultRoute;
|
||||||
|
protected $defaultController;
|
||||||
|
protected $prefix;
|
||||||
|
protected $routesFactory;
|
||||||
|
protected $controllersFactory;
|
||||||
|
|
||||||
|
public function __construct(Route $defaultRoute, RouteCollection $routesFactory = null, $controllersFactory = null)
|
||||||
|
{
|
||||||
|
$this->defaultRoute = $defaultRoute;
|
||||||
|
$this->routesFactory = $routesFactory;
|
||||||
|
$this->controllersFactory = $controllersFactory;
|
||||||
|
$this->defaultController = function (Request $request) {
|
||||||
|
throw new \LogicException(sprintf('The "%s" route must have code to run when it matches.', $request->attributes->get('_route')));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mounts controllers under the given route prefix.
|
||||||
|
*
|
||||||
|
* @param string $prefix The route prefix
|
||||||
|
* @param ControllerCollection|callable $controllers A ControllerCollection instance or a callable for defining routes
|
||||||
|
*
|
||||||
|
* @throws \LogicException
|
||||||
|
*/
|
||||||
|
public function mount($prefix, $controllers)
|
||||||
|
{
|
||||||
|
if (is_callable($controllers)) {
|
||||||
|
$collection = $this->controllersFactory ? call_user_func($this->controllersFactory) : new static(new Route(), new RouteCollection());
|
||||||
|
call_user_func($controllers, $collection);
|
||||||
|
$controllers = $collection;
|
||||||
|
} elseif (!$controllers instanceof self) {
|
||||||
|
throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance or callable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$controllers->prefix = $prefix;
|
||||||
|
|
||||||
|
$this->controllers[] = $controllers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a pattern to a callable.
|
||||||
|
*
|
||||||
|
* You can optionally specify HTTP methods that should be matched.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function match($pattern, $to = null)
|
||||||
|
{
|
||||||
|
$route = clone $this->defaultRoute;
|
||||||
|
$route->setPath($pattern);
|
||||||
|
$this->controllers[] = $controller = new Controller($route);
|
||||||
|
$route->setDefault('_controller', null === $to ? $this->defaultController : $to);
|
||||||
|
|
||||||
|
return $controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a GET request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function get($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this->match($pattern, $to)->method('GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a POST request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function post($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this->match($pattern, $to)->method('POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a PUT request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function put($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this->match($pattern, $to)->method('PUT');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a DELETE request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function delete($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this->match($pattern, $to)->method('DELETE');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps an OPTIONS request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function options($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this->match($pattern, $to)->method('OPTIONS');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a PATCH request to a callable.
|
||||||
|
*
|
||||||
|
* @param string $pattern Matched route pattern
|
||||||
|
* @param mixed $to Callback that returns the response when matched
|
||||||
|
*
|
||||||
|
* @return Controller
|
||||||
|
*/
|
||||||
|
public function patch($pattern, $to = null)
|
||||||
|
{
|
||||||
|
return $this->match($pattern, $to)->method('PATCH');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call($method, $arguments)
|
||||||
|
{
|
||||||
|
if (!method_exists($this->defaultRoute, $method)) {
|
||||||
|
throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', get_class($this->defaultRoute), $method));
|
||||||
|
}
|
||||||
|
|
||||||
|
call_user_func_array(array($this->defaultRoute, $method), $arguments);
|
||||||
|
|
||||||
|
foreach ($this->controllers as $controller) {
|
||||||
|
call_user_func_array(array($controller, $method), $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists and freezes staged controllers.
|
||||||
|
*
|
||||||
|
* @return RouteCollection A RouteCollection instance
|
||||||
|
*/
|
||||||
|
public function flush()
|
||||||
|
{
|
||||||
|
if (null === $this->routesFactory) {
|
||||||
|
$routes = new RouteCollection();
|
||||||
|
} else {
|
||||||
|
$routes = $this->routesFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->doFlush('', $routes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function doFlush($prefix, RouteCollection $routes)
|
||||||
|
{
|
||||||
|
if ($prefix !== '') {
|
||||||
|
$prefix = '/'.trim(trim($prefix), '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->controllers as $controller) {
|
||||||
|
if ($controller instanceof Controller) {
|
||||||
|
$controller->getRoute()->setPath($prefix.$controller->getRoute()->getPath());
|
||||||
|
if (!$name = $controller->getRouteName()) {
|
||||||
|
$name = $base = $controller->generateRouteName('');
|
||||||
|
$i = 0;
|
||||||
|
while ($routes->get($name)) {
|
||||||
|
$name = $base.'_'.++$i;
|
||||||
|
}
|
||||||
|
$controller->bind($name);
|
||||||
|
}
|
||||||
|
$routes->add($name, $controller->getRoute());
|
||||||
|
$controller->freeze();
|
||||||
|
} else {
|
||||||
|
$controller->doFlush($prefix.$controller->prefix, $routes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->controllers = array();
|
||||||
|
|
||||||
|
return $routes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds Application as a valid argument for controllers.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* @deprecated This class can be dropped once Symfony 3.0 is not supported anymore.
|
||||||
|
*/
|
||||||
|
class ControllerResolver extends BaseControllerResolver
|
||||||
|
{
|
||||||
|
protected $app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param Application $app An Application instance
|
||||||
|
* @param LoggerInterface $logger A LoggerInterface instance
|
||||||
|
*/
|
||||||
|
public function __construct(Application $app, LoggerInterface $logger = null)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
|
||||||
|
parent::__construct($logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function doGetArguments(Request $request, $controller, array $parameters)
|
||||||
|
{
|
||||||
|
foreach ($parameters as $param) {
|
||||||
|
if ($param->getClass() && $param->getClass()->isInstance($this->app)) {
|
||||||
|
$request->attributes->set($param->getName(), $this->app);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::doGetArguments($request, $controller, $parameters);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?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\EventListener;
|
||||||
|
|
||||||
|
use Silex\CallbackResolver;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles converters.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ConverterListener implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
protected $routes;
|
||||||
|
protected $callbackResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param RouteCollection $routes A RouteCollection instance
|
||||||
|
* @param CallbackResolver $callbackResolver A CallbackResolver instance
|
||||||
|
*/
|
||||||
|
public function __construct(RouteCollection $routes, CallbackResolver $callbackResolver)
|
||||||
|
{
|
||||||
|
$this->routes = $routes;
|
||||||
|
$this->callbackResolver = $callbackResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles converters.
|
||||||
|
*
|
||||||
|
* @param FilterControllerEvent $event The event to handle
|
||||||
|
*/
|
||||||
|
public function onKernelController(FilterControllerEvent $event)
|
||||||
|
{
|
||||||
|
$request = $event->getRequest();
|
||||||
|
$route = $this->routes->get($request->attributes->get('_route'));
|
||||||
|
if ($route && $converters = $route->getOption('_converters')) {
|
||||||
|
foreach ($converters as $name => $callback) {
|
||||||
|
$callback = $this->callbackResolver->resolveCallback($callback);
|
||||||
|
|
||||||
|
$request->attributes->set($name, call_user_func($callback, $request->attributes->get($name), $request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
KernelEvents::CONTROLLER => 'onKernelController',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
<?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\EventListener;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\LogLevel;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs request, response, and exceptions.
|
||||||
|
*/
|
||||||
|
class LogListener implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
protected $logger;
|
||||||
|
protected $exceptionLogFilter;
|
||||||
|
|
||||||
|
public function __construct(LoggerInterface $logger, $exceptionLogFilter = null)
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
if (null === $exceptionLogFilter) {
|
||||||
|
$exceptionLogFilter = function (\Exception $e) {
|
||||||
|
if ($e instanceof HttpExceptionInterface && $e->getStatusCode() < 500) {
|
||||||
|
return LogLevel::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LogLevel::CRITICAL;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->exceptionLogFilter = $exceptionLogFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs master requests on event KernelEvents::REQUEST.
|
||||||
|
*
|
||||||
|
* @param GetResponseEvent $event
|
||||||
|
*/
|
||||||
|
public function onKernelRequest(GetResponseEvent $event)
|
||||||
|
{
|
||||||
|
if (!$event->isMasterRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logRequest($event->getRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs master response on event KernelEvents::RESPONSE.
|
||||||
|
*
|
||||||
|
* @param FilterResponseEvent $event
|
||||||
|
*/
|
||||||
|
public function onKernelResponse(FilterResponseEvent $event)
|
||||||
|
{
|
||||||
|
if (!$event->isMasterRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logResponse($event->getResponse());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs uncaught exceptions on event KernelEvents::EXCEPTION.
|
||||||
|
*
|
||||||
|
* @param GetResponseForExceptionEvent $event
|
||||||
|
*/
|
||||||
|
public function onKernelException(GetResponseForExceptionEvent $event)
|
||||||
|
{
|
||||||
|
$this->logException($event->getException());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a request.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
*/
|
||||||
|
protected function logRequest(Request $request)
|
||||||
|
{
|
||||||
|
$this->logger->log(LogLevel::DEBUG, '> '.$request->getMethod().' '.$request->getRequestUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a response.
|
||||||
|
*
|
||||||
|
* @param Response $response
|
||||||
|
*/
|
||||||
|
protected function logResponse(Response $response)
|
||||||
|
{
|
||||||
|
$message = '< '.$response->getStatusCode();
|
||||||
|
|
||||||
|
if ($response instanceof RedirectResponse) {
|
||||||
|
$message .= ' '.$response->getTargetUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log(LogLevel::DEBUG, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an exception.
|
||||||
|
*/
|
||||||
|
protected function logException(\Exception $e)
|
||||||
|
{
|
||||||
|
$this->logger->log(call_user_func($this->exceptionLogFilter, $e), sprintf('%s: %s (uncaught exception) at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), array('exception' => $e));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
KernelEvents::REQUEST => array('onKernelRequest', 0),
|
||||||
|
KernelEvents::RESPONSE => array('onKernelResponse', 0),
|
||||||
|
/*
|
||||||
|
* Priority -4 is used to come after those from SecurityServiceProvider (0)
|
||||||
|
* but before the error handlers added with Silex\Application::error (defaults to -8)
|
||||||
|
*/
|
||||||
|
KernelEvents::EXCEPTION => array('onKernelException', -4),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?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\EventListener;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Silex\Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the route middlewares.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class MiddlewareListener implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
protected $app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param Application $app An Application instance
|
||||||
|
*/
|
||||||
|
public function __construct(Application $app)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs before filters.
|
||||||
|
*
|
||||||
|
* @param GetResponseEvent $event The event to handle
|
||||||
|
*/
|
||||||
|
public function onKernelRequest(GetResponseEvent $event)
|
||||||
|
{
|
||||||
|
$request = $event->getRequest();
|
||||||
|
$routeName = $request->attributes->get('_route');
|
||||||
|
if (!$route = $this->app['routes']->get($routeName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((array) $route->getOption('_before_middlewares') as $callback) {
|
||||||
|
$ret = call_user_func($this->app['callback_resolver']->resolveCallback($callback), $request, $this->app);
|
||||||
|
if ($ret instanceof Response) {
|
||||||
|
$event->setResponse($ret);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} elseif (null !== $ret) {
|
||||||
|
throw new \RuntimeException(sprintf('A before middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs after filters.
|
||||||
|
*
|
||||||
|
* @param FilterResponseEvent $event The event to handle
|
||||||
|
*/
|
||||||
|
public function onKernelResponse(FilterResponseEvent $event)
|
||||||
|
{
|
||||||
|
$request = $event->getRequest();
|
||||||
|
$routeName = $request->attributes->get('_route');
|
||||||
|
if (!$route = $this->app['routes']->get($routeName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((array) $route->getOption('_after_middlewares') as $callback) {
|
||||||
|
$response = call_user_func($this->app['callback_resolver']->resolveCallback($callback), $request, $event->getResponse(), $this->app);
|
||||||
|
if ($response instanceof Response) {
|
||||||
|
$event->setResponse($response);
|
||||||
|
} elseif (null !== $response) {
|
||||||
|
throw new \RuntimeException(sprintf('An after middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
// this must be executed after the late events defined with before() (and their priority is -512)
|
||||||
|
KernelEvents::REQUEST => array('onKernelRequest', -1024),
|
||||||
|
KernelEvents::RESPONSE => array('onKernelResponse', 128),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?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\EventListener;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts string responses to proper Response instances.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class StringToResponseListener implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handles string responses.
|
||||||
|
*
|
||||||
|
* @param GetResponseForControllerResultEvent $event The event to handle
|
||||||
|
*/
|
||||||
|
public function onKernelView(GetResponseForControllerResultEvent $event)
|
||||||
|
{
|
||||||
|
$response = $event->getControllerResult();
|
||||||
|
|
||||||
|
if (!(
|
||||||
|
null === $response
|
||||||
|
|| is_array($response)
|
||||||
|
|| $response instanceof Response
|
||||||
|
|| (is_object($response) && !method_exists($response, '__toString'))
|
||||||
|
)) {
|
||||||
|
$event->setResponse(new Response((string) $response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
KernelEvents::VIEW => array('onKernelView', -10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?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\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception, is thrown when a frozen controller is modified.
|
||||||
|
*
|
||||||
|
* @author Igor Wiedler <igor@wiedler.ch>
|
||||||
|
*/
|
||||||
|
class ControllerFrozenException extends \RuntimeException
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler;
|
||||||
|
use Symfony\Component\Debug\Exception\FlattenException;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default exception handler.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ExceptionHandler implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
protected $debug;
|
||||||
|
|
||||||
|
public function __construct($debug)
|
||||||
|
{
|
||||||
|
$this->debug = $debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSilexError(GetResponseForExceptionEvent $event)
|
||||||
|
{
|
||||||
|
$handler = new DebugExceptionHandler($this->debug);
|
||||||
|
|
||||||
|
$exception = $event->getException();
|
||||||
|
if (!$exception instanceof FlattenException) {
|
||||||
|
$exception = FlattenException::create($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = Response::create($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders())->setCharset(ini_get('default_charset'));
|
||||||
|
|
||||||
|
$event->setResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(KernelEvents::EXCEPTION => array('onSilexError', -255));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps exception listeners.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ExceptionListenerWrapper
|
||||||
|
{
|
||||||
|
protected $app;
|
||||||
|
protected $callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param Application $app An Application instance
|
||||||
|
* @param callable $callback
|
||||||
|
*/
|
||||||
|
public function __construct(Application $app, $callback)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
$this->callback = $callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(GetResponseForExceptionEvent $event)
|
||||||
|
{
|
||||||
|
$exception = $event->getException();
|
||||||
|
$this->callback = $this->app['callback_resolver']->resolveCallback($this->callback);
|
||||||
|
|
||||||
|
if (!$this->shouldRun($exception)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500;
|
||||||
|
|
||||||
|
$response = call_user_func($this->callback, $exception, $event->getRequest(), $code);
|
||||||
|
|
||||||
|
$this->ensureResponse($response, $event);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function shouldRun(\Exception $exception)
|
||||||
|
{
|
||||||
|
if (is_array($this->callback)) {
|
||||||
|
$callbackReflection = new \ReflectionMethod($this->callback[0], $this->callback[1]);
|
||||||
|
} elseif (is_object($this->callback) && !$this->callback instanceof \Closure) {
|
||||||
|
$callbackReflection = new \ReflectionObject($this->callback);
|
||||||
|
$callbackReflection = $callbackReflection->getMethod('__invoke');
|
||||||
|
} else {
|
||||||
|
$callbackReflection = new \ReflectionFunction($this->callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($callbackReflection->getNumberOfParameters() > 0) {
|
||||||
|
$parameters = $callbackReflection->getParameters();
|
||||||
|
$expectedException = $parameters[0];
|
||||||
|
if ($expectedException->getClass() && !$expectedException->getClass()->isInstance($exception)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function ensureResponse($response, GetResponseForExceptionEvent $event)
|
||||||
|
{
|
||||||
|
if ($response instanceof Response) {
|
||||||
|
$event->setResponse($response);
|
||||||
|
} else {
|
||||||
|
$viewEvent = new GetResponseForControllerResultEvent($this->app['kernel'], $event->getRequest(), $event->getRequestType(), $response);
|
||||||
|
$this->app['dispatcher']->dispatch(KernelEvents::VIEW, $viewEvent);
|
||||||
|
|
||||||
|
if ($viewEvent->hasResponse()) {
|
||||||
|
$event->setResponse($viewEvent->getResponse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Symfony\Component\Asset\Packages;
|
||||||
|
use Symfony\Component\Asset\Package;
|
||||||
|
use Symfony\Component\Asset\PathPackage;
|
||||||
|
use Symfony\Component\Asset\UrlPackage;
|
||||||
|
use Symfony\Component\Asset\Context\RequestStackContext;
|
||||||
|
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
|
||||||
|
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony Asset component Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class AssetServiceProvider implements ServiceProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['assets.packages'] = function ($app) {
|
||||||
|
$packages = array();
|
||||||
|
foreach ($app['assets.named_packages'] as $name => $package) {
|
||||||
|
$version = $app['assets.strategy_factory'](isset($package['version']) ? $package['version'] : '', isset($package['version_format']) ? $package['version_format'] : null);
|
||||||
|
|
||||||
|
$packages[$name] = $app['assets.package_factory'](isset($package['base_path']) ? $package['base_path'] : '', isset($package['base_urls']) ? $package['base_urls'] : array(), $version, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Packages($app['assets.default_package'], $packages);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['assets.default_package'] = function ($app) {
|
||||||
|
$version = $app['assets.strategy_factory']($app['assets.version'], $app['assets.version_format']);
|
||||||
|
|
||||||
|
return $app['assets.package_factory']($app['assets.base_path'], $app['assets.base_urls'], $version, 'default');
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['assets.context'] = function ($app) {
|
||||||
|
return new RequestStackContext($app['request_stack']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['assets.base_path'] = '';
|
||||||
|
$app['assets.base_urls'] = array();
|
||||||
|
$app['assets.version'] = null;
|
||||||
|
$app['assets.version_format'] = null;
|
||||||
|
|
||||||
|
$app['assets.named_packages'] = array();
|
||||||
|
|
||||||
|
// prototypes
|
||||||
|
|
||||||
|
$app['assets.strategy_factory'] = $app->protect(function ($version, $format) use ($app) {
|
||||||
|
if (!$version) {
|
||||||
|
return new EmptyVersionStrategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StaticVersionStrategy($version, $format);
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['assets.package_factory'] = $app->protect(function ($basePath, $baseUrls, $version, $name) use ($app) {
|
||||||
|
if ($basePath && $baseUrls) {
|
||||||
|
throw new \LogicException(sprintf('Asset package "%s" cannot have base URLs and base paths.', $name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$baseUrls) {
|
||||||
|
return new PathPackage($basePath, $version, $app['assets.context']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UrlPackage($baseUrls, $version, $app['assets.context']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Symfony\Component\Security\Csrf\CsrfTokenManager;
|
||||||
|
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
|
||||||
|
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
|
||||||
|
use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony CSRF Security component Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class CsrfServiceProvider implements ServiceProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['csrf.token_manager'] = function ($app) {
|
||||||
|
return new CsrfTokenManager($app['csrf.token_generator'], $app['csrf.token_storage']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['csrf.token_storage'] = function ($app) {
|
||||||
|
if (isset($app['session'])) {
|
||||||
|
return new SessionTokenStorage($app['session'], $app['csrf.session_namespace']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NativeSessionTokenStorage($app['csrf.session_namespace']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['csrf.token_generator'] = function ($app) {
|
||||||
|
return new UriSafeTokenGenerator();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['csrf.session_namespace'] = '_csrf';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Doctrine\DBAL\DriverManager;
|
||||||
|
use Doctrine\DBAL\Configuration;
|
||||||
|
use Doctrine\Common\EventManager;
|
||||||
|
use Symfony\Bridge\Doctrine\Logger\DbalLogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doctrine DBAL Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class DoctrineServiceProvider implements ServiceProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['db.default_options'] = array(
|
||||||
|
'driver' => 'pdo_mysql',
|
||||||
|
'dbname' => null,
|
||||||
|
'host' => 'localhost',
|
||||||
|
'user' => 'root',
|
||||||
|
'password' => null,
|
||||||
|
);
|
||||||
|
|
||||||
|
$app['dbs.options.initializer'] = $app->protect(function () use ($app) {
|
||||||
|
static $initialized = false;
|
||||||
|
|
||||||
|
if ($initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$initialized = true;
|
||||||
|
|
||||||
|
if (!isset($app['dbs.options'])) {
|
||||||
|
$app['dbs.options'] = array('default' => isset($app['db.options']) ? $app['db.options'] : array());
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp = $app['dbs.options'];
|
||||||
|
foreach ($tmp as $name => &$options) {
|
||||||
|
$options = array_replace($app['db.default_options'], $options);
|
||||||
|
|
||||||
|
if (!isset($app['dbs.default'])) {
|
||||||
|
$app['dbs.default'] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$app['dbs.options'] = $tmp;
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['dbs'] = function ($app) {
|
||||||
|
$app['dbs.options.initializer']();
|
||||||
|
|
||||||
|
$dbs = new Container();
|
||||||
|
foreach ($app['dbs.options'] as $name => $options) {
|
||||||
|
if ($app['dbs.default'] === $name) {
|
||||||
|
// we use shortcuts here in case the default has been overridden
|
||||||
|
$config = $app['db.config'];
|
||||||
|
$manager = $app['db.event_manager'];
|
||||||
|
} else {
|
||||||
|
$config = $app['dbs.config'][$name];
|
||||||
|
$manager = $app['dbs.event_manager'][$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbs[$name] = function ($dbs) use ($options, $config, $manager) {
|
||||||
|
return DriverManager::getConnection($options, $config, $manager);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dbs;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['dbs.config'] = function ($app) {
|
||||||
|
$app['dbs.options.initializer']();
|
||||||
|
|
||||||
|
$configs = new Container();
|
||||||
|
$addLogger = isset($app['logger']) && null !== $app['logger'] && class_exists('Symfony\Bridge\Doctrine\Logger\DbalLogger');
|
||||||
|
foreach ($app['dbs.options'] as $name => $options) {
|
||||||
|
$configs[$name] = new Configuration();
|
||||||
|
if ($addLogger) {
|
||||||
|
$configs[$name]->setSQLLogger(new DbalLogger($app['logger'], isset($app['stopwatch']) ? $app['stopwatch'] : null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $configs;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['dbs.event_manager'] = function ($app) {
|
||||||
|
$app['dbs.options.initializer']();
|
||||||
|
|
||||||
|
$managers = new Container();
|
||||||
|
foreach ($app['dbs.options'] as $name => $options) {
|
||||||
|
$managers[$name] = new EventManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $managers;
|
||||||
|
};
|
||||||
|
|
||||||
|
// shortcuts for the "first" DB
|
||||||
|
$app['db'] = function ($app) {
|
||||||
|
$dbs = $app['dbs'];
|
||||||
|
|
||||||
|
return $dbs[$app['dbs.default']];
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['db.config'] = function ($app) {
|
||||||
|
$dbs = $app['dbs.config'];
|
||||||
|
|
||||||
|
return $dbs[$app['dbs.default']];
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['db.event_manager'] = function ($app) {
|
||||||
|
$dbs = $app['dbs.event_manager'];
|
||||||
|
|
||||||
|
return $dbs[$app['dbs.default']];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Silex\Provider;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Silex\ExceptionHandler;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
class ExceptionHandlerServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['exception_handler'] = function ($app) {
|
||||||
|
return new ExceptionHandler($app['debug']);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
if (isset($app['exception_handler'])) {
|
||||||
|
$dispatcher->addSubscriber($app['exception_handler']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
<?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\Form;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
use Symfony\Component\Form\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Form\FormExtensionInterface;
|
||||||
|
use Symfony\Component\Form\FormTypeGuesserChain;
|
||||||
|
|
||||||
|
class SilexFormExtension implements FormExtensionInterface
|
||||||
|
{
|
||||||
|
private $app;
|
||||||
|
private $types;
|
||||||
|
private $typeExtensions;
|
||||||
|
private $guessers;
|
||||||
|
private $guesserLoaded = false;
|
||||||
|
private $guesser;
|
||||||
|
|
||||||
|
public function __construct(Container $app, array $types, array $typeExtensions, array $guessers)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
$this->setTypes($types);
|
||||||
|
$this->setTypeExtensions($typeExtensions);
|
||||||
|
$this->setGuessers($guessers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType($name)
|
||||||
|
{
|
||||||
|
if (!isset($this->types[$name])) {
|
||||||
|
throw new InvalidArgumentException(sprintf('The type "%s" is not the name of a registered form type.', $name));
|
||||||
|
}
|
||||||
|
if (!is_object($this->types[$name])) {
|
||||||
|
$this->types[$name] = $this->app[$this->types[$name]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->types[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasType($name)
|
||||||
|
{
|
||||||
|
return isset($this->types[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeExtensions($name)
|
||||||
|
{
|
||||||
|
return isset($this->typeExtensions[$name]) ? $this->typeExtensions[$name] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasTypeExtensions($name)
|
||||||
|
{
|
||||||
|
return isset($this->typeExtensions[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeGuesser()
|
||||||
|
{
|
||||||
|
if (!$this->guesserLoaded) {
|
||||||
|
$this->guesserLoaded = true;
|
||||||
|
|
||||||
|
if ($this->guessers) {
|
||||||
|
$guessers = [];
|
||||||
|
foreach ($this->guessers as $guesser) {
|
||||||
|
if (!is_object($guesser)) {
|
||||||
|
$guesser = $this->app[$guesser];
|
||||||
|
}
|
||||||
|
$guessers[] = $guesser;
|
||||||
|
}
|
||||||
|
$this->guesser = new FormTypeGuesserChain($guessers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->guesser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setTypes(array $types)
|
||||||
|
{
|
||||||
|
$this->types = [];
|
||||||
|
foreach ($types as $type) {
|
||||||
|
if (!is_object($type)) {
|
||||||
|
if (!isset($this->app[$type])) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid form type. The silex service "%s" does not exist.', $type));
|
||||||
|
}
|
||||||
|
$this->types[$type] = $type;
|
||||||
|
} else {
|
||||||
|
$this->types[get_class($type)] = $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setTypeExtensions(array $typeExtensions)
|
||||||
|
{
|
||||||
|
$this->typeExtensions = [];
|
||||||
|
foreach ($typeExtensions as $extension) {
|
||||||
|
if (!is_object($extension)) {
|
||||||
|
if (!isset($this->app[$extension])) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid form type extension. The silex service "%s" does not exist.', $extension));
|
||||||
|
}
|
||||||
|
$extension = $this->app[$extension];
|
||||||
|
}
|
||||||
|
$this->typeExtensions[$extension->getExtendedType()][] = $extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setGuessers(array $guessers)
|
||||||
|
{
|
||||||
|
$this->guessers = [];
|
||||||
|
foreach ($guessers as $guesser) {
|
||||||
|
if (!is_object($guesser) && !isset($this->app[$guesser])) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid form type guesser. The silex service "%s" does not exist.', $guesser));
|
||||||
|
}
|
||||||
|
$this->guessers[] = $guesser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
|
||||||
|
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
|
||||||
|
use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension;
|
||||||
|
use Symfony\Component\Form\Forms;
|
||||||
|
use Symfony\Component\Form\ResolvedFormTypeFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony Form component Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class FormServiceProvider implements ServiceProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
if (!class_exists('Locale')) {
|
||||||
|
throw new \RuntimeException('You must either install the PHP intl extension or the Symfony Intl Component to use the Form extension.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$app['form.types'] = function ($app) {
|
||||||
|
return array();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['form.type.extensions'] = function ($app) {
|
||||||
|
return array();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['form.type.guessers'] = function ($app) {
|
||||||
|
return array();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['form.extension.csrf'] = function ($app) {
|
||||||
|
if (isset($app['translator'])) {
|
||||||
|
return new CsrfExtension($app['csrf.token_manager'], $app['translator']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CsrfExtension($app['csrf.token_manager']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['form.extension.silex'] = function ($app) {
|
||||||
|
return new Form\SilexFormExtension($app, $app['form.types'], $app['form.type.extensions'], $app['form.type.guessers']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['form.extensions'] = function ($app) {
|
||||||
|
$extensions = array(
|
||||||
|
new HttpFoundationExtension(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($app['csrf.token_manager'])) {
|
||||||
|
$extensions[] = $app['form.extension.csrf'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($app['validator'])) {
|
||||||
|
$extensions[] = new FormValidatorExtension($app['validator']);
|
||||||
|
}
|
||||||
|
$extensions[] = $app['form.extension.silex'];
|
||||||
|
|
||||||
|
return $extensions;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['form.factory'] = function ($app) {
|
||||||
|
return Forms::createFormFactoryBuilder()
|
||||||
|
->addExtensions($app['form.extensions'])
|
||||||
|
->setResolvedTypeFactory($app['form.resolved_type_factory'])
|
||||||
|
->getFormFactory()
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['form.resolved_type_factory'] = function ($app) {
|
||||||
|
return new ResolvedFormTypeFactory();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?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\HttpCache;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\HttpCache\HttpCache as BaseHttpCache;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP Cache extension to allow using the run() shortcut.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class HttpCache extends BaseHttpCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handles the Request and delivers the Response.
|
||||||
|
*
|
||||||
|
* @param Request $request The Request object
|
||||||
|
*/
|
||||||
|
public function run(Request $request = null)
|
||||||
|
{
|
||||||
|
if (null === $request) {
|
||||||
|
$request = Request::createFromGlobals();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->handle($request);
|
||||||
|
$response->send();
|
||||||
|
$this->terminate($request, $response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Provider\HttpCache\HttpCache;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\HttpKernel\HttpCache\Esi;
|
||||||
|
use Symfony\Component\HttpKernel\HttpCache\Store;
|
||||||
|
use Symfony\Component\HttpKernel\EventListener\SurrogateListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony HttpKernel component Provider for HTTP cache.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class HttpCacheServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['http_cache'] = function ($app) {
|
||||||
|
$app['http_cache.options'] = array_replace(
|
||||||
|
array(
|
||||||
|
'debug' => $app['debug'],
|
||||||
|
), $app['http_cache.options']
|
||||||
|
);
|
||||||
|
|
||||||
|
return new HttpCache($app, $app['http_cache.store'], $app['http_cache.esi'], $app['http_cache.options']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['http_cache.esi'] = function ($app) {
|
||||||
|
return new Esi();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['http_cache.store'] = function ($app) {
|
||||||
|
return new Store($app['http_cache.cache_dir']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['http_cache.esi_listener'] = function ($app) {
|
||||||
|
return new SurrogateListener($app['http_cache.esi']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['http_cache.options'] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addSubscriber($app['http_cache.esi_listener']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
|
||||||
|
use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer;
|
||||||
|
use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer;
|
||||||
|
use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer;
|
||||||
|
use Symfony\Component\HttpKernel\EventListener\FragmentListener;
|
||||||
|
use Symfony\Component\HttpKernel\Kernel;
|
||||||
|
use Symfony\Component\HttpKernel\UriSigner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpKernel Fragment integration for Silex.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class HttpFragmentServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['fragment.handler'] = function ($app) {
|
||||||
|
return new FragmentHandler($app['request_stack'], $app['fragment.renderers'], $app['debug']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['fragment.renderer.inline'] = function ($app) {
|
||||||
|
$renderer = new InlineFragmentRenderer($app['kernel'], $app['dispatcher']);
|
||||||
|
$renderer->setFragmentPath($app['fragment.path']);
|
||||||
|
|
||||||
|
return $renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['fragment.renderer.hinclude'] = function ($app) {
|
||||||
|
$renderer = new HIncludeFragmentRenderer(null, $app['uri_signer'], $app['fragment.renderer.hinclude.global_template'], $app['charset']);
|
||||||
|
$renderer->setFragmentPath($app['fragment.path']);
|
||||||
|
|
||||||
|
return $renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['fragment.renderer.esi'] = function ($app) {
|
||||||
|
$renderer = new EsiFragmentRenderer($app['http_cache.esi'], $app['fragment.renderer.inline']);
|
||||||
|
$renderer->setFragmentPath($app['fragment.path']);
|
||||||
|
|
||||||
|
return $renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['fragment.listener'] = function ($app) {
|
||||||
|
return new FragmentListener($app['uri_signer'], $app['fragment.path']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['uri_signer'] = function ($app) {
|
||||||
|
return new UriSigner($app['uri_signer.secret']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['uri_signer.secret'] = md5(__DIR__);
|
||||||
|
$app['fragment.path'] = '/_fragment';
|
||||||
|
$app['fragment.renderer.hinclude.global_template'] = null;
|
||||||
|
$app['fragment.renderers'] = function ($app) {
|
||||||
|
$renderers = array($app['fragment.renderer.inline'], $app['fragment.renderer.hinclude']);
|
||||||
|
|
||||||
|
if (isset($app['http_cache.esi'])) {
|
||||||
|
$renderers[] = $app['fragment.renderer.esi'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $renderers;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addSubscriber($app['fragment.listener']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Silex\Provider;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Silex\AppArgumentValueResolver;
|
||||||
|
use Silex\CallbackResolver;
|
||||||
|
use Silex\ControllerResolver;
|
||||||
|
use Silex\EventListener\ConverterListener;
|
||||||
|
use Silex\EventListener\MiddlewareListener;
|
||||||
|
use Silex\EventListener\StringToResponseListener;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ControllerResolver as SfControllerResolver;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
|
||||||
|
use Symfony\Component\HttpKernel\EventListener\ResponseListener;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernel;
|
||||||
|
use Symfony\Component\HttpKernel\Kernel;
|
||||||
|
|
||||||
|
class HttpKernelServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['resolver'] = function ($app) {
|
||||||
|
if (Kernel::VERSION_ID >= 30100) {
|
||||||
|
return new SfControllerResolver($app['logger']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ControllerResolver($app, $app['logger']);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Kernel::VERSION_ID >= 30100) {
|
||||||
|
$app['argument_metadata_factory'] = function ($app) {
|
||||||
|
return new ArgumentMetadataFactory();
|
||||||
|
};
|
||||||
|
$app['argument_value_resolvers'] = function ($app) {
|
||||||
|
if (Kernel::VERSION_ID < 30200) {
|
||||||
|
return array(
|
||||||
|
new AppArgumentValueResolver($app),
|
||||||
|
new RequestAttributeValueResolver(),
|
||||||
|
new RequestValueResolver(),
|
||||||
|
new DefaultValueResolver(),
|
||||||
|
new VariadicValueResolver(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge(array(new AppArgumentValueResolver($app)), ArgumentResolver::getDefaultArgumentValueResolvers());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$app['argument_resolver'] = function ($app) {
|
||||||
|
if (Kernel::VERSION_ID >= 30100) {
|
||||||
|
return new ArgumentResolver($app['argument_metadata_factory'], $app['argument_value_resolvers']);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['kernel'] = function ($app) {
|
||||||
|
return new HttpKernel($app['dispatcher'], $app['resolver'], $app['request_stack'], $app['argument_resolver']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['request_stack'] = function () {
|
||||||
|
return new RequestStack();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['dispatcher'] = function () {
|
||||||
|
return new EventDispatcher();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['callback_resolver'] = function ($app) {
|
||||||
|
return new CallbackResolver($app);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addSubscriber(new ResponseListener($app['charset']));
|
||||||
|
$dispatcher->addSubscriber(new MiddlewareListener($app));
|
||||||
|
$dispatcher->addSubscriber(new ConverterListener($app['routes'], $app['callback_resolver']));
|
||||||
|
$dispatcher->addSubscriber(new StringToResponseListener());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2010-2015 Fabien Potencier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?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\Locale;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\Routing\RequestContext;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the locale based on the current request.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Jérôme Tamarelle <jerome@tamarelle.net>
|
||||||
|
*/
|
||||||
|
class LocaleListener implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
private $app;
|
||||||
|
private $defaultLocale;
|
||||||
|
private $requestStack;
|
||||||
|
private $requestContext;
|
||||||
|
|
||||||
|
public function __construct(Container $app, $defaultLocale = 'en', RequestStack $requestStack, RequestContext $requestContext = null)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
$this->defaultLocale = $defaultLocale;
|
||||||
|
$this->requestStack = $requestStack;
|
||||||
|
$this->requestContext = $requestContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onKernelRequest(GetResponseEvent $event)
|
||||||
|
{
|
||||||
|
$request = $event->getRequest();
|
||||||
|
$request->setDefaultLocale($this->defaultLocale);
|
||||||
|
|
||||||
|
$this->setLocale($request);
|
||||||
|
$this->setRouterContext($request);
|
||||||
|
|
||||||
|
$this->app['locale'] = $request->getLocale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onKernelFinishRequest(FinishRequestEvent $event)
|
||||||
|
{
|
||||||
|
if (null !== $parentRequest = $this->requestStack->getParentRequest()) {
|
||||||
|
$this->setRouterContext($parentRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setLocale(Request $request)
|
||||||
|
{
|
||||||
|
if ($locale = $request->attributes->get('_locale')) {
|
||||||
|
$request->setLocale($locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setRouterContext(Request $request)
|
||||||
|
{
|
||||||
|
if (null !== $this->requestContext) {
|
||||||
|
$this->requestContext->setParameter('_locale', $request->getLocale());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
// must be registered after the Router to have access to the _locale
|
||||||
|
KernelEvents::REQUEST => array(array('onKernelRequest', 16)),
|
||||||
|
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Silex\Provider\Locale\LocaleListener;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locale Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class LocaleServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['locale.listener'] = function ($app) {
|
||||||
|
return new LocaleListener($app, $app['locale'], $app['request_stack'], isset($app['request_context']) ? $app['request_context'] : null);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['locale'] = 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addSubscriber($app['locale.listener']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Monolog\Formatter\LineFormatter;
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Monolog\Handler;
|
||||||
|
use Monolog\ErrorHandler;
|
||||||
|
use Silex\Application;
|
||||||
|
use Silex\Api\BootableProviderInterface;
|
||||||
|
use Symfony\Bridge\Monolog\Handler\DebugHandler;
|
||||||
|
use Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy;
|
||||||
|
use Silex\EventListener\LogListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monolog Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class MonologServiceProvider implements ServiceProviderInterface, BootableProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['logger'] = function () use ($app) {
|
||||||
|
return $app['monolog'];
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($bridge = class_exists('Symfony\Bridge\Monolog\Logger')) {
|
||||||
|
$app['monolog.handler.debug'] = function () use ($app) {
|
||||||
|
$level = MonologServiceProvider::translateLevel($app['monolog.level']);
|
||||||
|
|
||||||
|
return new DebugHandler($level);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isset($app['request_stack'])) {
|
||||||
|
$app['monolog.not_found_activation_strategy'] = function () use ($app) {
|
||||||
|
return new NotFoundActivationStrategy($app['request_stack'], array('^/'), $app['monolog.level']);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$app['monolog.logger.class'] = $bridge ? 'Symfony\Bridge\Monolog\Logger' : 'Monolog\Logger';
|
||||||
|
|
||||||
|
$app['monolog'] = function ($app) {
|
||||||
|
$log = new $app['monolog.logger.class']($app['monolog.name']);
|
||||||
|
|
||||||
|
$handler = new Handler\GroupHandler($app['monolog.handlers']);
|
||||||
|
if (isset($app['monolog.not_found_activation_strategy'])) {
|
||||||
|
$handler = new Handler\FingersCrossedHandler($handler, $app['monolog.not_found_activation_strategy']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$log->pushHandler($handler);
|
||||||
|
|
||||||
|
if ($app['debug'] && isset($app['monolog.handler.debug'])) {
|
||||||
|
$log->pushHandler($app['monolog.handler.debug']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $log;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['monolog.formatter'] = function () {
|
||||||
|
return new LineFormatter();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['monolog.handler'] = $defaultHandler = function () use ($app) {
|
||||||
|
$level = MonologServiceProvider::translateLevel($app['monolog.level']);
|
||||||
|
|
||||||
|
$handler = new Handler\StreamHandler($app['monolog.logfile'], $level, $app['monolog.bubble'], $app['monolog.permission']);
|
||||||
|
$handler->setFormatter($app['monolog.formatter']);
|
||||||
|
|
||||||
|
return $handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['monolog.handlers'] = function () use ($app, $defaultHandler) {
|
||||||
|
$handlers = array();
|
||||||
|
|
||||||
|
// enables the default handler if a logfile was set or the monolog.handler service was redefined
|
||||||
|
if ($app['monolog.logfile'] || $defaultHandler !== $app->raw('monolog.handler')) {
|
||||||
|
$handlers[] = $app['monolog.handler'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handlers;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['monolog.level'] = function () {
|
||||||
|
return Logger::DEBUG;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['monolog.listener'] = function () use ($app) {
|
||||||
|
return new LogListener($app['logger'], $app['monolog.exception.logger_filter']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['monolog.name'] = 'app';
|
||||||
|
$app['monolog.bubble'] = true;
|
||||||
|
$app['monolog.permission'] = null;
|
||||||
|
$app['monolog.exception.logger_filter'] = null;
|
||||||
|
$app['monolog.logfile'] = null;
|
||||||
|
$app['monolog.use_error_handler'] = !$app['debug'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(Application $app)
|
||||||
|
{
|
||||||
|
if ($app['monolog.use_error_handler']) {
|
||||||
|
ErrorHandler::register($app['monolog']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($app['monolog.listener'])) {
|
||||||
|
$app['dispatcher']->addSubscriber($app['monolog.listener']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function translateLevel($name)
|
||||||
|
{
|
||||||
|
// level is already translated to logger constant, return as-is
|
||||||
|
if (is_int($name)) {
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$levels = Logger::getLevels();
|
||||||
|
$upper = strtoupper($name);
|
||||||
|
|
||||||
|
if (!isset($levels[$upper])) {
|
||||||
|
throw new \InvalidArgumentException("Provided logging level '$name' does not exist. Must be a valid monolog logging level.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $levels[$upper];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\RememberMeListener;
|
||||||
|
use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices;
|
||||||
|
use Symfony\Component\Security\Http\RememberMe\ResponseListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remember-me authentication for the SecurityServiceProvider.
|
||||||
|
*
|
||||||
|
* @author Jérôme Tamarelle <jerome@tamarelle.net>
|
||||||
|
*/
|
||||||
|
class RememberMeServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['security.remember_me.response_listener'] = function ($app) {
|
||||||
|
if (!isset($app['security.token_storage'])) {
|
||||||
|
throw new \LogicException('You must register the SecurityServiceProvider to use the RememberMeServiceProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseListener();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.authentication_listener.factory.remember_me'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
if (empty($options['key'])) {
|
||||||
|
$options['key'] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($app['security.remember_me.service.'.$name])) {
|
||||||
|
$app['security.remember_me.service.'.$name] = $app['security.remember_me.service._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($app['security.authentication_listener.'.$name.'.remember_me'])) {
|
||||||
|
$app['security.authentication_listener.'.$name.'.remember_me'] = $app['security.authentication_listener.remember_me._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($app['security.authentication_provider.'.$name.'.remember_me'])) {
|
||||||
|
$app['security.authentication_provider.'.$name.'.remember_me'] = $app['security.authentication_provider.remember_me._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'security.authentication_provider.'.$name.'.remember_me',
|
||||||
|
'security.authentication_listener.'.$name.'.remember_me',
|
||||||
|
null, // entry point
|
||||||
|
'remember_me',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.remember_me.service._proto'] = $app->protect(function ($providerKey, $options) use ($app) {
|
||||||
|
return function () use ($providerKey, $options, $app) {
|
||||||
|
$options = array_replace(array(
|
||||||
|
'name' => 'REMEMBERME',
|
||||||
|
'lifetime' => 31536000,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => null,
|
||||||
|
'secure' => false,
|
||||||
|
'httponly' => true,
|
||||||
|
'always_remember_me' => false,
|
||||||
|
'remember_me_parameter' => '_remember_me',
|
||||||
|
), $options);
|
||||||
|
|
||||||
|
return new TokenBasedRememberMeServices(array($app['security.user_provider.'.$providerKey]), $options['key'], $providerKey, $options, $app['logger']);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_listener.remember_me._proto'] = $app->protect(function ($providerKey) use ($app) {
|
||||||
|
return function () use ($app, $providerKey) {
|
||||||
|
$listener = new RememberMeListener(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$app['security.remember_me.service.'.$providerKey],
|
||||||
|
$app['security.authentication_manager'],
|
||||||
|
$app['logger'],
|
||||||
|
$app['dispatcher']
|
||||||
|
);
|
||||||
|
|
||||||
|
return $listener;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_provider.remember_me._proto'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
return function () use ($app, $name, $options) {
|
||||||
|
return new RememberMeAuthenticationProvider($app['security.user_checker'], $options['key'], $name);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addSubscriber($app['security.remember_me.response_listener']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?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\Routing;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
||||||
|
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a lazy UrlMatcher.
|
||||||
|
*
|
||||||
|
* @author Igor Wiedler <igor@wiedler.ch>
|
||||||
|
* @author Jérôme Tamarelle <jerome@tamarelle.net>
|
||||||
|
*/
|
||||||
|
class LazyRequestMatcher implements RequestMatcherInterface
|
||||||
|
{
|
||||||
|
private $factory;
|
||||||
|
|
||||||
|
public function __construct(\Closure $factory)
|
||||||
|
{
|
||||||
|
$this->factory = $factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the corresponding RequestMatcherInterface instance.
|
||||||
|
*
|
||||||
|
* @return UrlMatcherInterface
|
||||||
|
*/
|
||||||
|
public function getRequestMatcher()
|
||||||
|
{
|
||||||
|
$matcher = call_user_func($this->factory);
|
||||||
|
if (!$matcher instanceof RequestMatcherInterface) {
|
||||||
|
throw new \LogicException("Factory supplied to LazyRequestMatcher must return implementation of Symfony\Component\Routing\RequestMatcherInterface.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function matchRequest(Request $request)
|
||||||
|
{
|
||||||
|
return $this->getRequestMatcher()->matchRequest($request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?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\Routing;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\Routing\Matcher\RedirectableUrlMatcher as BaseRedirectableUrlMatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the RedirectableUrlMatcherInterface for Silex.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class RedirectableUrlMatcher extends BaseRedirectableUrlMatcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function redirect($path, $route, $scheme = null)
|
||||||
|
{
|
||||||
|
$url = $this->context->getBaseUrl().$path;
|
||||||
|
$query = $this->context->getQueryString() ?: '';
|
||||||
|
|
||||||
|
if ($query !== '') {
|
||||||
|
$url .= '?'.$query;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->context->getHost()) {
|
||||||
|
if ($scheme) {
|
||||||
|
$port = '';
|
||||||
|
if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
|
||||||
|
$port = ':'.$this->context->getHttpPort();
|
||||||
|
} elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) {
|
||||||
|
$port = ':'.$this->context->getHttpsPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = $scheme.'://'.$this->context->getHost().$port.$url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'_controller' => function ($url) { return new RedirectResponse($url, 301); },
|
||||||
|
'_route' => null,
|
||||||
|
'url' => $url,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\ControllerCollection;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Silex\Provider\Routing\RedirectableUrlMatcher;
|
||||||
|
use Silex\Provider\Routing\LazyRequestMatcher;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGenerator;
|
||||||
|
use Symfony\Component\Routing\RequestContext;
|
||||||
|
use Symfony\Component\HttpKernel\EventListener\RouterListener;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony Routing component Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class RoutingServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['route_class'] = 'Silex\\Route';
|
||||||
|
|
||||||
|
$app['route_factory'] = $app->factory(function ($app) {
|
||||||
|
return new $app['route_class']();
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['routes_factory'] = $app->factory(function () {
|
||||||
|
return new RouteCollection();
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['routes'] = function ($app) {
|
||||||
|
return $app['routes_factory'];
|
||||||
|
};
|
||||||
|
$app['url_generator'] = function ($app) {
|
||||||
|
return new UrlGenerator($app['routes'], $app['request_context']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['request_matcher'] = function ($app) {
|
||||||
|
return new RedirectableUrlMatcher($app['routes'], $app['request_context']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['request_context'] = function ($app) {
|
||||||
|
$context = new RequestContext();
|
||||||
|
|
||||||
|
$context->setHttpPort(isset($app['request.http_port']) ? $app['request.http_port'] : 80);
|
||||||
|
$context->setHttpsPort(isset($app['request.https_port']) ? $app['request.https_port'] : 443);
|
||||||
|
|
||||||
|
return $context;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['controllers'] = function ($app) {
|
||||||
|
return $app['controllers_factory'];
|
||||||
|
};
|
||||||
|
|
||||||
|
$controllers_factory = function () use ($app, &$controllers_factory) {
|
||||||
|
return new ControllerCollection($app['route_factory'], $app['routes_factory'], $controllers_factory);
|
||||||
|
};
|
||||||
|
$app['controllers_factory'] = $app->factory($controllers_factory);
|
||||||
|
|
||||||
|
$app['routing.listener'] = function ($app) {
|
||||||
|
$urlMatcher = new LazyRequestMatcher(function () use ($app) {
|
||||||
|
return $app['request_matcher'];
|
||||||
|
});
|
||||||
|
|
||||||
|
return new RouterListener($urlMatcher, $app['request_stack'], $app['request_context'], $app['logger']);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addSubscriber($app['routing.listener']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,684 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Application;
|
||||||
|
use Silex\Api\BootableProviderInterface;
|
||||||
|
use Silex\Api\ControllerProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestMatcher;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Security\Core\User\UserChecker;
|
||||||
|
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
|
||||||
|
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
|
||||||
|
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
|
||||||
|
use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder;
|
||||||
|
use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
|
||||||
|
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
|
||||||
|
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
|
||||||
|
use Symfony\Component\Security\Core\Role\RoleHierarchy;
|
||||||
|
use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator;
|
||||||
|
use Symfony\Component\Security\Http\Firewall;
|
||||||
|
use Symfony\Component\Security\Http\FirewallMap;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\AccessListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\LogoutListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\ContextListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\ChannelListener;
|
||||||
|
use Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint;
|
||||||
|
use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint;
|
||||||
|
use Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint;
|
||||||
|
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
|
||||||
|
use Symfony\Component\Security\Http\Logout\SessionLogoutHandler;
|
||||||
|
use Symfony\Component\Security\Http\Logout\DefaultLogoutSuccessHandler;
|
||||||
|
use Symfony\Component\Security\Http\AccessMap;
|
||||||
|
use Symfony\Component\Security\Http\HttpUtils;
|
||||||
|
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
||||||
|
use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener;
|
||||||
|
use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony Security component Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class SecurityServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface, ControllerProviderInterface, BootableProviderInterface
|
||||||
|
{
|
||||||
|
protected $fakeRoutes;
|
||||||
|
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
// used to register routes for login_check and logout
|
||||||
|
$this->fakeRoutes = array();
|
||||||
|
|
||||||
|
$that = $this;
|
||||||
|
|
||||||
|
$app['security.role_hierarchy'] = array();
|
||||||
|
$app['security.access_rules'] = array();
|
||||||
|
$app['security.hide_user_not_found'] = true;
|
||||||
|
$app['security.encoder.bcrypt.cost'] = 13;
|
||||||
|
|
||||||
|
$app['security.authorization_checker'] = function ($app) {
|
||||||
|
return new AuthorizationChecker($app['security.token_storage'], $app['security.authentication_manager'], $app['security.access_manager']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.token_storage'] = function ($app) {
|
||||||
|
return new TokenStorage();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['user'] = $app->factory(function ($app) {
|
||||||
|
if (null === $token = $app['security.token_storage']->getToken()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_object($user = $token->getUser())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_manager'] = function ($app) {
|
||||||
|
$manager = new AuthenticationProviderManager($app['security.authentication_providers']);
|
||||||
|
$manager->setEventDispatcher($app['dispatcher']);
|
||||||
|
|
||||||
|
return $manager;
|
||||||
|
};
|
||||||
|
|
||||||
|
// by default, all users use the digest encoder
|
||||||
|
$app['security.encoder_factory'] = function ($app) {
|
||||||
|
return new EncoderFactory(array(
|
||||||
|
'Symfony\Component\Security\Core\User\UserInterface' => $app['security.default_encoder'],
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
// by default, all users use the BCrypt encoder
|
||||||
|
$app['security.default_encoder'] = function ($app) {
|
||||||
|
return $app['security.encoder.bcrypt'];
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.encoder.digest'] = function ($app) {
|
||||||
|
return new MessageDigestPasswordEncoder();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.encoder.bcrypt'] = function ($app) {
|
||||||
|
return new BCryptPasswordEncoder($app['security.encoder.bcrypt.cost']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.encoder.pbkdf2'] = function ($app) {
|
||||||
|
return new Pbkdf2PasswordEncoder();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.user_checker'] = function ($app) {
|
||||||
|
return new UserChecker();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.access_manager'] = function ($app) {
|
||||||
|
return new AccessDecisionManager($app['security.voters']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.voters'] = function ($app) {
|
||||||
|
return array(
|
||||||
|
new RoleHierarchyVoter(new RoleHierarchy($app['security.role_hierarchy'])),
|
||||||
|
new AuthenticatedVoter($app['security.trust_resolver']),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.firewall'] = function ($app) {
|
||||||
|
return new Firewall($app['security.firewall_map'], $app['dispatcher']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.channel_listener'] = function ($app) {
|
||||||
|
return new ChannelListener(
|
||||||
|
$app['security.access_map'],
|
||||||
|
new RetryAuthenticationEntryPoint(
|
||||||
|
isset($app['request.http_port']) ? $app['request.http_port'] : 80,
|
||||||
|
isset($app['request.https_port']) ? $app['request.https_port'] : 443
|
||||||
|
),
|
||||||
|
$app['logger']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// generate the build-in authentication factories
|
||||||
|
foreach (array('logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous') as $type) {
|
||||||
|
$entryPoint = null;
|
||||||
|
if ('http' === $type) {
|
||||||
|
$entryPoint = 'http';
|
||||||
|
} elseif ('form' === $type) {
|
||||||
|
$entryPoint = 'form';
|
||||||
|
} elseif ('guard' === $type) {
|
||||||
|
$entryPoint = 'guard';
|
||||||
|
}
|
||||||
|
|
||||||
|
$app['security.authentication_listener.factory.'.$type] = $app->protect(function ($name, $options) use ($type, $app, $entryPoint) {
|
||||||
|
if ($entryPoint && !isset($app['security.entry_point.'.$name.'.'.$entryPoint])) {
|
||||||
|
$app['security.entry_point.'.$name.'.'.$entryPoint] = $app['security.entry_point.'.$entryPoint.'._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($app['security.authentication_listener.'.$name.'.'.$type])) {
|
||||||
|
$app['security.authentication_listener.'.$name.'.'.$type] = $app['security.authentication_listener.'.$type.'._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
$provider = 'dao';
|
||||||
|
if ('anonymous' === $type) {
|
||||||
|
$provider = 'anonymous';
|
||||||
|
} elseif ('guard' === $type) {
|
||||||
|
$provider = 'guard';
|
||||||
|
}
|
||||||
|
if (!isset($app['security.authentication_provider.'.$name.'.'.$provider])) {
|
||||||
|
$app['security.authentication_provider.'.$name.'.'.$provider] = $app['security.authentication_provider.'.$provider.'._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'security.authentication_provider.'.$name.'.'.$provider,
|
||||||
|
'security.authentication_listener.'.$name.'.'.$type,
|
||||||
|
$entryPoint ? 'security.entry_point.'.$name.'.'.$entryPoint : null,
|
||||||
|
$type,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$app['security.firewall_map'] = function ($app) {
|
||||||
|
$positions = array('logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous');
|
||||||
|
$providers = array();
|
||||||
|
$configs = array();
|
||||||
|
foreach ($app['security.firewalls'] as $name => $firewall) {
|
||||||
|
$entryPoint = null;
|
||||||
|
$pattern = isset($firewall['pattern']) ? $firewall['pattern'] : null;
|
||||||
|
$users = isset($firewall['users']) ? $firewall['users'] : array();
|
||||||
|
$security = isset($firewall['security']) ? (bool) $firewall['security'] : true;
|
||||||
|
$stateless = isset($firewall['stateless']) ? (bool) $firewall['stateless'] : false;
|
||||||
|
$context = isset($firewall['context']) ? $firewall['context'] : $name;
|
||||||
|
unset($firewall['pattern'], $firewall['users'], $firewall['security'], $firewall['stateless'], $firewall['context']);
|
||||||
|
|
||||||
|
$protected = false === $security ? false : count($firewall);
|
||||||
|
|
||||||
|
$listeners = array('security.channel_listener');
|
||||||
|
|
||||||
|
if ($protected) {
|
||||||
|
if (!isset($app['security.context_listener.'.$name])) {
|
||||||
|
if (!isset($app['security.user_provider.'.$name])) {
|
||||||
|
$app['security.user_provider.'.$name] = is_array($users) ? $app['security.user_provider.inmemory._proto']($users) : $users;
|
||||||
|
}
|
||||||
|
|
||||||
|
$app['security.context_listener.'.$name] = $app['security.context_listener._proto']($name, array($app['security.user_provider.'.$name]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $stateless) {
|
||||||
|
$listeners[] = 'security.context_listener.'.$context;
|
||||||
|
}
|
||||||
|
|
||||||
|
$factories = array();
|
||||||
|
foreach ($positions as $position) {
|
||||||
|
$factories[$position] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($firewall as $type => $options) {
|
||||||
|
if ('switch_user' === $type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize options
|
||||||
|
if (!is_array($options)) {
|
||||||
|
if (!$options) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($app['security.authentication_listener.factory.'.$type])) {
|
||||||
|
throw new \LogicException(sprintf('The "%s" authentication entry is not registered.', $type));
|
||||||
|
}
|
||||||
|
|
||||||
|
$options['stateless'] = $stateless;
|
||||||
|
|
||||||
|
list($providerId, $listenerId, $entryPointId, $position) = $app['security.authentication_listener.factory.'.$type]($name, $options);
|
||||||
|
|
||||||
|
if (null !== $entryPointId) {
|
||||||
|
$entryPoint = $entryPointId;
|
||||||
|
}
|
||||||
|
|
||||||
|
$factories[$position][] = $listenerId;
|
||||||
|
$providers[] = $providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($positions as $position) {
|
||||||
|
foreach ($factories[$position] as $listener) {
|
||||||
|
$listeners[] = $listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$listeners[] = 'security.access_listener';
|
||||||
|
|
||||||
|
if (isset($firewall['switch_user'])) {
|
||||||
|
$app['security.switch_user.'.$name] = $app['security.authentication_listener.switch_user._proto']($name, $firewall['switch_user']);
|
||||||
|
|
||||||
|
$listeners[] = 'security.switch_user.'.$name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($app['security.exception_listener.'.$name])) {
|
||||||
|
if (null == $entryPoint) {
|
||||||
|
$app[$entryPoint = 'security.entry_point.'.$name.'.form'] = $app['security.entry_point.form._proto']($name, array());
|
||||||
|
}
|
||||||
|
$accessDeniedHandler = null;
|
||||||
|
if (isset($app['security.access_denied_handler.'.$name])) {
|
||||||
|
$accessDeniedHandler = $app['security.access_denied_handler.'.$name];
|
||||||
|
}
|
||||||
|
$app['security.exception_listener.'.$name] = $app['security.exception_listener._proto']($entryPoint, $name, $accessDeniedHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$configs[$name] = array($pattern, $listeners, $protected);
|
||||||
|
}
|
||||||
|
|
||||||
|
$app['security.authentication_providers'] = array_map(function ($provider) use ($app) {
|
||||||
|
return $app[$provider];
|
||||||
|
}, array_unique($providers));
|
||||||
|
|
||||||
|
$map = new FirewallMap();
|
||||||
|
foreach ($configs as $name => $config) {
|
||||||
|
$map->add(
|
||||||
|
is_string($config[0]) ? new RequestMatcher($config[0]) : $config[0],
|
||||||
|
array_map(function ($listenerId) use ($app, $name) {
|
||||||
|
$listener = $app[$listenerId];
|
||||||
|
|
||||||
|
if (isset($app['security.remember_me.service.'.$name])) {
|
||||||
|
if ($listener instanceof AbstractAuthenticationListener || $listener instanceof GuardAuthenticationListener) {
|
||||||
|
$listener->setRememberMeServices($app['security.remember_me.service.'.$name]);
|
||||||
|
}
|
||||||
|
if ($listener instanceof LogoutListener) {
|
||||||
|
$listener->addHandler($app['security.remember_me.service.'.$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $listener;
|
||||||
|
}, $config[1]),
|
||||||
|
$config[2] ? $app['security.exception_listener.'.$name] : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.access_listener'] = function ($app) {
|
||||||
|
return new AccessListener(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$app['security.access_manager'],
|
||||||
|
$app['security.access_map'],
|
||||||
|
$app['security.authentication_manager'],
|
||||||
|
$app['logger']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.access_map'] = function ($app) {
|
||||||
|
$map = new AccessMap();
|
||||||
|
|
||||||
|
foreach ($app['security.access_rules'] as $rule) {
|
||||||
|
if (is_string($rule[0])) {
|
||||||
|
$rule[0] = new RequestMatcher($rule[0]);
|
||||||
|
} elseif (is_array($rule[0])) {
|
||||||
|
$rule[0] += [
|
||||||
|
'path' => null,
|
||||||
|
'host' => null,
|
||||||
|
'methods' => null,
|
||||||
|
'ips' => null,
|
||||||
|
'attributes' => array(),
|
||||||
|
'schemes' => null,
|
||||||
|
];
|
||||||
|
$rule[0] = new RequestMatcher($rule[0]['path'], $rule[0]['host'], $rule[0]['methods'], $rule[0]['ips'], $rule[0]['attributes'], $rule[0]['schemes']);
|
||||||
|
}
|
||||||
|
$map->add($rule[0], (array) $rule[1], isset($rule[2]) ? $rule[2] : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.trust_resolver'] = function ($app) {
|
||||||
|
return new AuthenticationTrustResolver('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken');
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.session_strategy'] = function ($app) {
|
||||||
|
return new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.http_utils'] = function ($app) {
|
||||||
|
return new HttpUtils($app['url_generator'], $app['request_matcher']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['security.last_error'] = $app->protect(function (Request $request) {
|
||||||
|
if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) {
|
||||||
|
return $request->attributes->get(Security::AUTHENTICATION_ERROR)->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$session = $request->getSession();
|
||||||
|
if ($session && $session->has(Security::AUTHENTICATION_ERROR)) {
|
||||||
|
$message = $session->get(Security::AUTHENTICATION_ERROR)->getMessage();
|
||||||
|
$session->remove(Security::AUTHENTICATION_ERROR);
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// prototypes (used by the Firewall Map)
|
||||||
|
|
||||||
|
$app['security.context_listener._proto'] = $app->protect(function ($providerKey, $userProviders) use ($app) {
|
||||||
|
return function () use ($app, $userProviders, $providerKey) {
|
||||||
|
return new ContextListener(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$userProviders,
|
||||||
|
$providerKey,
|
||||||
|
$app['logger'],
|
||||||
|
$app['dispatcher']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.user_provider.inmemory._proto'] = $app->protect(function ($params) use ($app) {
|
||||||
|
return function () use ($app, $params) {
|
||||||
|
$users = array();
|
||||||
|
foreach ($params as $name => $user) {
|
||||||
|
$users[$name] = array('roles' => (array) $user[0], 'password' => $user[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InMemoryUserProvider($users);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.exception_listener._proto'] = $app->protect(function ($entryPoint, $name, $accessDeniedHandler = null) use ($app) {
|
||||||
|
return function () use ($app, $entryPoint, $name, $accessDeniedHandler) {
|
||||||
|
return new ExceptionListener(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$app['security.trust_resolver'],
|
||||||
|
$app['security.http_utils'],
|
||||||
|
$name,
|
||||||
|
$app[$entryPoint],
|
||||||
|
null, // errorPage
|
||||||
|
$accessDeniedHandler,
|
||||||
|
$app['logger']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication.success_handler._proto'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
return function () use ($name, $options, $app) {
|
||||||
|
$handler = new DefaultAuthenticationSuccessHandler(
|
||||||
|
$app['security.http_utils'],
|
||||||
|
$options
|
||||||
|
);
|
||||||
|
$handler->setProviderKey($name);
|
||||||
|
|
||||||
|
return $handler;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication.failure_handler._proto'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
return function () use ($name, $options, $app) {
|
||||||
|
return new DefaultAuthenticationFailureHandler(
|
||||||
|
$app,
|
||||||
|
$app['security.http_utils'],
|
||||||
|
$options,
|
||||||
|
$app['logger']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_listener.guard._proto'] = $app->protect(function ($providerKey, $options) use ($app, $that) {
|
||||||
|
return function () use ($app, $providerKey, $options, $that) {
|
||||||
|
if (!isset($app['security.authentication.guard_handler'])) {
|
||||||
|
$app['security.authentication.guard_handler'] = new GuardAuthenticatorHandler($app['security.token_storage'], $app['dispatcher']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$authenticators = array();
|
||||||
|
foreach ($options['authenticators'] as $authenticatorId) {
|
||||||
|
$authenticators[] = $app[$authenticatorId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GuardAuthenticationListener(
|
||||||
|
$app['security.authentication.guard_handler'],
|
||||||
|
$app['security.authentication_manager'],
|
||||||
|
$providerKey,
|
||||||
|
$authenticators,
|
||||||
|
$app['logger']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_listener.form._proto'] = $app->protect(function ($name, $options) use ($app, $that) {
|
||||||
|
return function () use ($app, $name, $options, $that) {
|
||||||
|
$that->addFakeRoute(
|
||||||
|
'match',
|
||||||
|
$tmp = isset($options['check_path']) ? $options['check_path'] : '/login_check',
|
||||||
|
str_replace('/', '_', ltrim($tmp, '/'))
|
||||||
|
);
|
||||||
|
|
||||||
|
$class = isset($options['listener_class']) ? $options['listener_class'] : 'Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordFormAuthenticationListener';
|
||||||
|
|
||||||
|
if (!isset($app['security.authentication.success_handler.'.$name])) {
|
||||||
|
$app['security.authentication.success_handler.'.$name] = $app['security.authentication.success_handler._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($app['security.authentication.failure_handler.'.$name])) {
|
||||||
|
$app['security.authentication.failure_handler.'.$name] = $app['security.authentication.failure_handler._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $class(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$app['security.authentication_manager'],
|
||||||
|
isset($app['security.session_strategy.'.$name]) ? $app['security.session_strategy.'.$name] : $app['security.session_strategy'],
|
||||||
|
$app['security.http_utils'],
|
||||||
|
$name,
|
||||||
|
$app['security.authentication.success_handler.'.$name],
|
||||||
|
$app['security.authentication.failure_handler.'.$name],
|
||||||
|
$options,
|
||||||
|
$app['logger'],
|
||||||
|
$app['dispatcher'],
|
||||||
|
isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_listener.http._proto'] = $app->protect(function ($providerKey, $options) use ($app) {
|
||||||
|
return function () use ($app, $providerKey, $options) {
|
||||||
|
return new BasicAuthenticationListener(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$app['security.authentication_manager'],
|
||||||
|
$providerKey,
|
||||||
|
$app['security.entry_point.'.$providerKey.'.http'],
|
||||||
|
$app['logger']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_listener.anonymous._proto'] = $app->protect(function ($providerKey, $options) use ($app) {
|
||||||
|
return function () use ($app, $providerKey, $options) {
|
||||||
|
return new AnonymousAuthenticationListener(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$providerKey,
|
||||||
|
$app['logger']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication.logout_handler._proto'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
return function () use ($name, $options, $app) {
|
||||||
|
return new DefaultLogoutSuccessHandler(
|
||||||
|
$app['security.http_utils'],
|
||||||
|
isset($options['target_url']) ? $options['target_url'] : '/'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_listener.logout._proto'] = $app->protect(function ($name, $options) use ($app, $that) {
|
||||||
|
return function () use ($app, $name, $options, $that) {
|
||||||
|
$that->addFakeRoute(
|
||||||
|
'get',
|
||||||
|
$tmp = isset($options['logout_path']) ? $options['logout_path'] : '/logout',
|
||||||
|
str_replace('/', '_', ltrim($tmp, '/'))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isset($app['security.authentication.logout_handler.'.$name])) {
|
||||||
|
$app['security.authentication.logout_handler.'.$name] = $app['security.authentication.logout_handler._proto']($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
$listener = new LogoutListener(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$app['security.http_utils'],
|
||||||
|
$app['security.authentication.logout_handler.'.$name],
|
||||||
|
$options,
|
||||||
|
isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null
|
||||||
|
);
|
||||||
|
|
||||||
|
$invalidateSession = isset($options['invalidate_session']) ? $options['invalidate_session'] : true;
|
||||||
|
if (true === $invalidateSession && false === $options['stateless']) {
|
||||||
|
$listener->addHandler(new SessionLogoutHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $listener;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_listener.switch_user._proto'] = $app->protect(function ($name, $options) use ($app, $that) {
|
||||||
|
return function () use ($app, $name, $options, $that) {
|
||||||
|
return new SwitchUserListener(
|
||||||
|
$app['security.token_storage'],
|
||||||
|
$app['security.user_provider.'.$name],
|
||||||
|
$app['security.user_checker'],
|
||||||
|
$name,
|
||||||
|
$app['security.access_manager'],
|
||||||
|
$app['logger'],
|
||||||
|
isset($options['parameter']) ? $options['parameter'] : '_switch_user',
|
||||||
|
isset($options['role']) ? $options['role'] : 'ROLE_ALLOWED_TO_SWITCH',
|
||||||
|
$app['dispatcher']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.entry_point.form._proto'] = $app->protect(function ($name, array $options) use ($app) {
|
||||||
|
return function () use ($app, $options) {
|
||||||
|
$loginPath = isset($options['login_path']) ? $options['login_path'] : '/login';
|
||||||
|
$useForward = isset($options['use_forward']) ? $options['use_forward'] : false;
|
||||||
|
|
||||||
|
return new FormAuthenticationEntryPoint($app, $app['security.http_utils'], $loginPath, $useForward);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.entry_point.http._proto'] = $app->protect(function ($name, array $options) use ($app) {
|
||||||
|
return function () use ($app, $name, $options) {
|
||||||
|
return new BasicAuthenticationEntryPoint(isset($options['real_name']) ? $options['real_name'] : 'Secured');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.entry_point.guard._proto'] = $app->protect(function ($name, array $options) use ($app) {
|
||||||
|
if (isset($options['entry_point'])) {
|
||||||
|
// if it's configured explicitly, use it!
|
||||||
|
return $app[$options['entry_point']];
|
||||||
|
}
|
||||||
|
$authenticatorIds = $options['authenticators'];
|
||||||
|
if (count($authenticatorIds) == 1) {
|
||||||
|
// if there is only one authenticator, use that as the entry point
|
||||||
|
return $app[reset($authenticatorIds)];
|
||||||
|
}
|
||||||
|
// we have multiple entry points - we must ask them to configure one
|
||||||
|
throw new \LogicException(sprintf(
|
||||||
|
'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of you configurators (%s)',
|
||||||
|
implode(', ', $authenticatorIds)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_provider.dao._proto'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
return function () use ($app, $name) {
|
||||||
|
return new DaoAuthenticationProvider(
|
||||||
|
$app['security.user_provider.'.$name],
|
||||||
|
$app['security.user_checker'],
|
||||||
|
$name,
|
||||||
|
$app['security.encoder_factory'],
|
||||||
|
$app['security.hide_user_not_found']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_provider.guard._proto'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
return function () use ($app, $name, $options) {
|
||||||
|
$authenticators = array();
|
||||||
|
foreach ($options['authenticators'] as $authenticatorId) {
|
||||||
|
$authenticators[] = $app[$authenticatorId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GuardAuthenticationProvider(
|
||||||
|
$authenticators,
|
||||||
|
$app['security.user_provider.'.$name],
|
||||||
|
$name,
|
||||||
|
$app['security.user_checker']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['security.authentication_provider.anonymous._proto'] = $app->protect(function ($name, $options) use ($app) {
|
||||||
|
return function () use ($app, $name) {
|
||||||
|
return new AnonymousAuthenticationProvider($name);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isset($app['validator'])) {
|
||||||
|
$app['security.validator.user_password_validator'] = function ($app) {
|
||||||
|
return new UserPasswordValidator($app['security.token_storage'], $app['security.encoder_factory']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['validator.validator_service_ids'] = array_merge($app['validator.validator_service_ids'], array('security.validator.user_password' => 'security.validator.user_password_validator'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addSubscriber($app['security.firewall']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function connect(Application $app)
|
||||||
|
{
|
||||||
|
$controllers = $app['controllers_factory'];
|
||||||
|
foreach ($this->fakeRoutes as $route) {
|
||||||
|
list($method, $pattern, $name) = $route;
|
||||||
|
|
||||||
|
$controllers->$method($pattern)->run(null)->bind($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $controllers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(Application $app)
|
||||||
|
{
|
||||||
|
$app->mount('/', $this->connect($app));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFakeRoute($method, $pattern, $name)
|
||||||
|
{
|
||||||
|
$this->fakeRoutes[] = array($method, $pattern, $name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Symfony\Component\Serializer\Serializer;
|
||||||
|
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||||
|
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\CustomNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony Serializer component Provider.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Marijn Huizendveld <marijn@pink-tie.com>
|
||||||
|
*/
|
||||||
|
class SerializerServiceProvider implements ServiceProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* This method registers a serializer service. {@link http://api.symfony.com/master/Symfony/Component/Serializer/Serializer.html
|
||||||
|
* The service is provided by the Symfony Serializer component}.
|
||||||
|
*/
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['serializer'] = function ($app) {
|
||||||
|
return new Serializer($app['serializer.normalizers'], $app['serializer.encoders']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['serializer.encoders'] = function () {
|
||||||
|
return array(new JsonEncoder(), new XmlEncoder());
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['serializer.normalizers'] = function () {
|
||||||
|
return array(new CustomNormalizer(), new GetSetMethodNormalizer());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\ServiceControllerResolver;
|
||||||
|
|
||||||
|
class ServiceControllerServiceProvider implements ServiceProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app->extend('resolver', function ($resolver, $app) {
|
||||||
|
return new ServiceControllerResolver($resolver, $app['callback_resolver']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?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\Session;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
use Symfony\Component\HttpKernel\EventListener\SessionListener as BaseSessionListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the session in the request.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class SessionListener extends BaseSessionListener
|
||||||
|
{
|
||||||
|
private $app;
|
||||||
|
|
||||||
|
public function __construct(Container $app)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSession()
|
||||||
|
{
|
||||||
|
if (!isset($this->app['session'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->app['session'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?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\Session;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
use Symfony\Component\HttpKernel\EventListener\TestSessionListener as BaseTestSessionListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates sessions for testing purpose.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class TestSessionListener extends BaseTestSessionListener
|
||||||
|
{
|
||||||
|
private $app;
|
||||||
|
|
||||||
|
public function __construct(Container $app)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSession()
|
||||||
|
{
|
||||||
|
if (!isset($this->app['session'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->app['session'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?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 Pimple\Container;
|
||||||
|
use Pimple\ServiceProviderInterface;
|
||||||
|
use Silex\Api\EventListenerProviderInterface;
|
||||||
|
use Silex\Provider\Session\SessionListener;
|
||||||
|
use Silex\Provider\Session\TestSessionListener;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony HttpFoundation component Provider for sessions.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class SessionServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface
|
||||||
|
{
|
||||||
|
public function register(Container $app)
|
||||||
|
{
|
||||||
|
$app['session.test'] = false;
|
||||||
|
|
||||||
|
$app['session'] = function ($app) {
|
||||||
|
return new Session($app['session.storage'], $app['session.attribute_bag'], $app['session.flash_bag']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['session.storage'] = function ($app) {
|
||||||
|
if ($app['session.test']) {
|
||||||
|
return $app['session.storage.test'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $app['session.storage.native'];
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['session.storage.handler'] = function ($app) {
|
||||||
|
return new NativeFileSessionHandler($app['session.storage.save_path']);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['session.storage.native'] = function ($app) {
|
||||||
|
return new NativeSessionStorage(
|
||||||
|
$app['session.storage.options'],
|
||||||
|
$app['session.storage.handler']
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['session.listener'] = function ($app) {
|
||||||
|
return new SessionListener($app);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['session.storage.test'] = function () {
|
||||||
|
return new MockFileSessionStorage();
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['session.listener.test'] = function ($app) {
|
||||||
|
return new TestSessionListener($app);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app['session.storage.options'] = array();
|
||||||
|
$app['session.default_locale'] = 'en';
|
||||||
|
$app['session.storage.save_path'] = null;
|
||||||
|
$app['session.attribute_bag'] = null;
|
||||||
|
$app['session.flash_bag'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subscribe(Container $app, EventDispatcherInterface $dispatcher)
|
||||||
|
{
|
||||||
|
$dispatcher->addSubscriber($app['session.listener']);
|
||||||
|
|
||||||
|
if ($app['session.test']) {
|
||||||
|
$app['dispatcher']->addSubscriber($app['session.listener.test']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue