This commit is contained in:
R. Eric Wheeler 2016-07-02 10:58:30 -07:00
commit 227f2047a6
169 changed files with 17835 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/phpunit.xml
/vendor
/build
/composer.lock

36
.travis.yml Normal file
View File

@ -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

19
LICENSE Normal file
View File

@ -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.

62
README.rst Normal file
View File

@ -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

67
bin/build Executable file
View File

@ -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

View File

@ -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.*"
}
}

11
bin/skeleton/index.php Normal file
View File

@ -0,0 +1,11 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
$app = new Silex\Application();
$app->get('/hello', function () {
return 'Hello!';
});
$app->run();

View File

@ -0,0 +1,5 @@
{
"require": {
"silex/silex": "~1.1"
}
}

76
composer.json Normal file
View File

@ -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"
}

353
doc/changelog.rst Normal file
View File

@ -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');

17
doc/conf.py Normal file
View File

@ -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)

34
doc/contributing.rst Normal file
View File

@ -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

View File

@ -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);

View File

@ -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>`_.

View File

@ -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>`_.

40
doc/cookbook/index.rst Normal file
View File

@ -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>`.

View File

@ -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!"}

View File

@ -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),
);
};

View File

@ -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>`_

View File

@ -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.

View File

@ -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

19
doc/index.rst Normal file
View File

@ -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

84
doc/internals.rst Normal file
View File

@ -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/>`_.

50
doc/intro.rst Normal file
View File

@ -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/

162
doc/middlewares.rst Normal file
View File

@ -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``.

View File

@ -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>`.

256
doc/providers.rst Normal file
View File

@ -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.

60
doc/providers/asset.rst Normal file
View File

@ -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>`_.

52
doc/providers/csrf.rst Normal file
View File

@ -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'));

137
doc/providers/doctrine.rst Normal file
View File

@ -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/>`_.

204
doc/providers/form.rst Normal file
View File

@ -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>`_.

View File

@ -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>`_.

View File

@ -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')) }}

24
doc/providers/index.rst Normal file
View File

@ -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

24
doc/providers/locale.rst Normal file
View File

@ -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());

115
doc/providers/monolog.rst Normal file
View File

@ -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>`_.

View File

@ -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``).

711
doc/providers/security.rst Normal file
View File

@ -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

View File

@ -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+");

View File

@ -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');

103
doc/providers/session.rst Normal file
View File

@ -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;

View File

@ -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>`_.

View File

@ -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 %}

200
doc/providers/twig.rst Normal file
View File

@ -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

217
doc/providers/validator.rst Normal file
View File

@ -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>`_.

View File

@ -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.

264
doc/services.rst Normal file
View File

@ -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.

221
doc/testing.rst Normal file
View File

@ -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.

805
doc/usage.rst Normal file
View File

@ -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

165
doc/web_servers.rst Normal file
View File

@ -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.

24
phpunit.xml.dist Normal file
View File

@ -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>

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

19
src/Silex/Api/LICENSE Normal file
View File

@ -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.

View File

@ -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"
}
}
}

View File

@ -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;
}
}

506
src/Silex/Application.php Normal file
View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

122
src/Silex/Controller.php Normal file
View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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',
);
}
}

View File

@ -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),
);
}
}

View File

@ -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),
);
}
}

View File

@ -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),
);
}
}

View File

@ -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
{
}

View File

@ -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));
}
}

View File

@ -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());
}
}
}
}

View File

@ -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']);
});
}
}

View File

@ -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';
}
}

View File

@ -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']];
};
}
}

View File

@ -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']);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
};
}
}

View File

@ -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);
}
}

View File

@ -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']);
}
}

View File

@ -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']);
}
}

View File

@ -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());
}
}

View File

@ -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.

View File

@ -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)),
);
}
}

View File

@ -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']);
}
}

View File

@ -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];
}
}

View File

@ -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']);
}
}

View File

@ -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);
}
}

View File

@ -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,
);
}
}

View File

@ -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']);
}
}

View File

@ -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);
}
}

View File

@ -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());
};
}
}

View File

@ -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']);
});
}
}

View File

@ -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'];
}
}

View File

@ -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'];
}
}

View File

@ -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