diff --git a/best_practices/forms.rst b/best_practices/forms.rst index 3655c298750..189cb1d04c1 100644 --- a/best_practices/forms.rst +++ b/best_practices/forms.rst @@ -207,3 +207,21 @@ Second, we recommend using ``$form->isSubmitted()`` in the ``if`` statement for clarity. This isn't technically needed, since ``isValid()`` first calls ``isSubmitted()``. But without this, the flow doesn't read well as it *looks* like the form is *always* processed (even on the GET request). + +Custom Form Field Types +----------------------- + +.. best-practice:: + + Add the ``app_`` prefix to your custom form field types to avoid collisions. + +Custom form field types inherit from the ``AbstractType`` class, which defines the +``getName()`` method to configure the name of that form type. These names must +be unique in the application. + +If a custom form type uses the same name as any of the Symfony's built-in form +types, it will override it. The same happens when the custom form type matches +any of the types defined by the third-party bundles installed in your application. + +Add the ``app_`` prefix to your custom form field types to avoid name collisions +that can lead to hard to debug errors. diff --git a/book/controller.rst b/book/controller.rst index 609ce6cd859..62f5a1ba303 100644 --- a/book/controller.rst +++ b/book/controller.rst @@ -451,7 +451,7 @@ Or, if you want to redirect externally, just use ``redirect()`` and pass it the } By default, the ``redirectToRoute()`` method performs a 302 (temporary) redirect. To -perform a 301 (permanent) redirect, modify the second argument:: +perform a 301 (permanent) redirect, modify the third argument:: public function indexAction() { diff --git a/book/doctrine.rst b/book/doctrine.rst index 7c69e801cca..033a69f9d95 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -646,6 +646,9 @@ to easily fetch objects based on multiple conditions:: If you click the icon, the profiler will open, showing you the exact queries that were made. + + The icon will turn yellow if there were more than 50 queries on the + page. This could indicate that something is not correct. Updating an Object ~~~~~~~~~~~~~~~~~~ diff --git a/book/forms.rst b/book/forms.rst index 5441564b13a..0d695e71a95 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -1061,9 +1061,16 @@ that will house the logic for building the task form:: } } -This new class contains all the directions needed to create the task form -(note that the ``getName()`` method should return a unique identifier for this -form "type"). It can be used to quickly build a form object in the controller:: +.. caution:: + + The ``getName()`` method returns the identifier of this form "type". These + identifiers must be unique in the application. Unless you want to override + a built-in type, they should be different from the default Symfony types + and from any type defined by a third-party bundle installed in your application. + Consider prefixing your types with ``app_`` to avoid identifier collisions. + +This new class contains all the directions needed to create the task form. It can +be used to quickly build a form object in the controller:: // src/AppBundle/Controller/DefaultController.php diff --git a/book/http_fundamentals.rst b/book/http_fundamentals.rst index 06532b8fcb2..83e8f29dba9 100644 --- a/book/http_fundamentals.rst +++ b/book/http_fundamentals.rst @@ -460,7 +460,7 @@ by adding an entry for ``/contact`` to your routing configuration file: When someone visits the ``/contact`` page, this route is matched, and the specified controller is executed. As you'll learn in the :doc:`routing chapter `, -the ``AcmeDemoBundle:Main:contact`` string is a short syntax that points to a +the ``AppBundle:Main:contact`` string is a short syntax that points to a specific PHP method ``contactAction`` inside a class called ``MainController``:: // src/AppBundle/Controller/MainController.php diff --git a/book/translation.rst b/book/translation.rst index 395e771d324..13cc28682d4 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -427,17 +427,28 @@ via the ``request`` object:: public function indexAction(Request $request) { $locale = $request->getLocale(); - - $request->setLocale('en_US'); } -.. tip:: +To set the user's locale, you may want to create a custom event listener +so that it's set before any other parts of the system (i.e. the translator) +need it:: - Read :doc:`/cookbook/session/locale_sticky_session` to learn how to store - the user's locale in the session. + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); -.. index:: - single: Translations; Fallback and default locale + // some logic to determine the $locale + $request->getSession()->set('_locale', $locale); + } + +Read :doc:`/cookbook/session/locale_sticky_session` for more on the topic. + +.. note:: + + Setting the locale using ``$request->setLocale()`` in the controller + is too late to affect the translator. Either set the locale via a listener + (like above), the URL (see next) or call ``setLocale()`` directly on + the ``translator`` service. See the :ref:`book-translation-locale-url` section below about setting the locale via routing. @@ -518,6 +529,9 @@ in your application. Read :doc:`/cookbook/routing/service_container_parameters` to learn how to avoid hardcoding the ``_locale`` requirement in all your routes. +.. index:: + single: Translations; Fallback and default locale + Setting a default Locale ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/components/using_components.rst b/components/using_components.rst index 4103b956afc..e9c7ce56c27 100644 --- a/components/using_components.rst +++ b/components/using_components.rst @@ -36,12 +36,6 @@ whatever component you want. file in your directory. In that case, no worries! Just run ``php composer.phar require symfony/finder``. -If you know you need a specific version of the library, add that to the command: - -.. code-block:: bash - - $ composer require symfony/finder - **3.** Write your code! Once Composer has downloaded the component(s), all you need to do is include @@ -51,7 +45,8 @@ immediately:: // File example: src/script.php - // update this to the path to the "vendor/" directory, relative to this file + // update this to the path to the "vendor/" + // directory, relative to this file require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\Finder\Finder; diff --git a/contributing/code/core_team.rst b/contributing/code/core_team.rst index 3932df6b616..f8b0b6e6c38 100644 --- a/contributing/code/core_team.rst +++ b/contributing/code/core_team.rst @@ -60,14 +60,19 @@ Active Core Members MonologBridge_, and TwigBridge_ components; * **Kévin Dunglas** (`dunglas`_) can merge into the Serializer_ - component. + component; + + * **Abdellatif AitBoudad** (`aitboudad`_) can merge into the Translation_ + component; + + * **Jakub Zalas** (`jakzal`_) can merge into the DomCrawler_ component. * **Deciders** (``@symfony/deciders`` on GitHub): - * **Jakub Zalas** (`jakzal`_); * **Jordi Boggiano** (`seldaek`_); * **Lukas Kahwe Smith** (`lsmith77`_); - * **Ryan Weaver** (`weaverryan`_). + * **Ryan Weaver** (`weaverryan`_); + * **Christian Flothmann** (`xabbuh`_). Core Membership Application ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -165,6 +170,7 @@ discretion of the **Project Leader**. .. _PropertyAccess: https://github.com/symfony/PropertyAccess .. _Routing: https://github.com/symfony/Routing .. _Serializer: https://github.com/symfony/Serializer +.. _Translation: https://github.com/symfony/Translation .. _Stopwatch: https://github.com/symfony/Stopwatch .. _TwigBridge: https://github.com/symfony/TwigBridge .. _Validator: https://github.com/symfony/Validator @@ -181,3 +187,5 @@ discretion of the **Project Leader**. .. _`Seldaek`: https://github.com/Seldaek/ .. _`lsmith77`: https://github.com/lsmith77/ .. _`weaverryan`: https://github.com/weaverryan/ +.. _`aitboudad`: https://github.com/aitboudad/ +.. _`xabbuh`: https://github.com/xabbuh/ diff --git a/cookbook/assetic/apply_to_option.rst b/cookbook/assetic/apply_to_option.rst index eedc6e8e7af..29615b09214 100644 --- a/cookbook/assetic/apply_to_option.rst +++ b/cookbook/assetic/apply_to_option.rst @@ -9,7 +9,7 @@ as you'll see here, files that have a specific extension. To show you how to handle each option, suppose that you want to use Assetic's CoffeeScript filter, which compiles CoffeeScript files into JavaScript. -The main configuration is just the paths to coffee, node and node_modules. +The main configuration is just the paths to ``coffee``, ``node`` and ``node_modules``. An example configuration might look like this: .. configuration-block:: @@ -102,8 +102,7 @@ You can also combine multiple CoffeeScript files into a single output file: -Both the files will now be served up as a single file compiled into regular -JavaScript. +Both files will now be served up as a single file compiled into regular JavaScript. .. _cookbook-assetic-apply-to: @@ -117,10 +116,10 @@ since they will ultimately all be served as JavaScript. Unfortunately just adding the JavaScript files to the files to be combined as above will not work as the regular JavaScript files will not survive the CoffeeScript compilation. -This problem can be avoided by using the ``apply_to`` option in the config, -which allows you to specify which filter should always be applied to particular -file extensions. In this case you can specify that the ``coffee`` filter is -applied to all ``.coffee`` files: +This problem can be avoided by using the ``apply_to`` option, which allows you +to specify which filter should always be applied to particular file extensions. +In this case you can specify that the ``coffee`` filter is applied to all +``.coffee`` files: .. configuration-block:: @@ -161,10 +160,10 @@ applied to all ``.coffee`` files: ), )); -With this, you no longer need to specify the ``coffee`` filter in the template. -You can also list regular JavaScript files, all of which will be combined -and rendered as a single JavaScript file (with only the ``.coffee`` files -being run through the CoffeeScript filter): +With this option, you no longer need to specify the ``coffee`` filter in the +template. You can also list regular JavaScript files, all of which will be +combined and rendered as a single JavaScript file (with only the ``.coffee`` +files being run through the CoffeeScript filter): .. configuration-block:: diff --git a/cookbook/assetic/asset_management.rst b/cookbook/assetic/asset_management.rst index 8a84bbd0e43..503036bb3fe 100644 --- a/cookbook/assetic/asset_management.rst +++ b/cookbook/assetic/asset_management.rst @@ -73,9 +73,9 @@ To include JavaScript files, use the ``javascripts`` tag in any template: .. note:: - If you're using the default block names from the Symfony Standard Edition, - the ``javascripts`` tag will most commonly live in the ``javascripts`` - block: + If your application templates use the default block names from the Symfony + Standard Edition, the ``javascripts`` tag will most commonly live in the + ``javascripts`` block: .. code-block:: html+jinja @@ -108,8 +108,8 @@ that reference images by their relative path. See :ref:`cookbook-assetic-cssrewr Including CSS Stylesheets ~~~~~~~~~~~~~~~~~~~~~~~~~ -To bring in CSS stylesheets, you can use the same methodologies seen -above, except with the ``stylesheets`` tag: +To bring in CSS stylesheets, you can use the same technique explained above, +except with the ``stylesheets`` tag: .. configuration-block:: @@ -130,9 +130,9 @@ above, except with the ``stylesheets`` tag: .. note:: - If you're using the default block names from the Symfony Standard Edition, - the ``stylesheets`` tag will most commonly live in the ``stylesheets`` - block: + If your application templates use the default block names from the Symfony + Standard Edition, the ``stylesheets`` tag will most commonly live in the + ``stylesheets`` block: .. code-block:: html+jinja @@ -204,7 +204,7 @@ Combining Assets ~~~~~~~~~~~~~~~~ One feature of Assetic is that it will combine many files into one. This helps -to reduce the number of HTTP requests, which is great for front end performance. +to reduce the number of HTTP requests, which is great for frontend performance. It also allows you to maintain the files more easily by splitting them into manageable parts. This can help with re-usability as you can easily split project-specific files from those which can be used in other applications, @@ -350,7 +350,7 @@ Filters Once they're managed by Assetic, you can apply filters to your assets before they are served. This includes filters that compress the output of your assets -for smaller file sizes (and better front-end optimization). Other filters +for smaller file sizes (and better frontend optimization). Other filters can compile JavaScript file from CoffeeScript files and process SASS into CSS. In fact, Assetic has a long list of available filters. @@ -366,8 +366,8 @@ To use a filter, you first need to specify it in the Assetic configuration. Adding a filter here doesn't mean it's being used - it just means that it's available to use (you'll use the filter below). -For example to use the UglifyJS JavaScript minifier the following config should -be added: +For example to use the UglifyJS JavaScript minifier the following configuration +should be defined: .. configuration-block:: @@ -489,8 +489,8 @@ environment is just too slow. .. _cookbook-assetic-dump-prod: -Instead, each time you use your app in the ``prod`` environment (and therefore, -each time you deploy), you should run the following task: +Instead, each time you use your application in the ``prod`` environment (and therefore, +each time you deploy), you should run the following command: .. code-block:: bash @@ -532,7 +532,7 @@ the following change in your ``config_dev.yml`` file: )); Next, since Symfony is no longer generating these assets for you, you'll -need to dump them manually. To do so, run the following: +need to dump them manually. To do so, run the following command: .. code-block:: bash @@ -547,8 +547,8 @@ assets will be regenerated automatically *as they change*: $ php app/console assetic:watch -The ``assetic:watch`` command was introduced in AsseticBundle 2.4. In prior -versions, you had to use the ``--watch`` option of the ``assetic:dump`` +The ``assetic:watch`` command was introduced in AsseticBundle 2.4. In prior +versions, you had to use the ``--watch`` option of the ``assetic:dump`` command for the same behavior. Since running this command in the ``dev`` environment may generate a bunch diff --git a/cookbook/assetic/jpeg_optimize.rst b/cookbook/assetic/jpeg_optimize.rst index 7955771ca02..d3aebeb9e94 100644 --- a/cookbook/assetic/jpeg_optimize.rst +++ b/cookbook/assetic/jpeg_optimize.rst @@ -13,8 +13,9 @@ for your end users. Using Jpegoptim --------------- -`Jpegoptim`_ is a utility for optimizing JPEG files. To use it with Assetic, -add the following to the Assetic config: +`Jpegoptim`_ is a utility for optimizing JPEG files. To use it with Assetic, make +sure to have it already installed on your system and then, configure its location +using the ``bin`` option of the ``jpegoptim`` filter: .. configuration-block:: @@ -46,11 +47,6 @@ add the following to the Assetic config: ), )); -.. note:: - - Notice that to use jpegoptim, you must have it already installed on your - system. The ``bin`` option points to the location of the compiled binary. - It can now be used from a template: .. configuration-block:: @@ -74,9 +70,9 @@ It can now be used from a template: Removing all EXIF Data ~~~~~~~~~~~~~~~~~~~~~~ -By default, running this filter only removes some of the meta information -stored in the file. Any EXIF data and comments are not removed, but you can -remove these by using the ``strip_all`` option: +By default, the ``jpegoptim`` filter removes some of the meta information stored +in the image. To remove all EXIF data and comments, set the ``strip_all`` option +to ``true``: .. configuration-block:: @@ -111,13 +107,13 @@ remove these by using the ``strip_all`` option: ), )); -Lowering maximum Quality +Lowering Maximum Quality ~~~~~~~~~~~~~~~~~~~~~~~~ -The quality level of the JPEG is not affected by default. You can gain -further file size reductions by setting the max quality setting lower than -the current level of the images. This will of course be at the expense of -image quality: +By default, the ``jpegoptim`` filter doesn't alter the quality level of the JPEG +image. Use the ``max`` option to configure the maximum quality setting (in a +scale of ``0`` to ``100``). The reduction in the image file size will of course +be at the expense of its quality: .. configuration-block:: @@ -157,7 +153,7 @@ Shorter Syntax: Twig Function If you're using Twig, it's possible to achieve all of this with a shorter syntax by enabling and using a special Twig function. Start by adding the -following config: +following configuration: .. configuration-block:: @@ -206,7 +202,8 @@ The Twig template can now be changed to the following: Example -You can specify the output directory in the config in the following way: +You can also specify the output directory for images in the Assetic configuration +file: .. configuration-block:: diff --git a/cookbook/assetic/uglifyjs.rst b/cookbook/assetic/uglifyjs.rst index 8b0ddd66028..533a005bd10 100644 --- a/cookbook/assetic/uglifyjs.rst +++ b/cookbook/assetic/uglifyjs.rst @@ -16,43 +16,48 @@ talked about briefly. Install UglifyJS ---------------- -UglifyJS is available as an `Node.js`_ npm module and can be installed using -npm. First, you need to `install Node.js`_. Afterwards you can install UglifyJS -using npm: +UglifyJS is available as a `Node.js`_ module. First, you need to `install Node.js`_ +and then, decide the installation method: global or local. + +Global Installation +~~~~~~~~~~~~~~~~~~~ + +The global installation method makes all your projects use the very same UglifyJS +version, which simplifies its maintenance. Open your command console and execute +the following command (you may need to run it as a root user): .. code-block:: bash $ npm install -g uglify-js -This command will install UglifyJS globally and you may need to run it as -a root user. +Now you can execute the global ``uglifyjs`` command anywhere on your system: -.. note:: +.. code-block:: bash - It's also possible to install UglifyJS inside your project only. To do - this, install it without the ``-g`` option and specify the path where - to put the module: + $ uglifyjs --help - .. code-block:: bash +Local Installation +~~~~~~~~~~~~~~~~~~ - $ cd /path/to/symfony - $ mkdir app/Resources/node_modules - $ npm install uglify-js --prefix app/Resources +It's also possible to install UglifyJS inside your project only, which is useful +when your project requires an specific UglifyJS version. To do this, install it +without the ``-g`` option and specify the path where to put the module: - It is recommended that you install UglifyJS in your ``app/Resources`` folder - and add the ``node_modules`` folder to version control. Alternatively, - you can create an npm `package.json`_ file and specify your dependencies - there. +.. code-block:: bash -Depending on your installation method, you should either be able to execute -the ``uglifyjs`` executable globally, or execute the physical file that lives -in the ``node_modules`` directory: + $ cd /path/to/your/symfony/project + $ npm install uglify-js --prefix app/Resources -.. code-block:: bash +It is recommended that you install UglifyJS in your ``app/Resources`` folder and +add the ``node_modules`` folder to version control. Alternatively, you can create +an npm `package.json`_ file and specify your dependencies there. - $ uglifyjs --help +Now you can execute the ``uglifyjs`` command that lives in the ``node_modules`` +directory: - $ ./app/Resources/node_modules/.bin/uglifyjs --help +.. code-block:: bash + + $ "./app/Resources/node_modules/.bin/uglifyjs" --help Configure the ``uglifyjs2`` Filter ---------------------------------- @@ -96,8 +101,7 @@ your JavaScripts: .. note:: The path where UglifyJS is installed may vary depending on your system. - To find out where npm stores the ``bin`` folder, you can use the following - command: + To find out where npm stores the ``bin`` folder, execute the following command: .. code-block:: bash @@ -154,8 +158,8 @@ can configure its location using the ``node`` key: Minify your Assets ------------------ -In order to use UglifyJS on your assets, you need to apply it to them. Since -your assets are a part of the view layer, this work is done in your templates: +In order to apply UglifyJS on your assets, add the ``filter`` option in the +asset tags of your templates to tell Assetic to use the ``uglifyjs2`` filter: .. configuration-block:: @@ -178,8 +182,7 @@ your assets are a part of the view layer, this work is done in your templates: The above example assumes that you have a bundle called AppBundle and your JavaScript files are in the ``Resources/public/js`` directory under your - bundle. This isn't important however - you can include your JavaScript - files no matter where they are. + bundle. However you can include your JavaScript files no matter where they are. With the addition of the ``uglifyjs2`` filter to the asset tags above, you should now see minified JavaScripts coming over the wire much faster. @@ -216,12 +219,9 @@ and :ref:`dump your assetic assets `. .. tip:: - Instead of adding the filter to the asset tags, you can also globally - enable it by adding the ``apply_to`` attribute to the filter configuration, for - example in the ``uglifyjs2`` filter ``apply_to: "\.js$"``. To only have - the filter applied in production, add this to the ``config_prod`` file - rather than the common config file. For details on applying filters by - file extension, see :ref:`cookbook-assetic-apply-to`. + Instead of adding the filters to the asset tags, you can also configure which + filters to apply for each file in your application configuration file. + See :ref:`cookbook-assetic-apply-to` for more details. Install, Configure and Use UglifyCSS ------------------------------------ @@ -231,8 +231,13 @@ the node package is installed: .. code-block:: bash + # global installation $ npm install -g uglifycss + # local installation + $ cd /path/to/your/symfony/project + $ npm install uglifycss --prefix app/Resources + Next, add the configuration for this filter: .. configuration-block:: diff --git a/cookbook/assetic/yuicompressor.rst b/cookbook/assetic/yuicompressor.rst index 356b67b6071..dccb8351a56 100644 --- a/cookbook/assetic/yuicompressor.rst +++ b/cookbook/assetic/yuicompressor.rst @@ -4,19 +4,15 @@ How to Minify JavaScripts and Stylesheets with YUI Compressor ============================================================= -Yahoo! provides an excellent utility for minifying JavaScripts and stylesheets -so they travel over the wire faster, the `YUI Compressor`_. Thanks to Assetic, -you can take advantage of this tool very easily. - .. caution:: - The YUI Compressor is `no longer maintained by Yahoo`_ but by an independent - volunteer. Moreover, Yahoo has decided to `stop all new development on YUI`_ - and to move to other modern alternatives such as Node.js. + The YUI Compressor is `no longer maintained by Yahoo`_. That's why you are + **strongly advised to avoid using YUI utilities** unless strictly necessary. + Read :doc:`/cookbook/assetic/uglifyjs` for a modern and up-to-date alternative. - That's why you are **strongly advised** to avoid using YUI utilities unless - strictly necessary. Read :doc:`/cookbook/assetic/uglifyjs` for a modern and - up-to-date alternative. +Yahoo! provides an excellent utility for minifying JavaScripts and stylesheets +so they travel over the wire faster, the `YUI Compressor`_. Thanks to Assetic, +you can take advantage of this tool very easily. Download the YUI Compressor JAR ------------------------------- @@ -170,4 +166,3 @@ apply this filter when debug mode is off. .. _`YUI Compressor`: http://developer.yahoo.com/yui/compressor/ .. _`Download the JAR`: https://github.com/yui/yuicompressor/releases .. _`no longer maintained by Yahoo`: http://www.yuiblog.com/blog/2013/01/24/yui-compressor-has-a-new-owner/ -.. _`stop all new development on YUI`: http://yahooeng.tumblr.com/post/96098168666/important-announcement-regarding-yui diff --git a/cookbook/deployment/heroku.rst b/cookbook/deployment/heroku.rst index 44f33b46362..5a7b1f1028e 100644 --- a/cookbook/deployment/heroku.rst +++ b/cookbook/deployment/heroku.rst @@ -69,14 +69,20 @@ You are now ready to deploy the application as explained in the next section. Deploying your Application on Heroku ------------------------------------ -To deploy your application to Heroku, you must first create a ``Procfile``, -which tells Heroku what command to use to launch the web server with the -correct document root. After that, you will ensure that your Symfony application -runs the ``prod`` environment, and then you'll be ready to ``git push`` to -Heroku for your first deploy! +Before your first deploy, you need to do just three more things, which are explained +below: -Creating a Procfile -~~~~~~~~~~~~~~~~~~~ +#. :ref:`Create a Procfile ` + +#. :ref:`Set the Environment to prod ` + +#. :ref:`Push your Code to Heroku ` + +.. _heroku-procfile: +.. _creating-a-procfile: + +1) Create a Procfile +~~~~~~~~~~~~~~~~~~~~ By default, Heroku will launch an Apache web server together with PHP to serve applications. However, two special circumstances apply to Symfony applications: @@ -111,8 +117,11 @@ create the ``Procfile`` file and to add it to the repository: [master 35075db] Procfile for Apache and PHP 1 file changed, 1 insertion(+) -Setting the ``prod`` Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _heroku-setting-env-to-prod: +.. _setting-the-prod-environment: + +2) Set the Environment to prod +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ During a deploy, Heroku runs ``composer install --no-dev`` to install all of the dependencies your application requires. However, typical `post-install-commands`_ @@ -132,8 +141,11 @@ variables, you can issue a single command to prepare your app for a deployment: $ heroku config:set SYMFONY_ENV=prod -Pushing to Heroku -~~~~~~~~~~~~~~~~~ +.. _heroku-push-code: +.. _pushing-to-heroku: + +3) Push your Code to Heroku +~~~~~~~~~~~~~~~~~~~~~~~~~~~ Next up, it's finally time to deploy your application to Heroku. If you are doing this for the very first time, you may see a message such as the following: @@ -216,4 +228,4 @@ You should be seeing your Symfony application in your browser. .. _`Logplex`: https://devcenter.heroku.com/articles/logplex .. _`verified that the RSA key fingerprint is correct`: https://devcenter.heroku.com/articles/git-repository-ssh-fingerprints .. _`post-install-commands`: https://getcomposer.org/doc/articles/scripts.md -.. _`config vars`: https://devcenter.heroku.com/articles/config-vars \ No newline at end of file +.. _`config vars`: https://devcenter.heroku.com/articles/config-vars diff --git a/cookbook/security/custom_authentication_provider.rst b/cookbook/security/custom_authentication_provider.rst index f4a72350a56..b377b800f44 100644 --- a/cookbook/security/custom_authentication_provider.rst +++ b/cookbook/security/custom_authentication_provider.rst @@ -211,6 +211,7 @@ the ``PasswordDigest`` header value matches with the user's password. use Symfony\Component\Security\Core\Exception\NonceExpiredException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use AppBundle\Security\Authentication\Token\WsseUserToken; + use Symfony\Component\Security\Core\Util\StringUtils; class WsseProvider implements AuthenticationProviderInterface { @@ -269,7 +270,7 @@ the ``PasswordDigest`` header value matches with the user's password. // Validate Secret $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true)); - return $digest === $expected; + return StringUtils::equals($expected, $digest); } public function supports(TokenInterface $token) @@ -286,6 +287,14 @@ the ``PasswordDigest`` header value matches with the user's password. provider for the given token. In the case of multiple providers, the authentication manager will then move to the next provider in the list. +.. note:: + + The comparsion of the expected and the provided digests uses a constant + time comparison provided by the + :method:`Symfony\\Component\\Security\\Core\\Util\\StringUtils::equals` + method of the ``StringUtils`` class. It is used to mitigate possible + `timing attacks`_. + The Factory ----------- @@ -617,3 +626,4 @@ in the factory and consumed or passed to the other classes in the container. .. _`WSSE`: http://www.xml.com/pub/a/2003/12/17/dive.html .. _`nonce`: http://en.wikipedia.org/wiki/Cryptographic_nonce +.. _`timing attacks`: http://en.wikipedia.org/wiki/Timing_attack diff --git a/cookbook/security/entity_provider.rst b/cookbook/security/entity_provider.rst index 6e20607bc11..cf8987f0291 100644 --- a/cookbook/security/entity_provider.rst +++ b/cookbook/security/entity_provider.rst @@ -5,69 +5,52 @@ How to Load Security Users from the Database (the Entity Provider) ================================================================== -The security layer is one of the smartest tools of Symfony. It handles two -things: the authentication and the authorization processes. Although it may -seem difficult to understand how it works internally, the security system -is very flexible and allows you to integrate your application with any authentication -backend, like Active Directory, an OAuth server or a database. +Symfony's security system can load security users from anywhere - like a +database, via Active Directory or an OAuth server. This article will show +you how to load your users from the database via a Doctrine entity. Introduction ------------ -This article focuses on how to authenticate users against a database table -managed by a Doctrine entity class. The content of this cookbook entry is split -in three parts. The first part is about designing a Doctrine ``User`` entity -class and making it usable in the security layer of Symfony. The second part -describes how to easily authenticate a user with the Doctrine -:class:`Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider` object -bundled with the framework and some configuration. -Finally, the tutorial will demonstrate how to create a custom -:class:`Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider` object to -retrieve users from a database with custom conditions. - -.. sidebar:: Code along with the Example - - If you want to follow along with the example in this chapter, create - an AcmeUserBundle via: - - .. code-block:: bash - - $ php app/console generate:bundle --namespace=Acme/UserBundle +.. tip:: -The Data Model --------------- + Before you start, you should check out `FOSUserBundle`_. This external + bundle allows you to load users from the database (like you'll learn here) + *and* gives you built-in routes & controllers for things like login, + registration and forgot password. But, if you need to heavily customize + your user system *or* if you want to learn how things work, this tutorial + is even better. -For the purpose of this cookbook, the AcmeUserBundle bundle contains a ``User`` -entity class with the following fields: ``id``, ``username``, ``password``, -``email`` and ``isActive``. The ``isActive`` field tells whether or not the -user account is active. +Loading users via a Doctrine entity has 2 basic steps: -To make it shorter, the getter and setter methods for each have been removed to -focus on the most important methods that come from the -:class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. +#. :ref:`Create your User entity ` +#. :ref:`Configure security.yml to load from your entity ` -.. tip:: +Afterwards, you can learn more about :ref:`forbidding inactive users `, +:ref:`using a custom query ` +and :ref:`user serialization to the session ` - You can :ref:`generate the missing getter and setters ` - by running: +.. _security-crete-user-entity: +.. _the-data-model: - .. code-block:: bash +1) Create your User Entity +-------------------------- - $ php app/console doctrine:generate:entities Acme/UserBundle/Entity/User +For this entry, suppose that you already have a ``User`` entity inside an +``AppBundle`` with the following fields: ``id``, ``username``, ``password``, +``email`` and ``isActive``: .. code-block:: php - // src/Acme/UserBundle/Entity/User.php - namespace Acme\UserBundle\Entity; + // src/AppBundle/Entity/User.php + namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface; /** - * Acme\UserBundle\Entity\User - * - * @ORM\Table(name="acme_users") - * @ORM\Entity(repositoryClass="Acme\UserBundle\Entity\UserRepository") + * @ORM\Table(name="app_users") + * @ORM\Entity(repositoryClass="AppBundle\Entity\UserRepository") */ class User implements UserInterface, \Serializable { @@ -105,17 +88,11 @@ focus on the most important methods that come from the // $this->salt = md5(uniqid(null, true)); } - /** - * @inheritDoc - */ public function getUsername() { return $this->username; } - /** - * @inheritDoc - */ public function getSalt() { // you *may* need a real salt depending on your encoder @@ -123,32 +100,21 @@ focus on the most important methods that come from the return null; } - /** - * @inheritDoc - */ public function getPassword() { return $this->password; } - /** - * @inheritDoc - */ public function getRoles() { return array('ROLE_USER'); } - /** - * @inheritDoc - */ public function eraseCredentials() { } - /** - * @see \Serializable::serialize() - */ + /** @see \Serializable::serialize() */ public function serialize() { return serialize(array( @@ -160,9 +126,7 @@ focus on the most important methods that come from the )); } - /** - * @see \Serializable::unserialize() - */ + /** @see \Serializable::unserialize() */ public function unserialize($serialized) { list ( @@ -175,26 +139,27 @@ focus on the most important methods that come from the } } -.. note:: +To make things shorter, some of the getter and setter methods aren't shown. +But you can :ref:`generate ` these +by running: + +.. code-block:: bash - If you choose to implement - :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`, - you determine yourself which properties need to be compared to distinguish - your user objects. + $ php app/console doctrine:generate:entities AppBundle/Entity/User -.. tip:: +Next, make sure to :ref:`create the database table `: - :ref:`Generate the database table ` - for your ``User`` entity by running: +.. code-block:: bash - .. code-block:: bash + $ php app/console doctrine:schema:update --force - $ php app/console doctrine:schema:update --force +What's this UserInterface? +~~~~~~~~~~~~~~~~~~~~~~~~~~ -In order to use an instance of the ``AcmeUserBundle:User`` class in the Symfony -security layer, the entity class must implement the +So far, this is just a normal entity. But in order to use this class in the +security system, it must implement :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. This -interface forces the class to implement the five following methods: +forces the class to have the five following methods: * :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getRoles` * :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getPassword` @@ -202,72 +167,33 @@ interface forces the class to implement the five following methods: * :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getUsername` * :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::eraseCredentials` -For more details on each of these, see :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. - -.. sidebar:: What is the importance of serialize and unserialize? - - The :phpclass:`Serializable` interface and its ``serialize`` and ``unserialize`` - methods have been added to allow the ``User`` class to be serialized - to the session. This may or may not be needed depending on your setup, - but it's probably a good idea. The ``id`` is the most important value - that needs to be serialized because the - :method:`Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider::refreshUser` - method reloads the user on each request by using the ``id``. In practice, - this means that the User object is reloaded from the database on each - request using the ``id`` from the serialized object. This makes sure - all of the User's data is fresh. - - Symfony also uses the ``username``, ``salt``, and ``password`` to verify - that the User has not changed between requests. Failing to serialize - these may cause you to be logged out on each request. If your User implements - :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`, - then instead of these properties being checked, your ``isEqualTo`` method - is simply called, and you can check whatever properties you want. Unless - you understand this, you probably *won't* need to implement this interface - or worry about it. - -Below is an export of the ``User`` table from MySQL with user ``admin`` and -password ``admin`` (which has been encoded). For details on how to create -user records and encode their password, see :ref:`book-security-encoding-user-password`. +To learn more about each of these, see :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. -.. code-block:: bash +What do the serialize and unserialize Methods do? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - $ mysql> SELECT * FROM acme_users; - +----+----------+------------------------------------------+--------------------+-----------+ - | id | username | password | email | is_active | - +----+----------+------------------------------------------+--------------------+-----------+ - | 1 | admin | d033e22ae348aeb5660fc2140aec35850c4da997 | admin@example.com | 1 | - +----+----------+------------------------------------------+--------------------+-----------+ - -The next part will focus on how to authenticate one of these users -thanks to the Doctrine entity user provider and a couple of lines of -configuration. +At the end of each request, the User object is serialized to the session. +On the next request, it's unserialized. To help PHP do this correctly, you +need to implement ``Serializable``. But you don't need to serialize everything: +you only need a few fields (the ones shown above plus a few extra if you +decide to implement :ref:`AdvancedUserInterface `). +On each request, the ``id`` is used to query for a fresh ``User`` object +from the database. -.. sidebar:: Do you need to use a Salt? +Want to know more? See :ref:`cookbook-security-serialize-equatable`. - Yes. Hashing a password with a salt is a necessary step so that encoded - passwords can't be decoded. However, some encoders - like Bcrypt - have - a built-in salt mechanism. If you configure ``bcrypt`` as your encoder - in ``security.yml`` (see the next section), then ``getSalt()`` should - return ``null``, so that Bcrypt generates the salt itself. +.. _authenticating-someone-against-a-database: +.. _security-config-entity-provider: - However, if you use an encoder that does *not* have a built-in salting - ability (e.g. ``sha512``), you *must* (from a security perspective) generate - your own, random salt, store it on a ``salt`` property that is saved to - the database, and return it from ``getSalt()``. Some of the code needed - is commented out in the above example. +2) Configure Security to load from your Entity +---------------------------------------------- -Authenticating Someone against a Database ------------------------------------------ +Now that you have a ``User`` entity that implements ``UserInterface``, you +just need to tell Symfony's security system about it in ``security.yml``. -Authenticating a Doctrine user against the database with the Symfony security -layer is a piece of cake. Everything resides in the configuration of the -:doc:`SecurityBundle ` stored in the -``app/config/security.yml`` file. - -Below is an example of configuration where the user will enter their -username and password via HTTP basic authentication. That information will -then be checked against your User entity records in the database: +In this example, the user will enter their username and password via HTTP +basic authentication. Symfony will query for a ``User`` entity matching +the username and then check the password (more on passwords in a moment): .. configuration-block:: @@ -276,45 +202,46 @@ then be checked against your User entity records in the database: # app/config/security.yml security: encoders: - Acme\UserBundle\Entity\User: + AppBundle\Entity\User: algorithm: bcrypt - role_hierarchy: - ROLE_ADMIN: ROLE_USER - ROLE_SUPER_ADMIN: [ ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ] + # ... providers: - administrators: - entity: { class: AcmeUserBundle:User, property: username } + our_db_provider: + entity: + class: AppBundle:User + property: username + # if you're using multiple entity managers + # manager_name: customer firewalls: - admin_area: - pattern: ^/admin + default: + pattern: ^/ http_basic: ~ + provider: our_db_provider - access_control: - - { path: ^/admin, roles: ROLE_ADMIN } + # ... .. code-block:: xml - - ROLE_USER - ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH + - - + + - + - - + + .. code-block:: php @@ -322,149 +249,87 @@ then be checked against your User entity records in the database: // app/config/security.php $container->loadFromExtension('security', array( 'encoders' => array( - 'Acme\UserBundle\Entity\User' => array( + 'AppBundle\Entity\User' => array( 'algorithm' => 'bcrypt', ), ), - 'role_hierarchy' => array( - 'ROLE_ADMIN' => 'ROLE_USER', - 'ROLE_SUPER_ADMIN' => array('ROLE_USER', 'ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'), - ), + // ... 'providers' => array( - 'administrator' => array( + 'our_db_provider' => array( 'entity' => array( - 'class' => 'AcmeUserBundle:User', + 'class' => 'AppBundle:User', 'property' => 'username', ), ), ), 'firewalls' => array( - 'admin_area' => array( - 'pattern' => '^/admin', + 'default' => array( + 'pattern' => '^/', 'http_basic' => null, + 'provider' => 'our_db_provider', ), ), - 'access_control' => array( - array('path' => '^/admin', 'role' => 'ROLE_ADMIN'), - ), + // ... )); -The ``encoders`` section associates the ``bcrypt`` password encoder to the entity -class. This means that Symfony will expect the password that's stored in -the database to be encoded using this encoder. For details on how to create -a new User object with a properly encoded password, see the -:ref:`book-security-encoding-user-password` section of the security chapter. +First, the ``encoders`` section tells Symfony to expect that the passwords +in the database will be encoded using ``bcrypt``. Second, the ``providers`` +section creates a "user provider" called ``our_db_provider`` that knows to +query from your ``AppBundle:User`` entity by the ``username`` property. The +name ``our_db_provider`` isn't important: it just needs to match the value +of the ``provider`` key under your firewall. Or, if you don't set the ``provider`` +key under your firewall, the first "user provider" is automatically used. .. include:: /cookbook/security/_ircmaxwell_password-compat.rst.inc -The ``providers`` section defines an ``administrators`` user provider. A -user provider is a "source" of where users are loaded during authentication. -In this case, the ``entity`` keyword means that Symfony will use the Doctrine -entity user provider to load User entity objects from the database by using -the ``username`` unique field. In other words, this tells Symfony how to -fetch the user from the database before checking the password validity. +Creating your First User +~~~~~~~~~~~~~~~~~~~~~~~~ -.. note:: - - By default, the entity provider uses the default entity manager to fetch - user information from the database. If you - :doc:`use multiple entity managers `, - you can specify which manager to use with the ``manager_name`` option: - - .. configuration-block:: - - .. code-block:: yaml +To add users, you can implement a :doc:`registration form ` +or add some `fixtures`_. This is just a normal entity, so there's nothing +tricky, *except* that you need to encode each user's password. But don't +worry, Symfony gives you a service that will do this for you. See :ref:`security-encoding-password` +for details. - # app/config/config.yml - security: - # ... +Below is an export of the ``app_users`` table from MySQL with user ``admin`` +and password ``admin`` (which has been encoded). - providers: - administrators: - entity: - class: AcmeUserBundle:User - property: username - manager_name: customer - - # ... +.. code-block:: bash - .. code-block:: xml + $ mysql> SELECT * FROM app_users; + +----+----------+------------------------------------------+--------------------+-----------+ + | id | username | password | email | is_active | + +----+----------+------------------------------------------+--------------------+-----------+ + | 1 | admin | d033e22ae348aeb5660fc2140aec35850c4da997 | admin@example.com | 1 | + +----+----------+------------------------------------------+--------------------+-----------+ - - - - - +.. sidebar:: Do you need to a Salt property? - - - + If you use ``bcrypt``, no. Otherwise, yes. All passwords must be hashed + with a salt, but ``bcrypt`` does this internally. Since this tutorial + *does* use ``bcrypt``, the ``getSalt()`` method in ``User`` can just + return ``null`` (it's not used). If you use a different algorithm, you'll + need to uncomment the ``salt`` lines in the ``User`` entity and add a + persisted ``salt`` property. - - - +.. _security-advanced-user-interface: - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('security', array( - // ... - 'providers' => array( - 'administrator' => array( - 'entity' => array( - 'class' => 'AcmeUserBundle:User', - 'property' => 'username', - 'manager_name' => 'customer', - ), - ), - ), - // ... - )); - -Forbid inactive Users ---------------------- +Forbid Inactive Users (AdvancedUserInterface) +--------------------------------------------- If a User's ``isActive`` property is set to ``false`` (i.e. ``is_active`` -is 0 in the database), the user will still be able to login access the site -normally. To prevent "inactive" users from logging in, you'll need to do a -little more work. - -The easiest way to exclude inactive users is to implement the -:class:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface` -interface that takes care of checking the user's account status. -The :class:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface` -extends the :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface` -interface, so you just need to switch to the new interface in the ``AcmeUserBundle:User`` -entity class to benefit from simple and advanced authentication behaviors. - -The :class:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface` -interface adds four extra methods to validate the account status: - -* :method:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface::isAccountNonExpired` - checks whether the user's account has expired; -* :method:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface::isAccountNonLocked` - checks whether the user is locked; -* :method:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface::isCredentialsNonExpired` - checks whether the user's credentials (password) has expired; -* :method:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface::isEnabled` - checks whether the user is enabled. +is 0 in the database), the user will still be able to login to the site +normally. This is easily fixable. -For this example, the first three methods will return ``true`` whereas the -``isEnabled()`` method will return the boolean value in the ``isActive`` field. +To exclude inactive users, change your ``User`` clas to implement +:class:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface`. +This extends :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`, +so you only need the new interface:: -.. code-block:: php - - // src/Acme/UserBundle/Entity/User.php - namespace Acme\UserBundle\Entity; + // src/AppBundle/Entity/User.php - use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\AdvancedUserInterface; + // ... class User implements AdvancedUserInterface, \Serializable { @@ -489,73 +354,95 @@ For this example, the first three methods will return ``true`` whereas the { return $this->isActive; } + + // serialize and unserialize must be updated - see below + public function serialize() + { + return serialize(array( + // ... + $this->isActive + )); + } + public function unserialize($serialized) + { + list ( + // ... + $this->isActive + ) = unserialize($serialized); + } } -Now, if you try to authenticate as a user who's ``is_active`` database field -is set to 0, you won't be allowed. +The :class:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface` +interface adds four extra methods to validate the account status: + +* :method:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface::isAccountNonExpired` + checks whether the user's account has expired; +* :method:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface::isAccountNonLocked` + checks whether the user is locked; +* :method:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface::isCredentialsNonExpired` + checks whether the user's credentials (password) has expired; +* :method:`Symfony\\Component\\Security\\Core\\User\\AdvancedUserInterface::isEnabled` + checks whether the user is enabled. + +If *any* of these return ``false``, the user won't be allowed to login. You +can choose to have persisted properties for all of these, or whatever you +need (in this example, only ``isActive`` pulls from the database). + +So what's the difference between the methods? Each returns a slightly different +error message (and these can be translated when you render them in your login +template to customize them further). .. note:: - When using the ``AdvancedUserInterface``, you should also add any of - the properties used by these methods (like ``isActive()``) to the ``serialize()`` - method. If you *don't* do this, your user may not be deserialized correctly - from the session on each request. + If you use ``AdvancedUserInterface``, you also need to add any of the + properties used by these methods (like ``isActive``) to the ``serialize()`` + and ``unserialize()`` methods. If you *don't* do this, your user may + not be deserialized correctly from the session on each request. -The next session will focus on how to write a custom entity provider -to authenticate a user with their username or email address. +Congrats! Your database-loading security system is all setup! Next, add a +true :doc:`login form ` instead of HTTP Basic +or keep reading for other topics. -Authenticating Someone with a Custom Entity Provider ----------------------------------------------------- +.. _authenticating-someone-with-a-custom-entity-provider: -The next step is to allow a user to authenticate with their username or email -address as they are both unique in the database. Unfortunately, the native -entity provider is only able to handle a single property to fetch the user from -the database. +Using a Custom Query to Load the User +------------------------------------- -To accomplish this, create a custom entity provider that looks for a user -whose username *or* email field matches the submitted login username. -The good news is that a Doctrine repository object can act as an entity user -provider if it implements the -:class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`. This -interface comes with three methods to implement: ``loadUserByUsername($username)``, -``refreshUser(UserInterface $user)``, and ``supportsClass($class)``. For -more details, see :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`. +It would be great if a user could login with their username *or* email, as +both are unique in the database. Unfortunately, the native entity provider +is only able to handle querying via a single property on the user. -The code below shows the implementation of the -:class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface` in the -``UserRepository`` class:: +To do this, make your ``UserRepository`` implement a special +:class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`. This +interface requires three methods: ``loadUserByUsername($username)``, +``refreshUser(UserInterface $user)``, and ``supportsClass($class)``:: - // src/Acme/UserBundle/Entity/UserRepository.php - namespace Acme\UserBundle\Entity; + // src/AppBundle/Entity/UserRepository.php + namespace AppBundle\Entity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Doctrine\ORM\EntityRepository; - use Doctrine\ORM\NoResultException; class UserRepository extends EntityRepository implements UserProviderInterface { public function loadUserByUsername($username) { - $q = $this - ->createQueryBuilder('u') + $user = $this->createQueryBuilder('u') ->where('u.username = :username OR u.email = :email') ->setParameter('username', $username) ->setParameter('email', $username) - ->getQuery(); + ->getQuery() + ->getOneOrNullResult() - try { - // The Query::getSingleResult() method throws an exception - // if there is no record matching the criteria. - $user = $q->getSingleResult(); - } catch (NoResultException $e) { + if ($user) { $message = sprintf( - 'Unable to find an active admin AcmeUserBundle:User object identified by "%s".', + 'Unable to find an active admin AppBundle:User object identified by "%s".', $username ); - throw new UsernameNotFoundException($message, 0, $e); + throw new UsernameNotFoundException($message); } return $user; @@ -583,16 +470,15 @@ The code below shows the implementation of the } } +For more details on these methods, see :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`. + .. tip:: Don't forget to add the repository class to the :ref:`mapping definition of your entity `. -To finish the implementation, the configuration of the security layer must be -changed to tell Symfony to use the new custom entity provider instead of the -generic Doctrine entity provider. It's trivial to achieve by removing the -``property`` field in the ``security.providers.administrators.entity`` section -of the ``security.yml`` file. +To finish this, just remove the ``property`` key from the user provider in +``security.yml``: .. configuration-block:: @@ -602,8 +488,9 @@ of the ``security.yml`` file. security: # ... providers: - administrators: - entity: { class: AcmeUserBundle:User } + our_db_provider: + entity: + class: AppBundle:User # ... .. code-block:: xml @@ -612,8 +499,8 @@ of the ``security.yml`` file. - - + + @@ -625,18 +512,18 @@ of the ``security.yml`` file. $container->loadFromExtension('security', array( ..., 'providers' => array( - 'administrator' => array( + 'our_db_provider' => array( 'entity' => array( - 'class' => 'AcmeUserBundle:User', + 'class' => 'AppBundle:User', ), ), ), ..., )); -By doing this, the security layer will use an instance of ``UserRepository`` and -call its ``loadUserByUsername()`` method to fetch a user from the database -whether they filled in their username or email address. +This tells Symfony to *not* query automatically for the User. Instead, when +someone logs in, the ``loadUserByUsername()`` method on ``UserRepository`` +will be called. Managing Roles in the Database ------------------------------ @@ -664,15 +551,15 @@ about in this section. example, your roles will be ``ROLE_ADMIN`` or ``ROLE_USER`` instead of ``ADMIN`` or ``USER``. -In this example, the ``AcmeUserBundle:User`` entity class defines a -many-to-many relationship with a ``AcmeUserBundle:Role`` entity class. +In this example, the ``AppBundle:User`` entity class defines a +many-to-many relationship with a ``AppBundle:Role`` entity class. A user can be related to several roles and a role can be composed of one or more users. The previous ``getRoles()`` method now returns the list of related roles. Notice that ``__construct()`` and ``getRoles()`` methods have changed:: - // src/Acme/UserBundle/Entity/User.php - namespace Acme\UserBundle\Entity; + // src/AppBundle/Entity/User.php + namespace AppBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; // ... @@ -701,20 +588,20 @@ methods have changed:: } -The ``AcmeUserBundle:Role`` entity class defines three fields (``id``, +The ``AppBundle:Role`` entity class defines three fields (``id``, ``name`` and ``role``). The unique ``role`` field contains the role name (e.g. ``ROLE_ADMIN``) used by the Symfony security layer to secure parts of the application:: - // src/Acme/Bundle/UserBundle/Entity/Role.php - namespace Acme\UserBundle\Entity; + // src/AppBundle/Entity/Role.php + namespace AppBundle\Entity; use Symfony\Component\Security\Core\Role\RoleInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Table(name="acme_role") + * @ORM\Table(name="app_role") * @ORM\Entity() */ class Role implements RoleInterface @@ -762,7 +649,7 @@ For brevity, the getter and setter methods are hidden, but you can .. code-block:: bash - $ php app/console doctrine:generate:entities Acme/UserBundle/Entity/User + $ php app/console doctrine:generate:entities AppBundle/Entity/User Don't forget also to update your database schema: @@ -770,14 +657,14 @@ Don't forget also to update your database schema: $ php app/console doctrine:schema:update --force -This will create the ``acme_role`` table and a ``user_role`` that stores -the many-to-many relationship between ``acme_user`` and ``acme_role``. If +This will create the ``app_role`` table and a ``user_role`` that stores +the many-to-many relationship between ``app_user`` and ``app_role``. If you had one user linked to one role, your database might look something like this: .. code-block:: bash - $ mysql> SELECT * FROM acme_role; + $ mysql> SELECT * FROM app_role; +----+-------+------------+ | id | name | role | +----+-------+------------+ @@ -817,8 +704,8 @@ from the custom entity provider, you can use a Doctrine join to the roles relationship in the ``UserRepository::loadUserByUsername()`` method. This will fetch the user and their associated roles with a single query:: - // src/Acme/UserBundle/Entity/UserRepository.php - namespace Acme\UserBundle\Entity; + // src/AppBundle/Entity/UserRepository.php + namespace AppBundle\Entity; // ... @@ -842,7 +729,7 @@ fetch the user and their associated roles with a single query:: } The ``QueryBuilder::leftJoin()`` method joins and fetches related roles from -the ``AcmeUserBundle:User`` model class when a user is retrieved by their email +the ``AppBundle:User`` model class when a user is retrieved by their email address or username. .. _`cookbook-security-serialize-equatable`: @@ -855,9 +742,9 @@ the ``User`` class or how the User object is serialized or deserialized, then this section is for you. If not, feel free to skip this. Once the user is logged in, the entire User object is serialized into the -session. On the next request, the User object is deserialized. Then, value +session. On the next request, the User object is deserialized. Then, the value of the ``id`` property is used to re-query for a fresh User object from the -database. Finally, the fresh User object is compared in some way to the deserialized +database. Finally, the fresh User object is compared to the deserialized User object to make sure that they represent the same user. For example, if the ``username`` on the 2 User objects doesn't match for some reason, then the user will be logged out for security reasons. @@ -870,15 +757,17 @@ to the session. This may or may not be needed depending on your setup, but it's probably a good idea. In theory, only the ``id`` needs to be serialized, because the :method:`Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider::refreshUser` method refreshes the user on each request by using the ``id`` (as explained -above). However in practice, this means that the User object is reloaded from -the database on each request using the ``id`` from the serialized object. -This makes sure all of the User's data is fresh. - -Symfony also uses the ``username``, ``salt``, and ``password`` to verify -that the User has not changed between requests. Failing to serialize -these may cause you to be logged out on each request. If your User implements -the :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`, +above). This gives us a "fresh" User object. + +But Symfony also uses the ``username``, ``salt``, and ``password`` to verify +that the User has not changed between requests (it also calls your ``AdvancedUserInterface`` +methods if you implement it). Failing to serialize these may cause you to +be logged out on each request. If your User implements the +:class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`, then instead of these properties being checked, your ``isEqualTo`` method is simply called, and you can check whatever properties you want. Unless you understand this, you probably *won't* need to implement this interface or worry about it. + +.. _fixtures: http://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html +.. _FOSUserBundle: https://github.com/FriendsOfSymfony/FOSUserBundle diff --git a/cookbook/security/voters.rst b/cookbook/security/voters.rst index 236ff9c166e..835d15e409b 100644 --- a/cookbook/security/voters.rst +++ b/cookbook/security/voters.rst @@ -197,6 +197,36 @@ application configuration file with the following code. That's it! Now, when deciding whether or not a user should have access, the new voter will deny access to any user in the list of blacklisted IPs. +Note that the voters are only called, if any access is actually checked. So +you need at least something like + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + access_control: + - { path: ^/, role: IS_AUTHENTICATED_ANONYMOUSLY } + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // app/config/security.xml + $container->loadFromExtension('security', array( + 'access_control' => array( + array('path' => '^/', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'), + ), + )); + .. seealso:: For a more advanced usage see diff --git a/cookbook/testing/database.rst b/cookbook/testing/database.rst index 5a8709ac3db..04c8d07ee48 100644 --- a/cookbook/testing/database.rst +++ b/cookbook/testing/database.rst @@ -48,7 +48,8 @@ Suppose the class you want to test looks like this:: public function calculateTotalSalary($id) { - $employeeRepository = $this->entityManager->getRepository('AppBundle::Employee'); + $employeeRepository = $this->entityManager + ->getRepository('AppBundle:Employee'); $employee = $employeeRepository->find($id); return $employee->getSalary() + $employee->getBonus(); @@ -74,7 +75,8 @@ it's easy to pass a mock object within a test:: ->will($this->returnValue(1100)); // Now, mock the repository so it returns the mock of the employee - $employeeRepository = $this->getMockBuilder('\Doctrine\ORM\EntityRepository') + $employeeRepository = $this + ->getMockBuilder('\Doctrine\ORM\EntityRepository') ->disableOriginalConstructor() ->getMock(); $employeeRepository->expects($this->once()) @@ -82,7 +84,8 @@ it's easy to pass a mock object within a test:: ->will($this->returnValue($employee)); // Last, mock the EntityManager to return the mock of the repository - $entityManager = $this->getMockBuilder('\Doctrine\Common\Persistence\ObjectManager') + $entityManager = $this + ->getMockBuilder('\Doctrine\Common\Persistence\ObjectManager') ->disableOriginalConstructor() ->getMock(); $entityManager->expects($this->once()) diff --git a/cookbook/testing/profiling.rst b/cookbook/testing/profiling.rst index deb242498f2..0f822cc2079 100644 --- a/cookbook/testing/profiling.rst +++ b/cookbook/testing/profiling.rst @@ -21,7 +21,8 @@ the ``test`` environment):: { $client = static::createClient(); - // Enable the profiler for the next request (it does nothing if the profiler is not available) + // Enable the profiler for the next request + // (it does nothing if the profiler is not available) $client->enableProfiler(); $crawler = $client->request('GET', '/hello/Fabien'); diff --git a/images/components/http_kernel/01-workflow.png b/images/components/http_kernel/01-workflow.png index 44c32be53f6..78c155904ca 100644 Binary files a/images/components/http_kernel/01-workflow.png and b/images/components/http_kernel/01-workflow.png differ diff --git a/images/components/http_kernel/02-kernel-request.png b/images/components/http_kernel/02-kernel-request.png index f3ca215dcb5..b90aa35a3f3 100644 Binary files a/images/components/http_kernel/02-kernel-request.png and b/images/components/http_kernel/02-kernel-request.png differ diff --git a/images/components/http_kernel/03-kernel-request-response.png b/images/components/http_kernel/03-kernel-request-response.png index 817abec9024..5f4c4a188c4 100644 Binary files a/images/components/http_kernel/03-kernel-request-response.png and b/images/components/http_kernel/03-kernel-request-response.png differ diff --git a/images/components/http_kernel/04-resolve-controller.png b/images/components/http_kernel/04-resolve-controller.png index ae521aa4bcb..6283558ebd1 100644 Binary files a/images/components/http_kernel/04-resolve-controller.png and b/images/components/http_kernel/04-resolve-controller.png differ diff --git a/images/components/http_kernel/06-kernel-controller.png b/images/components/http_kernel/06-kernel-controller.png index 5327e4b30c4..95f1bcc4286 100644 Binary files a/images/components/http_kernel/06-kernel-controller.png and b/images/components/http_kernel/06-kernel-controller.png differ diff --git a/images/components/http_kernel/07-controller-arguments.png b/images/components/http_kernel/07-controller-arguments.png index a1c6ffc5269..66cdaa247eb 100644 Binary files a/images/components/http_kernel/07-controller-arguments.png and b/images/components/http_kernel/07-controller-arguments.png differ diff --git a/images/components/http_kernel/08-call-controller.png b/images/components/http_kernel/08-call-controller.png index bb740855580..877272b7c7c 100644 Binary files a/images/components/http_kernel/08-call-controller.png and b/images/components/http_kernel/08-call-controller.png differ diff --git a/images/components/http_kernel/09-controller-returns-response.png b/images/components/http_kernel/09-controller-returns-response.png index c1558476054..ae0d1d1d20a 100644 Binary files a/images/components/http_kernel/09-controller-returns-response.png and b/images/components/http_kernel/09-controller-returns-response.png differ diff --git a/images/components/http_kernel/10-kernel-view.png b/images/components/http_kernel/10-kernel-view.png index f8f64f39ce7..cdf3329e65a 100644 Binary files a/images/components/http_kernel/10-kernel-view.png and b/images/components/http_kernel/10-kernel-view.png differ diff --git a/images/components/http_kernel/11-kernel-exception.png b/images/components/http_kernel/11-kernel-exception.png index 3bb0e39a364..c380758e400 100644 Binary files a/images/components/http_kernel/11-kernel-exception.png and b/images/components/http_kernel/11-kernel-exception.png differ diff --git a/images/components/http_kernel/sub-request.png b/images/components/http_kernel/sub-request.png index 860909284b1..a26efe7ecd1 100644 Binary files a/images/components/http_kernel/sub-request.png and b/images/components/http_kernel/sub-request.png differ diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 2344879de26..9c1885d9996 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -100,6 +100,7 @@ Each part will be explained in the next section. entity: class: SecurityBundle:User property: username + # name of a non-default entity manager manager_name: ~ # Example custom provider diff --git a/reference/constraints/Length.rst b/reference/constraints/Length.rst index 8d345cdb79d..c207f3f6ac6 100644 --- a/reference/constraints/Length.rst +++ b/reference/constraints/Length.rst @@ -37,7 +37,7 @@ To verify that the ``firstName`` field length of a class is between "2" and min: 2 max: 50 minMessage: "Your first name must be at least {{ limit }} characters long" - maxMessage: "Your first name cannot be longer than {{ limit }} characters long" + maxMessage: "Your first name cannot be longer than {{ limit }} characters" .. code-block:: php-annotations @@ -53,7 +53,7 @@ To verify that the ``firstName`` field length of a class is between "2" and * min = 2, * max = 50, * minMessage = "Your first name must be at least {{ limit }} characters long", - * maxMessage = "Your first name cannot be longer than {{ limit }} characters long" + * maxMessage = "Your first name cannot be longer than {{ limit }} characters" * ) */ protected $firstName; @@ -73,7 +73,7 @@ To verify that the ``firstName`` field length of a class is between "2" and - + @@ -95,7 +95,7 @@ To verify that the ``firstName`` field length of a class is between "2" and 'min' => 2, 'max' => 50, 'minMessage' => 'Your first name must be at least {{ limit }} characters long', - 'maxMessage' => 'Your first name cannot be longer than {{ limit }} characters long', + 'maxMessage' => 'Your first name cannot be longer than {{ limit }} characters', ))); } } diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst index e294d9ac464..f511574d25b 100644 --- a/reference/forms/types/datetime.rst +++ b/reference/forms/types/datetime.rst @@ -62,9 +62,7 @@ for more details. date_widget ~~~~~~~~~~~ -**type**: ``string`` **default**: ``choice`` - -Defines the ``widget`` option for the :doc:`date ` type +.. include:: /reference/forms/types/options/date_widget_description.rst.inc .. include:: /reference/forms/types/options/days.rst.inc diff --git a/reference/forms/types/options/date_widget.rst.inc b/reference/forms/types/options/date_widget.rst.inc index 2921bc57b55..028c10e5279 100644 --- a/reference/forms/types/options/date_widget.rst.inc +++ b/reference/forms/types/options/date_widget.rst.inc @@ -1,14 +1,4 @@ widget ~~~~~~ -**type**: ``string`` **default**: ``choice`` - -The basic way in which this field should be rendered. Can be one of the following: - -* ``choice``: renders three select inputs. The order of the selects is defined - in the `format`_ option. - -* ``text``: renders a three field input of type ``text`` (month, day, year). - -* ``single_text``: renders a single input of type ``date``. User's input is - validated based on the `format`_ option. +.. include:: /reference/forms/types/options/date_widget_description.rst.inc diff --git a/reference/forms/types/options/date_widget_description.rst.inc b/reference/forms/types/options/date_widget_description.rst.inc new file mode 100644 index 00000000000..865af8472ac --- /dev/null +++ b/reference/forms/types/options/date_widget_description.rst.inc @@ -0,0 +1,11 @@ +**type**: ``string`` **default**: ``choice`` + +The basic way in which this field should be rendered. Can be one of the following: + +* ``choice``: renders three select inputs. The order of the selects is defined + in the `format`_ option. + +* ``text``: renders a three field input of type ``text`` (month, day, year). + +* ``single_text``: renders a single input of type ``date``. User's input is + validated based on the `format`_ option.