diff --git a/book/_security-2012-6431.rst.inc b/book/_security-2012-6431.rst.inc deleted file mode 100644 index 5d9137bba63..00000000000 --- a/book/_security-2012-6431.rst.inc +++ /dev/null @@ -1,9 +0,0 @@ -.. note:: - - Since Symfony 2.0.20/2.1.5, the Twig ``render`` tag now takes an absolute url - instead of a controller logical path. This fixes an important security - issue (`CVE-2012-6431`_) reported on the official blog. If your application - uses an older version of Symfony or still uses the previous ``render`` tag - syntax, you should upgrade as soon as possible. - -.. _`CVE-2012-6431`: http://symfony.com/blog/security-release-symfony-2-0-20-and-2-1-5-released \ No newline at end of file diff --git a/book/controller.rst b/book/controller.rst index e8405f7ab4b..cd0a384f0ad 100644 --- a/book/controller.rst +++ b/book/controller.rst @@ -141,7 +141,7 @@ Mapping a URL to a Controller ----------------------------- The new controller returns a simple HTML page. To actually view this page -in your browser, you need to create a route, which maps a specific URL pattern +in your browser, you need to create a route, which maps a specific URL path to the controller: .. configuration-block:: @@ -150,13 +150,13 @@ to the controller: # app/config/routing.yml hello: - pattern: /hello/{name} - defaults: { _controller: AcmeHelloBundle:Hello:index } + path: /hello/{name} + defaults: { _controller: AcmeHelloBundle:Hello:index } .. code-block:: xml - + AcmeHelloBundle:Hello:index @@ -229,13 +229,13 @@ example: # app/config/routing.yml hello: - pattern: /hello/{first_name}/{last_name} - defaults: { _controller: AcmeHelloBundle:Hello:index, color: green } + path: /hello/{first_name}/{last_name} + defaults: { _controller: AcmeHelloBundle:Hello:index, color: green } .. code-block:: xml - + AcmeHelloBundle:Hello:index green @@ -325,7 +325,7 @@ working with forms, for example:: { $form = $this->createForm(...); - $form->bindRequest($request); + $form->bind($request); // ... } @@ -632,8 +632,8 @@ from any controller:: // in another controller for another request $foo = $session->get('foo'); - // set the user locale - $session->setLocale('fr'); + // use a default value if the key doesn't exist + $filters = $session->get('filters', array()); These attributes will remain on the user for the remainder of that user's session. @@ -655,14 +655,11 @@ For example, imagine you're processing a form submit:: { $form = $this->createForm(...); - $form->bindRequest($this->getRequest()); + $form->bind($this->getRequest()); if ($form->isValid()) { // do some sort of processing - $this->get('session')->setFlash( - 'notice', - 'Your changes were saved!' - ); + $this->get('session')->getFlashBag()->add('notice', 'Your changes were saved!'); return $this->redirect($this->generateUrl(...)); } @@ -681,19 +678,19 @@ the ``notice`` message: .. code-block:: html+jinja - {% if app.session.hasFlash('notice') %} + {% for flashMessage in app.session.flashbag.get('notice') %}
- {{ app.session.flash('notice') }} + {{ flashMessage }}
- {% endif %} + {% endfor %} .. code-block:: html+php - hasFlash('notice')): ?> + getFlashBag()->get('notice') as $message): ?>
- getFlash('notice') ?> + $message
" ?> - + By design, flash messages are meant to live for exactly one request (they're "gone in a flash"). They're designed to be used across redirects exactly as @@ -727,6 +724,11 @@ headers and content that's sent back to the client:: header names are normalized so that using ``Content-Type`` is equivalent to ``content-type`` or even ``content_type``. +.. tip:: + + There is also a special :class:`Symfony\\Component\\HttpFoundation\\JsonResponse` + class that helps return JSON responses. See :ref:`component-http-foundation-json-response`. + .. index:: single: Controller; Request object diff --git a/book/doctrine.rst b/book/doctrine.rst index a21a39e152d..b45d24fd3df 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -44,21 +44,23 @@ Configuring the Database Before you really begin, you'll need to configure your database connection information. By convention, this information is usually configured in an -``app/config/parameters.ini`` file: +``app/config/parameters.yml`` file: -.. code-block:: ini +.. code-block:: yaml - ; app/config/parameters.ini - [parameters] - database_driver = pdo_mysql - database_host = localhost - database_name = test_project - database_user = root - database_password = password + # app/config/parameters.yml + parameters: + database_driver: pdo_mysql + database_host: localhost + database_name: test_project + database_user: root + database_password: password + + # ... .. note:: - Defining the configuration via ``parameters.ini`` is just a convention. + Defining the configuration via ``parameters.yml`` is just a convention. The parameters defined in that file are referenced by the main configuration file when setting up Doctrine: @@ -479,7 +481,7 @@ of the bundle: $product->setPrice('19.99'); $product->setDescription('Lorem ipsum dolor'); - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $em->persist($product); $em->flush(); @@ -626,7 +628,7 @@ you have a route that maps a product id to an update action in a controller:: public function updateAction($id) { - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $product = $em->getRepository('AcmeStoreBundle:Product')->find($id); if (!$product) { @@ -692,7 +694,7 @@ Imagine that you want to query for products, but only return products that cost more than ``19.99``, ordered from cheapest to most expensive. From inside a controller, do the following:: - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $query = $em->createQuery( 'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC' )->setParameter('price', '19.99'); @@ -868,7 +870,7 @@ ordered alphabetically. You can use this new method just like the default finder methods of the repository:: - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $products = $em->getRepository('AcmeStoreBundle:Product') ->findAllOrderedByName(); @@ -1101,7 +1103,7 @@ Now you can see this new code in action! Imagine you're inside a controller:: // relate this product to the category $product->setCategory($category); - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $em->persist($category); $em->persist($product); $em->flush(); @@ -1507,12 +1509,7 @@ Some notable or interesting tasks include: .. code-block:: bash - $ php app/console doctrine:ensure-production-settings --no-debug --env=prod - - .. caution:: - - Don't forget to add the ``--no-debug`` switch, because the debug flag is - always set to true, even if the environment is set to ``prod``. + $ php app/console doctrine:ensure-production-settings --env=prod * ``doctrine:mapping:import`` - allows Doctrine to introspect an existing database and create mapping information. For more information, see @@ -1563,14 +1560,14 @@ For more information about Doctrine, see the *Doctrine* section of the .. _`Doctrine`: http://www.doctrine-project.org/ .. _`MongoDB`: http://www.mongodb.org/ -.. _`Basic Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html -.. _`Query Builder`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/query-builder.html -.. _`Doctrine Query Language`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/dql-doctrine-query-language.html -.. _`Association Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/association-mapping.html +.. _`Basic Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html +.. _`Query Builder`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html +.. _`Doctrine Query Language`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html +.. _`Association Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html .. _`DateTime`: http://php.net/manual/en/class.datetime.php -.. _`Mapping Types Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#doctrine-mapping-types -.. _`Property Mapping documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#property-mapping -.. _`Lifecycle Events documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/events.html#lifecycle-events -.. _`Reserved SQL keywords documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#quoting-reserved-words -.. _`Persistent classes`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#persistent-classes -.. _`Property Mapping`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#property-mapping +.. _`Mapping Types Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#doctrine-mapping-types +.. _`Property Mapping documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping +.. _`Lifecycle Events documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events +.. _`Reserved SQL keywords documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words +.. _`Persistent classes`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#persistent-classes +.. _`Property Mapping`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping diff --git a/book/forms.rst b/book/forms.rst index 6eb7c18807a..272eae5e86e 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -186,8 +186,12 @@ it into a format that's suitable for being rendered in an HTML form. ``task`` property via the ``getTask()`` and ``setTask()`` methods on the ``Task`` class. Unless a property is public, it *must* have a "getter" and "setter" method so that the form component can get and put data onto the - property. For a Boolean property, you can use an "isser" method (e.g. - ``isPublished()``) instead of a getter (e.g. ``getPublished()``). + property. For a Boolean property, you can use an "isser" or "hasser" method + (e.g. ``isPublished()`` or ``hasReminder()``) instead of a getter (e.g. + ``getPublished()`` or ``getReminder()``). + + .. versionadded:: 2.1 + Support for "hasser" methods was added in Symfony 2.1. .. index:: single: Forms; Handling form submission @@ -213,8 +217,8 @@ controller:: ->add('dueDate', 'date') ->getForm(); - if ($request->getMethod() == 'POST') { - $form->bindRequest($request); + if ($request->isMethod('POST')) { + $form->bind($request); if ($form->isValid()) { // perform some action, such as saving the task to the database @@ -226,13 +230,18 @@ controller:: // ... } +.. versionadded:: 2.1 + The ``bind`` method was made more flexible in Symfony 2.1. It now accepts + the raw client data (same as before) or a Symfony Request object. This + is preferred over the deprecated ``bindRequest`` method. + Now, when submitting the form, the controller binds the submitted data to the form, which translates that data back to the ``task`` and ``dueDate`` properties -of the ``$task`` object. This all happens via the ``bindRequest()`` method. +of the ``$task`` object. This all happens via the ``bind()`` method. .. note:: - As soon as ``bindRequest()`` is called, the submitted data is transferred + As soon as ``bind()`` is called, the submitted data is transferred to the underlying object immediately. This happens regardless of whether or not the underlying data is actually valid. @@ -384,19 +393,63 @@ you'll need to specify which validation group(s) your form should use:: ))->add(...); If you're creating :ref:`form classes` (a -good practice), then you'll need to add the following to the ``getDefaultOptions()`` +good practice), then you'll need to add the following to the ``setDefaultOptions()`` method:: - public function getDefaultOptions(array $options) + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'validation_groups' => array('registration'), - ); + )); } In both of these cases, *only* the ``registration`` validation group will be used to validate the underlying object. +Groups based on Submitted Data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ability to specify a callback or Closure in ``validation_groups`` + is new to version 2.1 + +If you need some advanced logic to determine the validation groups (e.g. +based on submitted data), you can set the ``validation_groups`` option +to an array callback, or a ``Closure``:: + + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'validation_groups' => array('Acme\\AcmeBundle\\Entity\\Client', 'determineValidationGroups'), + )); + } + +This will call the static method ``determineValidationGroups()`` on the +``Client`` class after the form is bound, but before validation is executed. +The Form object is passed as an argument to that method (see next example). +You can also define whole logic inline by using a Closure:: + + use Symfony\Component\Form\FormInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'validation_groups' => function(FormInterface $form) { + $data = $form->getData(); + if (Entity\Client::TYPE_PERSON == $data->getType()) { + return array('person'); + } else { + return array('company'); + } + }, + )); + } + .. index:: single: Forms; Built-in field types @@ -513,7 +566,7 @@ the correct values of a number of field options. When these options are set, the field will be rendered with special HTML attributes that provide for HTML5 client-side validation. However, it - doesn't generate the equivalent server-side constraints (e.g. ``Assert\MaxLength``). + doesn't generate the equivalent server-side constraints (e.g. ``Assert\Length``). And though you'll need to manually add your server-side validation, these field type options can then be guessed from that information. @@ -523,8 +576,8 @@ the correct values of a number of field options. validation will automatically match your validation rules. * ``max_length``: If the field is some sort of text field, then the ``max_length`` - option can be guessed from the validation constraints (if ``MaxLength`` or ``Max`` - is used) or from the Doctrine metadata (via the field's length). + option can be guessed from the validation constraints (if ``Length`` or + ``Range`` is used) or from the Doctrine metadata (via the field's length). .. note:: @@ -747,11 +800,11 @@ that will house the logic for building the task form:: namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; class TaskType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('widget' => 'single_text')); @@ -796,11 +849,13 @@ the choice is ultimately up to you. good idea to explicitly specify the ``data_class`` option by adding the following to your form type class:: - public function getDefaultOptions(array $options) + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Task', - ); + )); } .. tip:: @@ -811,12 +866,14 @@ the choice is ultimately up to you. In cases where you need extra fields in the form (for example: a "do you agree with these terms" checkbox) that will not be mapped to the underlying - object, you need to set the property_path option to ``false``:: + object, you need to set the ``mapped`` option to ``false``:: + + use Symfony\Component\Form\FormBuilderInterface; - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('task'); - $builder->add('dueDate', null, array('property_path' => false)); + $builder->add('dueDate', null, array('mapped' => false)); } Additionally, if there are any fields on the form that aren't included in @@ -841,7 +898,7 @@ to be persisted via Doctrine (i.e. you've added it after a form submission can be done when the form is valid:: if ($form->isValid()) { - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $em->persist($task); $em->flush(); @@ -923,20 +980,21 @@ create a form class so that a ``Category`` object can be modified by the user:: namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; class CategoryType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('name'); } - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Category', - ); + )); } public function getName() @@ -952,7 +1010,9 @@ class: .. code-block:: php - public function buildForm(FormBuilder $builder, array $options) + use Symfony\Component\Form\FormBuilderInterface; + + public function buildForm(FormBuilderInterface $builder, array $options) { // ... @@ -960,7 +1020,18 @@ class: } The fields from ``CategoryType`` can now be rendered alongside those from -the ``TaskType`` class. Render the ``Category`` fields in the same way +the ``TaskType`` class. To activate validation on CategoryType, add +the ``cascade_validation`` option to ``TaskType``:: + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Acme\TaskBundle\Entity\Task', + 'cascade_validation' => true, + )); + } + +Render the ``Category`` fields in the same way as the original ``Task`` fields: .. configuration-block:: @@ -1039,7 +1110,7 @@ do this, create a new template file that will store the new markup: .. code-block:: html+jinja {# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #} - {% block field_row %} + {% block form_row %} {% spaceless %}
{{ form_label(form) }} @@ -1047,19 +1118,19 @@ do this, create a new template file that will store the new markup: {{ form_widget(form) }}
{% endspaceless %} - {% endblock field_row %} + {% endblock form_row %} .. code-block:: html+php - +
label($form, $label) ?> errors($form) ?> widget($form, $parameters) ?>
-The ``field_row`` form fragment is used when rendering most fields via the -``form_row`` function. To tell the form component to use your new ``field_row`` +The ``form_row`` form fragment is used when rendering most fields via the +``form_row`` function. To tell the form component to use your new ``form_row`` fragment defined above, add the following to the top of the template that renders the form: @@ -1085,8 +1156,8 @@ renders the form: The ``form_theme`` tag (in Twig) "imports" the fragments defined in the given template and uses them when rendering the form. In other words, when the -``form_row`` function is called later in this template, it will use the ``field_row`` -block from your custom theme (instead of the default ``field_row`` block +``form_row`` function is called later in this template, it will use the ``form_row`` +block from your custom theme (instead of the default ``form_row`` block that ships with Symfony). Your custom theme does not have to override all the blocks. When rendering a block @@ -1100,6 +1171,19 @@ To customize any portion of a form, you just need to override the appropriate fragment. Knowing exactly which block or file to override is the subject of the next section. +.. versionadded:: 2.1 + An alternate Twig syntax for ``form_theme`` has been introduced in 2.1. It accepts + any valid Twig expression (the most noticeable difference is using an array when + using multiple themes). + + .. code-block:: html+jinja + + {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} + + {% form_theme form with 'AcmeTaskBundle:Form:fields.html.twig' %} + + {% form_theme form with ['AcmeTaskBundle:Form:fields.html.twig', 'AcmeTaskBundle:Form:fields2.html.twig'] %} + For a more extensive discussion, see :doc:`/cookbook/form/form_customization`. .. index:: @@ -1124,10 +1208,10 @@ the `Resources/views/Form` directory of the framework bundle (`view on GitHub`_) Each fragment name follows the same basic pattern and is broken up into two pieces, separated by a single underscore character (``_``). A few examples are: -* ``field_row`` - used by ``form_row`` to render most fields; +* ``form_row`` - used by ``form_row`` to render most fields; * ``textarea_widget`` - used by ``form_widget`` to render a ``textarea`` field type; -* ``field_errors`` - used by ``form_errors`` to render errors for a field; +* ``form_errors`` - used by ``form_errors`` to render errors for a field; Each fragment follows the same basic pattern: ``type_part``. The ``type`` portion corresponds to the field *type* being rendered (e.g. ``textarea``, ``checkbox``, @@ -1136,13 +1220,13 @@ rendered (e.g. ``label``, ``widget``, ``errors``, etc). By default, there are 4 possible *parts* of a form that can be rendered: +-------------+--------------------------+---------------------------------------------------------+ -| ``label`` | (e.g. ``field_label``) | renders the field's label | +| ``label`` | (e.g. ``form_label``) | renders the field's label | +-------------+--------------------------+---------------------------------------------------------+ -| ``widget`` | (e.g. ``field_widget``) | renders the field's HTML representation | +| ``widget`` | (e.g. ``form_widget``) | renders the field's HTML representation | +-------------+--------------------------+---------------------------------------------------------+ -| ``errors`` | (e.g. ``field_errors``) | renders the field's errors | +| ``errors`` | (e.g. ``form_errors``) | renders the field's errors | +-------------+--------------------------+---------------------------------------------------------+ -| ``row`` | (e.g. ``field_row``) | renders the field's entire row (label, widget & errors) | +| ``row`` | (e.g. ``form_row``) | renders the field's entire row (label, widget & errors) | +-------------+--------------------------+---------------------------------------------------------+ .. note:: @@ -1164,16 +1248,17 @@ In some cases, the fragment you want to customize will appear to be missing. For example, there is no ``textarea_errors`` fragment in the default themes provided with Symfony. So how are the errors for a textarea field rendered? -The answer is: via the ``field_errors`` fragment. When Symfony renders the errors +The answer is: via the ``form_errors`` fragment. When Symfony renders the errors for a textarea type, it looks first for a ``textarea_errors`` fragment before -falling back to the ``field_errors`` fragment. Each field type has a *parent* -type (the parent type of ``textarea`` is ``field``), and Symfony uses the -fragment for the parent type if the base fragment doesn't exist. +falling back to the ``form_errors`` fragment. Each field type has a *parent* +type (the parent type of ``textarea`` is ``text``, its parent is ``form``), +and Symfony uses the fragment for the parent type if the base fragment doesn't +exist. So, to override the errors for *only* ``textarea`` fields, copy the -``field_errors`` fragment, rename it to ``textarea_errors`` and customize it. To +``form_errors`` fragment, rename it to ``textarea_errors`` and customize it. To override the default error rendering for *all* fields, copy and customize the -``field_errors`` fragment directly. +``form_errors`` fragment directly. .. tip:: @@ -1246,9 +1331,9 @@ to define form output. {% form_theme form _self %} {# make the form fragment customization #} - {% block field_row %} + {% block form_row %} {# custom field row output #} - {% endblock field_row %} + {% endblock form_row %} {% block content %} {# ... #} @@ -1345,19 +1430,21 @@ that all un-rendered fields are output. The CSRF token can be customized on a form-by-form basis. For example:: + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + class TaskType extends AbstractType { // ... - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Task', 'csrf_protection' => true, 'csrf_field_name' => '_token', // a unique key to help generate the secret token 'intention' => 'task_item', - ); + )); } // ... @@ -1399,8 +1486,8 @@ an array of the submitted data. This is actually really easy:: ->add('message', 'textarea') ->getForm(); - if ($request->getMethod() == 'POST') { - $form->bindRequest($request); + if ($request->isMethod('POST')) { + $form->bind($request); // data is an array with "name", "email", and "message" keys $data = $form->getData(); @@ -1439,64 +1526,53 @@ Adding Validation The only missing piece is validation. Usually, when you call ``$form->isValid()``, the object is validated by reading the constraints that you applied to that -class. But without a class, how can you add constraints to the data of your -form? - -The answer is to setup the constraints yourself, and pass them into your -form. The overall approach is covered a bit more in the :ref:`validation chapter`, -but here's a short example:: - - // import the namespaces above your controller class - use Symfony\Component\Validator\Constraints\Email; - use Symfony\Component\Validator\Constraints\MinLength; - use Symfony\Component\Validator\Constraints\Collection; - - $collectionConstraint = new Collection(array( - 'name' => new MinLength(5), - 'email' => new Email(array('message' => 'Invalid email address')), - )); - - // create a form, no default values, pass in the constraint option - $form = $this->createFormBuilder(null, array( - 'validation_constraint' => $collectionConstraint, - ))->add('email', 'email') - // ... - ; +class. If your form is binding to an object (i.e. you're using the ``data_class`` +option or passing an object to your form), this is almost always the approach +you want to use. See :doc:`/book/validation` for more details. -Now, when you call `$form->bindRequest($request)`, the constraints setup here are run -against your form's data. If you're using a form class, override the ``getDefaultOptions`` -method to specify the option:: +.. _form-option-constraints: - namespace Acme\TaskBundle\Form\Type; +But if you're not binding to an object and are instead retrieving a simple +array of your submitted data, how can you add constraints to the data of your +form? - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; - use Symfony\Component\Validator\Constraints\Email; - use Symfony\Component\Validator\Constraints\MinLength; - use Symfony\Component\Validator\Constraints\Collection; +The answer is to setup the constraints yourself, and attach them to the individual +fields. The overall approach is covered a bit more in the :ref:`validation chapter`, +but here's a short example: - class ContactType extends AbstractType - { - // ... +.. versionadded:: 2.1 + The ``constraints`` option, which accepts a single constraint or an array + of constraints (before 2.1, the option was called ``validation_constraint``, + and only accepted a single constraint) is new to Symfony 2.1. + +.. code-block:: php - public function getDefaultOptions(array $options) - { - $collectionConstraint = new Collection(array( - 'name' => new MinLength(5), - 'email' => new Email( - array('message' => 'Invalid email address') - ), - )); + use Symfony\Component\Validator\Constraints\Length; + use Symfony\Component\Validator\Constraints\NotBlank; + + $builder + ->add('firstName', 'text', array( + 'constraints' => new Length(array('min' => 3)), + )) + ->add('lastName', 'text', array( + 'constraints' => array( + new NotBlank(), + new Length(array('min' => 3)), + ), + )) + ; - return array('validation_constraint' => $collectionConstraint); - } - } +.. tip:: -Now, you have the flexibility to create forms - with validation - that return -an array of data, instead of an object. In most cases, it's better - and -certainly more robust - to bind your form to an object. But for simple forms, -this is a great approach. + If you are using Validation Groups, you need to either reference the + ``Default`` group when creating the form, or set the correct group on + the constraint you are adding. + +.. code-block:: php + new NotBlank(array('groups' => array('create', 'update')) + + Final Thoughts -------------- @@ -1527,7 +1603,7 @@ Learn more from the Cookbook .. _`Symfony2 Form Component`: https://github.com/symfony/Form .. _`DateTime`: http://php.net/manual/en/class.datetime.php -.. _`Twig Bridge`: https://github.com/symfony/symfony/tree/master/src/Symfony/Bridge/Twig -.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.0/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +.. _`Twig Bridge`: https://github.com/symfony/symfony/tree/2.2/src/Symfony/Bridge/Twig +.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.2/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig .. _`Cross-site request forgery`: http://en.wikipedia.org/wiki/Cross-site_request_forgery -.. _`view on GitHub`: https://github.com/symfony/symfony/tree/master/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form +.. _`view on GitHub`: https://github.com/symfony/symfony/tree/2.2/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form diff --git a/book/from_flat_php_to_symfony2.rst b/book/from_flat_php_to_symfony2.rst index 508327a5acf..ff684736571 100644 --- a/book/from_flat_php_to_symfony2.rst +++ b/book/from_flat_php_to_symfony2.rst @@ -420,34 +420,36 @@ Why should you have to reinvent solutions to all these routine problems? Add a Touch of Symfony2 ~~~~~~~~~~~~~~~~~~~~~~~ -Symfony2 to the rescue. Before actually using Symfony2, you need to make -sure PHP knows how to find the Symfony2 classes. This is accomplished via -an autoloader that Symfony provides. An autoloader is a tool that makes it -possible to start using PHP classes without explicitly including the file -containing the class. +Symfony2 to the rescue. Before actually using Symfony2, you need to download +it. This can be done by using Composer, which takes care of downloading the +correct version and all its dependencies and provides an autoloader. An +autoloader is a tool that makes it possible to start using PHP classes +without explicitly including the file containing the class. -First, `download Symfony`_ and place it into a ``vendor/symfony/`` directory. -Next, create an ``app/bootstrap.php`` file. Use it to ``require`` the two -files in the application and to configure the autoloader: +In your root directory, create a ``composer.json`` file with the following +content: -.. code-block:: html+php +.. code-block:: json - registerNamespaces(array( - 'Symfony' => __DIR__.'/../vendor/symfony/src', - )); +.. code-block:: bash - $loader->register(); + $ php composer.phar install -This tells the autoloader where the ``Symfony`` classes are. With this, you -can start using Symfony classes without using the ``require`` statement for -the files that contain them. +Beside downloading your dependencies, Composer generates a ``vendor/autoload.php`` file, +which takes care of autoloading for all the files in the Symfony Framework as well as +the files mentioned in the autoload section of your ``composer.json``. Core to Symfony's philosophy is the idea that an application's main job is to interpret each request and return a response. To this end, Symfony2 provides @@ -460,7 +462,7 @@ the HTTP response being returned. Use them to improve the blog: get('doctrine')->getEntityManager() + $posts = $this->get('doctrine')->getManager() ->createQuery('SELECT p FROM AcmeBlogBundle:Post p') ->execute(); @@ -563,7 +565,7 @@ them for you. Here's the same sample application, now built in Symfony2:: public function showAction($id) { $post = $this->get('doctrine') - ->getEntityManager() + ->getManager() ->getRepository('AcmeBlogBundle:Post') ->find($id) ; @@ -637,11 +639,11 @@ A routing configuration map provides this information in a readable format: # app/config/routing.yml blog_list: - pattern: /blog + path: /blog defaults: { _controller: AcmeBlogBundle:Blog:list } blog_show: - pattern: /blog/show/{id} + path: /blog/show/{id} defaults: { _controller: AcmeBlogBundle:Blog:show } Now that Symfony2 is handling all the mundane tasks, the front controller @@ -752,7 +754,7 @@ Learn more from the Cookbook * :doc:`/cookbook/controller/service` .. _`Doctrine`: http://www.doctrine-project.org -.. _`download Symfony`: http://symfony.com/download +.. _`download Composer`: http://getcomposer.org/download/ .. _`Routing`: https://github.com/symfony/Routing .. _`Templating`: https://github.com/symfony/Templating .. _`KnpBundles.com`: http://knpbundles.com/ diff --git a/book/http_cache.rst b/book/http_cache.rst index 5e4571a6450..c14cb9f9f3e 100644 --- a/book/http_cache.rst +++ b/book/http_cache.rst @@ -154,7 +154,10 @@ kernel:: $kernel->loadClassCache(); // wrap the default AppKernel with the AppCache one $kernel = new AppCache($kernel); - $kernel->handle(Request::createFromGlobals())->send(); + $request = Request::createFromGlobals(); + $response = $kernel->handle($request); + $response->send(); + $kernel->terminate($request, $response); The caching kernel will immediately act as a reverse proxy - caching responses from your application and returning them to the client. @@ -882,59 +885,38 @@ matter), Symfony2 uses the standard ``render`` helper to configure ESI tags: .. code-block:: jinja - {% render url('latest_news', { 'max': 5 }) with {}, {'standalone': true} %} + {# you can use a controller reference #} + {{ render_esi(controller('...:news', { 'max': 5 })) }} + + {# ... or a URL #} + {{ render_esi(url('latest_news', { 'max': 5 })) }} .. code-block:: html+php render( - $view['router']->generate('latest_news', array('max' => 5), true), - array(), - array('standalone' => true) - ); ?> - -.. include:: /book/_security-2012-6431.rst.inc - -The ``render`` tag takes the absolute url to the embedded action. This means -that you need to define a new route to the controller that you're embedding: - -.. code-block:: yaml + new ControllerReference('...:news', array('max' => 5)), + array('renderer' => 'esi')) + ?> - # app/config/routing.yml - latest_news: - pattern: /esi/latest-news/{max} - defaults: { _controller: AcmeNewsBundle:News:news } - requirements: { max: \d+ } - -.. caution:: - - Unless you want this URL to be accessible to the outside world, you - should use Symfony's firewall to secure it (by allowing access to your - reverse proxy's IP range). See the :ref:`Securing by IP` - section of the :doc:`Security Chapter ` for more information - on how to do this. - -.. tip:: - - The best practice is to mount all your ESI urls on a single prefix (e.g. - ``/esi``) of your choice. This has two main advantages. First, it eases - the management of ESI urls as you can easily identify the routes used for ESI. - Second, it eases security management since securing all urls starting - with the same prefix is easier than securing each individual url. See - the above note for more details on securing ESI URLs. - -By setting ``standalone`` to ``true`` in the ``render`` Twig tag, you tell -Symfony2 that the action should be rendered as an ESI tag. You might be -wondering why you would want to use a helper instead of just writing the ESI tag -yourself. That's because using a helper makes your application work even if -there is no gateway cache installed. - -When standalone is ``false`` (the default), Symfony2 merges the included page -content within the main one before sending the response to the client. But -when standalone is ``true``, *and* if Symfony2 detects that it's talking -to a gateway cache that supports ESI, it generates an ESI include tag. But -if there is no gateway cache or if it does not support ESI, Symfony2 will -just merge the included page content within the main one as it would have -done were standalone set to ``false``. + render( + $view['router']->generate('latest_news', array('max' => 5), true), + array('renderer' => 'esi'), + ) ?> + +By using the ``esi`` renderer (via the ``render_esi`` Twig function), you +tell Symfony2 that the action should be rendered as an ESI tag. You might be +wondering why you would want to use a helper instead of just writing the ESI +tag yourself. That's because using a helper makes your application work even +if there is no gateway cache installed. + +When using the default ``render`` function (or setting the renderer to +``inline``), Symfony2 merges the included page content into the main one +before sending the response to the client. But if you use the ``esi`` renderer +(i.e. call ``render_esi``), *and* if Symfony2 detects that it's talking to a +gateway cache that supports ESI, it generates an ESI include tag. But if there +is no gateway cache or if it does not support ESI, Symfony2 will just merge +the included page content within the main one as it would have done if you had +used ``render``. .. note:: @@ -949,17 +931,52 @@ of the master page. public function newsAction($max) { - // ... + // ... - $response->setSharedMaxAge(60); + $response->setSharedMaxAge(60); } With ESI, the full page cache will be valid for 600 seconds, but the news component cache will only last for 60 seconds. -One great advantage of this caching strategy is that you can make your -application as dynamic as needed and at the same time, hit the application as -little as possible. +When using a controller reference, the ESI tag should reference the embedded +action as an accessible URL so the gateway cache can fetch it independently of +the rest of the page. Symfony2 takes care of generating a unique URL for any +controller reference and it is able to route them properly thanks to a +listener that must be enabled in your configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + framework: + # ... + fragments: { path: /_fragment } + + .. code-block:: xml + + + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('framework', array( + // ... + 'fragments' => array('path' => '/_fragment'), + )); + +One great advantage of the ESI renderer is that you can make your application +as dynamic as needed and at the same time, hit the application as little as +possible. + +.. tip:: + + The listener listener only responds to local IP addresses or trusted + proxies. .. note:: @@ -969,7 +986,7 @@ little as possible. obey the ``max-age`` directive and cache the entire page. And you don't want that. -The ``render`` helper supports two other useful options: +The ``render_esi`` helper supports two other useful options: * ``alt``: used as the ``alt`` attribute on the ESI tag, which allows you to specify an alternative URL to be used if the ``src`` cannot be found; diff --git a/book/http_fundamentals.rst b/book/http_fundamentals.rst index 61760b5e41e..16520777863 100644 --- a/book/http_fundamentals.rst +++ b/book/http_fundamentals.rst @@ -425,12 +425,12 @@ by adding an entry for ``/contact`` to your routing configuration file: # app/config/routing.yml contact: - pattern: /contact + path: /contact defaults: { _controller: AcmeDemoBundle:Main:contact } .. code-block:: xml - + AcmeBlogBundle:Main:contact diff --git a/book/installation.rst b/book/installation.rst index e3f75e8e7dd..a001420e177 100644 --- a/book/installation.rst +++ b/book/installation.rst @@ -14,13 +14,13 @@ developing in immediately. If you're looking for instructions on how best to create a new project and store it via source control, see `Using Source Control`_. -Downloading a Symfony2 Distribution ------------------------------------ +Installing a Symfony2 Distribution +---------------------------------- .. tip:: First, check that you have installed and configured a Web server (such - as Apache) with PHP 5.3.2 or higher. For more information on Symfony2 + as Apache) with PHP 5.3.8 or higher. For more information on Symfony2 requirements, see the :doc:`requirements reference`. Symfony2 packages "distributions", which are fully-functional applications @@ -31,34 +31,52 @@ that can be used immediately to begin developing your application. Start by visiting the Symfony2 download page at `http://symfony.com/download`_. On this page, you'll see the *Symfony Standard Edition*, which is the main -Symfony2 distribution. Here, you'll need to make two choices: +Symfony2 distribution. There are 2 ways to get your project started: -* Download either a ``.tgz`` or ``.zip`` archive - both are equivalent, download - whatever you're more comfortable using; +Option 1) Composer +~~~~~~~~~~~~~~~~~~ -* Download the distribution with or without vendors. If you have `Git`_ installed - on your computer, you should download Symfony2 "without vendors", as it - adds a bit more flexibility when including third-party/vendor libraries. +`Composer`_ is a dependency management library for PHP, which you can use +to download the Symfony2 Standard Edition. -Download one of the archives somewhere under your local web server's root -directory and unpack it. From a UNIX command line, this can be done with -one of the following commands (replacing ``###`` with your actual filename): +Start by `downloading Composer`_ anywhere onto your local computer. If you +have curl installed, it's as easy as: .. code-block:: bash - # for .tgz file - $ tar zxvf Symfony_Standard_Vendors_2.0.###.tgz + curl -s https://getcomposer.org/installer | php - # for a .zip file - $ unzip Symfony_Standard_Vendors_2.0.###.zip +.. note:: + + If your computer is not ready to use Composer, you'll see some recommendations + when running this command. Follow those recommendations to get Composer + working properly. + +Composer is an executable PHAR file, which you can use to download the Standard +Distribution: + +.. code-block:: bash + + php composer.phar create-project symfony/framework-standard-edition /path/to/webroot/Symfony 2.2.0 + +.. tip:: -When you're finished, you should have a ``Symfony/`` directory that looks -something like this: + For an exact version, replace `2.2.0` with the latest Symfony version + (e.g. 2.2.1). For details, see the `Symfony Installation Page`_ + +.. tip:: + + To download the vendor files faster, add the ``--prefer-dist`` option at + the end of any Composer command. + +This command may take several minutes to run as Composer downloads the Standard +Distribution along with all of the vendor libraries that it needs. When it finishes, +you should have a directory that looks something like this: .. code-block:: text - www/ <- your web root directory - Symfony/ <- the unpacked archive + path/to/webroot/ <- your web root directory + Symfony/ <- the new directory app/ cache/ config/ @@ -71,26 +89,103 @@ something like this: app.php ... +Option 2) Download an Archive +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also download an archive of the Standard Edition. Here, you'll +need to make two choices: + +* Download either a ``.tgz`` or ``.zip`` archive - both are equivalent, download + whatever you're more comfortable using; + +* Download the distribution with or without vendors. If you're planning on + using more third-party libraries or bundles and managing them via Composer, + you should probably download "without vendors". + +Download one of the archives somewhere under your local web server's root +directory and unpack it. From a UNIX command line, this can be done with +one of the following commands (replacing ``###`` with your actual filename): + +.. code-block:: bash + + # for .tgz file + $ tar zxvf Symfony_Standard_Vendors_2.2.###.tgz + + # for a .zip file + $ unzip Symfony_Standard_Vendors_2.2.###.zip + +If you've downloaded "without vendors", you'll definitely need to read the +next section. + .. note:: You can easily override the default directory structure. See :doc:`/cookbook/configuration/override_dir_structure` for more information. +.. _installation-updating-vendors: + Updating Vendors ~~~~~~~~~~~~~~~~ -Finally, if you downloaded the archive "without vendors", install the vendors -by running the following command from the command line: +At this point, you've downloaded a fully-functional Symfony project in which +you'll start to develop your own application. A Symfony project depends on +a number of external libraries. These are downloaded into the `vendor/` directory +of your project via a library called `Composer`_. + +Depending on how you downloaded Symfony, you may or may not need to do update +your vendors right now. But, updating your vendors is always safe, and guarantees +that you have all the vendor libraries you need. + +Step 1: Get `Composer`_ (The great new PHP packaging system) + +.. code-block:: bash + + curl -s http://getcomposer.org/installer | php + +Make sure you download ``composer.phar`` in the same folder where +the ``composer.json`` file is located (this is your Symfony project +root by default). + +Step 2: Install vendors .. code-block:: bash - $ php bin/vendors install + $ php composer.phar install This command downloads all of the necessary vendor libraries - including -Symfony itself - into the ``vendor/`` directory. For more information on -how third-party vendor libraries are managed inside Symfony2, see -":ref:`cookbook-managing-vendor-libraries`". +Symfony itself - into the ``vendor/`` directory. + +.. note:: + + If you don't have ``curl`` installed, you can also just download the ``installer`` + file manually at http://getcomposer.org/installer. Place this file into your + project and then run: + + .. code-block:: bash + + php installer + php composer.phar install + +.. tip:: + + When running ``php composer.phar install`` or ``php composer.phar update``, + composer will execute post install/update commands to clear the cache + and install assets. By default, the assets will be copied into your ``web`` + directory. To create symlinks instead of copying the assets, you can + add an entry in the ``extra`` node of your composer.json file with the + key ``symfony-assets-install`` and the value ``symlink``: + + .. code-block:: json + + "extra": { + "symfony-app-dir": "app", + "symfony-web-dir": "web", + "symfony-assets-install": "symlink" + } + + When passing ``relative`` instead of ``symlink`` to symfony-assets-install, + the command will generate relative symlinks. Configuration and Setup ~~~~~~~~~~~~~~~~~~~~~~~ @@ -105,7 +200,7 @@ to check your configuration: .. code-block:: text - http://localhost/Symfony/web/config.php + http://localhost/config.php If there are any issues, correct them now before moving on. @@ -182,11 +277,11 @@ first "real" Symfony2 webpage: .. code-block:: text - http://localhost/Symfony/web/app_dev.php/ + http://localhost/app_dev.php/ Symfony2 should welcome and congratulate you for your hard work so far! -.. image:: /images/quick_tour/welcome.jpg +.. image:: /images/quick_tour/welcome.png .. tip:: @@ -210,6 +305,9 @@ If you're new to Symfony, check out ":doc:`page_creation`", where you'll learn how to create pages, change configuration, and do everything else you'll need in your new application. +Be sure to also check out the :doc:`Cookbook`, which contains +a wide variety of articles about solving specific problems with Symfony. + .. note:: If you want to remove the sample code from your distribution, take a look @@ -236,16 +334,19 @@ file: .. code-block:: text - vendor/ + /vendor/ Now, the vendor directory won't be committed to source control. This is fine (actually, it's great!) because when someone else clones or checks out the -project, he/she can simply run the ``php bin/vendors install`` script to -download all the necessary vendor libraries. +project, he/she can simply run the ``php composer.phar install`` script to +install all the necessary project dependencies. .. _`enable ACL support`: https://help.ubuntu.com/community/FilePermissionsACLs .. _`http://symfony.com/download`: http://symfony.com/download .. _`Git`: http://git-scm.com/ .. _`GitHub Bootcamp`: http://help.github.com/set-up-git-redirect +.. _`Composer`: http://getcomposer.org/ +.. _`downloading Composer`: http://getcomposer.org/download/ .. _`Apache`: http://httpd.apache.org/docs/current/mod/core.html#documentroot .. _`Nginx`: http://wiki.nginx.org/Symfony +.. _`Symfony Installation Page`: http://symfony.com/download diff --git a/book/internals.rst b/book/internals.rst index 031f5853e85..a67684ca52b 100644 --- a/book/internals.rst +++ b/book/internals.rst @@ -21,11 +21,9 @@ on top of the previous one. .. tip:: - Autoloading is not managed by the framework directly; it's done - independently with the help of the - :class:`Symfony\\Component\\ClassLoader\\UniversalClassLoader` class - and the ``src/autoload.php`` file. Read the :doc:`dedicated chapter - ` for more information. + Autoloading is not managed by the framework directly; it's done by using + Composer's autoloader (``vendor/autoload.php``), which is included in + the ``app/autoload.php`` file. ``HttpFoundation`` Component ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -376,6 +374,15 @@ and set a new ``Exception`` object, or do nothing:: // $event->setException($exception); } +.. note:: + + As Symfony 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 (which you + should not without a good reason), set the ``X-Status-Code`` header:: + + return new Response('Error', 404 /* ignored */, array('X-Status-Code' => 200)); + .. index:: single: Event Dispatcher @@ -511,7 +518,6 @@ the configuration for the development environment: web_profiler: toolbar: true intercept_redirects: true - verbose: true .. code-block:: xml @@ -551,10 +557,6 @@ When ``intercept-redirects`` is set to ``true``, the web profiler intercepts the redirects and gives you the opportunity to look at the collected data before following the redirect. -When ``verbose`` is set to ``true``, the Web Debug Toolbar displays a lot of -information. Setting ``verbose`` to ``false`` hides some secondary information -to make the toolbar shorter. - If you enable the web profiler, you also need to mount the profiler routes: .. configuration-block:: diff --git a/book/page_creation.rst b/book/page_creation.rst index c094643f92d..45a8f1b5780 100644 --- a/book/page_creation.rst +++ b/book/page_creation.rst @@ -8,7 +8,7 @@ Creating a new page in Symfony2 is a simple two-step process: * *Create a route*: A route defines the URL (e.g. ``/about``) to your page and specifies a controller (which is a PHP function) that Symfony2 should - execute when the URL of an incoming request matches the route pattern; + execute when the URL of an incoming request matches the route path; * *Create a controller*: A controller is a PHP function that takes the incoming request and transforms it into the Symfony2 ``Response`` object that's @@ -103,7 +103,7 @@ an entry when you generated the ``AcmeHelloBundle``: .. code-block:: yaml # app/config/routing.yml - AcmeHelloBundle: + acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml" prefix: / @@ -147,7 +147,7 @@ the new route that defines the URL of the page that you're about to create: # src/Acme/HelloBundle/Resources/config/routing.yml hello: - pattern: /hello/{name} + path: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } .. code-block:: xml @@ -159,7 +159,7 @@ the new route that defines the URL of the page that you're about to create: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeHelloBundle:Hello:index @@ -177,9 +177,9 @@ the new route that defines the URL of the page that you're about to create: return $collection; -The routing consists of two basic pieces: the ``pattern``, which is the URL +The routing consists of two basic pieces: the ``path``, which is the URL that this route will match, and a ``defaults`` array, which specifies the -controller that should be executed. The placeholder syntax in the pattern +controller that should be executed. The placeholder syntax in the path (``{name}``) is a wildcard. It means that ``/hello/Ryan``, ``/hello/Fabien`` or any other similar URL will match this route. The ``{name}`` placeholder parameter will also be passed to the controller so that you can use its value @@ -512,13 +512,13 @@ You'll learn more about each of these directories in later chapters. .. sidebar:: Autoloading - When Symfony is loading, a special file - ``app/autoload.php`` - is included. - This file is responsible for configuring the autoloader, which will autoload - your application files from the ``src/`` directory and third-party libraries - from the ``vendor/`` directory. + When Symfony is loading, a special file - ``vendor/autoload.php`` - is + included. This file is created by Composer and will autoload all + application files living in the `src/` folder as well as all + third-party libraries mentioned in the ``composer.json`` file. Because of the autoloader, you never need to worry about using ``include`` - or ``require`` statements. Instead, Symfony2 uses the namespace of a class + or ``require`` statements. Instead, Composer uses the namespace of a class to determine its location and automatically includes the file on your behalf the instant you need a class. @@ -533,11 +533,6 @@ You'll learn more about each of these directories in later chapters. Path: src/Acme/HelloBundle/Controller/HelloController.php - Typically, the only time you'll need to worry about the ``app/autoload.php`` - file is when you're including a new third-party library in the ``vendor/`` - directory. For more information on autoloading, see - :doc:`How to autoload Classes`. - The Source (``src``) Directory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -732,20 +727,13 @@ format you prefer: # app/config/config.yml imports: - - { resource: parameters.ini } + - { resource: parameters.yml } - { resource: security.yml } framework: secret: "%secret%" - charset: UTF-8 router: { resource: "%kernel.root_dir%/config/routing.yml" } - form: true - csrf_protection: true - validation: { enable_annotations: true } - templating: { engines: ['twig'] } #assets_version: SomeVersionScheme - session: - default_locale: "%locale%" - auto_start: true + # ... # Twig Configuration twig: @@ -758,19 +746,13 @@ format you prefer: - + - + - - - - - - - + @@ -780,23 +762,13 @@ format you prefer: .. code-block:: php - $this->import('parameters.ini'); + $this->import('parameters.yml'); $this->import('security.yml'); $container->loadFromExtension('framework', array( 'secret' => '%secret%', - 'charset' => 'UTF-8', 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'), - 'form' => array(), - 'csrf-protection' => array(), - 'validation' => array('annotations' => true), - 'templating' => array( - 'engines' => array('twig'), - #'assets_version' => "SomeVersionScheme", - ), - 'session' => array( - 'default_locale' => "%locale%", - 'auto_start' => true, + // ... ), )); @@ -836,6 +808,32 @@ options of each feature. * *PHP*: Very powerful but less readable than standard configuration formats. +Default Configuration Dump +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ``config:dump-reference`` command was added in Symfony 2.1 + +You can dump the default configuration for a bundle in yaml to the console using +the ``config:dump-reference`` command. Here is an example of dumping the default +FrameworkBundle configuration: + +.. code-block:: text + + app/console config:dump-reference FrameworkBundle + +The extension alias (configuration key) can also be used: + +.. code-block:: text + + app/console config:dump-reference framework + +.. note:: + + See the cookbook article: :doc:`How to expose a Semantic Configuration for + a Bundle` for information on adding + configuration for your own bundle. + .. index:: single: Environments; Introduction diff --git a/book/performance.rst b/book/performance.rst index 4a8a4de3a7a..29c998a3725 100644 --- a/book/performance.rst +++ b/book/performance.rst @@ -42,11 +42,11 @@ your php.ini configuration. .. index:: single: Performance; Autoloader -Use an Autoloader that caches (e.g. ``ApcUniversalClassLoader``) ----------------------------------------------------------------- +Use Composer's Class Map Functionality +-------------------------------------- -By default, the Symfony2 standard edition uses the ``UniversalClassLoader`` -in the `autoloader.php`_ file. This autoloader is easy to use, as it will +By default, the Symfony2 standard edition uses Composer's autoloader +in the `autoload.php`_ file. This autoloader is easy to use, as it will automatically find any new classes that you've placed in the registered directories. @@ -54,19 +54,37 @@ Unfortunately, this comes at a cost, as the loader iterates over all configured namespaces to find a particular file, making ``file_exists`` calls until it finally finds the file it's looking for. -The simplest solution is to cache the location of each class after it's located -the first time. Symfony comes with a class - ``ApcUniversalClassLoader`` - -loader that extends the ``UniversalClassLoader`` and stores the class locations -in APC. +The simplest solution is to tell Composer to build a "class map" (i.e. a +big array of the locations of all the classes). This can be done from the +command line, and might become part of your deploy process: -To use this class loader, simply adapt your ``autoloader.php`` as follows:: +.. code-block:: bash - // app/autoload.php - require __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php'; + php composer.phar dump-autoload --optimize - use Symfony\Component\ClassLoader\ApcUniversalClassLoader; +Internally, this builds the big class map array in ``vendor/composer/autoload_namespaces.php``. + +Caching the Autoloader with APC +------------------------------- + +Another solution is to to cache the location of each class after it's located +the first time. Symfony comes with a class - :class:`Symfony\\Component\\ClassLoader\\ApcClassLoader` - +that does exactly this. To use it, just adapt your front controller file. +If you're using the Standard Distribution, this code should already be available +as comments in this file:: + + // app.php + // ... + + $loader = require_once __DIR__.'/../app/bootstrap.php.cache'; + + // Use APC for autoloading to improve performance + // Change 'sf2' by the prefix you want in order to prevent key conflict with another application + /* + $loader = new ApcClassLoader('sf2', $loader); + $loader->register(true); + */ - $loader = new ApcUniversalClassLoader('some caching unique prefix'); // ... .. note:: @@ -107,7 +125,7 @@ Note that there are two disadvantages when using a bootstrap file: * when debugging, one will need to place break points inside the bootstrap file. If you're using Symfony2 Standard Edition, the bootstrap file is automatically -rebuilt after updating the vendor libraries via the ``php bin/vendors install`` +rebuilt after updating the vendor libraries via the ``php composer.phar install`` command. Bootstrap Files and Byte Code Caches @@ -120,5 +138,5 @@ is no longer a reason to use a bootstrap file. .. _`byte code caches`: http://en.wikipedia.org/wiki/List_of_PHP_accelerators .. _`APC`: http://php.net/manual/en/book.apc.php -.. _`autoloader.php`: https://github.com/symfony/symfony-standard/blob/2.0/app/autoload.php -.. _`bootstrap file`: https://github.com/sensio/SensioDistributionBundle/blob/2.0/Resources/bin/build_bootstrap.php +.. _`autoload.php`: https://github.com/symfony/symfony-standard/blob/master/app/autoload.php +.. _`bootstrap file`: https://github.com/sensio/SensioDistributionBundle/blob/master/Composer/ScriptHandler.php diff --git a/book/propel.rst b/book/propel.rst index 0bcee86a06d..374803c1f20 100644 --- a/book/propel.rst +++ b/book/propel.rst @@ -29,22 +29,22 @@ Configuring the Database Before you can start, you'll need to configure your database connection information. By convention, this information is usually configured in an -``app/config/parameters.ini`` file: +``app/config/parameters.yml`` file: -.. code-block:: ini +.. code-block:: yaml - ; app/config/parameters.ini - [parameters] - database_driver = mysql - database_host = localhost - database_name = test_project - database_user = root - database_password = password - database_charset = UTF8 + # app/config/parameters.yml + parameters: + database_driver: mysql + database_host: localhost + database_name: test_project + database_user: root + database_password: password + database_charset: UTF8 .. note:: - Defining the configuration via ``parameters.ini`` is just a convention. The + Defining the configuration via ``parameters.yml`` is just a convention. The parameters defined in that file are referenced by the main configuration file when setting up Propel: diff --git a/book/routing.rst b/book/routing.rst index 4b4844bec7f..8e77a607268 100644 --- a/book/routing.rst +++ b/book/routing.rst @@ -27,7 +27,7 @@ areas of your application. By the end of this chapter, you'll be able to: Routing in Action ----------------- -A *route* is a map from a URL pattern to a controller. For example, suppose +A *route* is a map from a URL path to a controller. For example, suppose you want to match any URL like ``/blog/my-post`` or ``/blog/all-about-symfony`` and send it to a controller that can look up and render that blog entry. The route is simple: @@ -38,7 +38,7 @@ The route is simple: # app/config/routing.yml blog_show: - pattern: /blog/{slug} + path: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } .. code-block:: xml @@ -49,7 +49,7 @@ The route is simple: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeBlogBundle:Blog:show @@ -67,7 +67,11 @@ The route is simple: return $collection; -The pattern defined by the ``blog_show`` route acts like ``/blog/*`` where +.. versionadded:: 2.2 + The ``path`` option is new in Symfony2.2, ``pattern`` is used in older + versions. + +The path defined by the ``blog_show`` route acts like ``/blog/*`` where the wildcard is given the name ``slug``. For the URL ``/blog/my-blog-post``, the ``slug`` variable gets a value of ``my-blog-post``, which is available for you to use in your controller (keep reading). @@ -186,7 +190,7 @@ Basic Route Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~ Defining a route is easy, and a typical application will have lots of routes. -A basic route consists of just two parts: the ``pattern`` to match and a +A basic route consists of just two parts: the ``path`` to match and a ``defaults`` array: .. configuration-block:: @@ -194,7 +198,7 @@ A basic route consists of just two parts: the ``pattern`` to match and a .. code-block:: yaml _welcome: - pattern: / + path: / defaults: { _controller: AcmeDemoBundle:Main:homepage } .. code-block:: xml @@ -205,7 +209,7 @@ A basic route consists of just two parts: the ``pattern`` to match and a xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeDemoBundle:Main:homepage @@ -242,7 +246,7 @@ routes will contain one or more named "wildcard" placeholders: .. code-block:: yaml blog_show: - pattern: /blog/{slug} + path: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } .. code-block:: xml @@ -253,7 +257,7 @@ routes will contain one or more named "wildcard" placeholders: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeBlogBundle:Blog:show @@ -270,13 +274,13 @@ routes will contain one or more named "wildcard" placeholders: return $collection; -The pattern will match anything that looks like ``/blog/*``. Even better, +The path will match anything that looks like ``/blog/*``. Even better, the value matching the ``{slug}`` placeholder will be available inside your controller. In other words, if the URL is ``/blog/hello-world``, a ``$slug`` variable, with a value of ``hello-world``, will be available in the controller. This can be used, for example, to load the blog post matching that string. -The pattern will *not*, however, match simply ``/blog``. That's because, +The path will *not*, however, match simply ``/blog``. That's because, by default, all placeholders are required. This can be changed by adding a placeholder value to the ``defaults`` array. @@ -291,7 +295,7 @@ the available blog posts for this imaginary blog application: .. code-block:: yaml blog: - pattern: /blog + path: /blog defaults: { _controller: AcmeBlogBundle:Blog:index } .. code-block:: xml @@ -302,7 +306,7 @@ the available blog posts for this imaginary blog application: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeBlogBundle:Blog:index @@ -329,7 +333,7 @@ entries? Update the route to have a new ``{page}`` placeholder: .. code-block:: yaml blog: - pattern: /blog/{page} + path: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index } .. code-block:: xml @@ -340,7 +344,7 @@ entries? Update the route to have a new ``{page}`` placeholder: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeBlogBundle:Blog:index @@ -372,7 +376,7 @@ This is done by including it in the ``defaults`` collection: .. code-block:: yaml blog: - pattern: /blog/{page} + path: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } .. code-block:: xml @@ -383,7 +387,7 @@ This is done by including it in the ``defaults`` collection: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeBlogBundle:Blog:index 1 @@ -433,11 +437,11 @@ Take a quick look at the routes that have been created so far: .. code-block:: yaml blog: - pattern: /blog/{page} + path: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } blog_show: - pattern: /blog/{slug} + path: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } .. code-block:: xml @@ -448,12 +452,12 @@ Take a quick look at the routes that have been created so far: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeBlogBundle:Blog:index 1 - + AcmeBlogBundle:Blog:show @@ -476,7 +480,7 @@ Take a quick look at the routes that have been created so far: return $collection; Can you spot the problem? Notice that both routes have patterns that match -URLs that look like ``/blog/*``. The Symfony router will always choose the +URL's that look like ``/blog/*``. The Symfony router will always choose the **first** matching route it finds. In other words, the ``blog_show`` route will *never* be matched. Instead, a URL like ``/blog/my-blog-post`` will match the first route (``blog``) and return a nonsense value of ``my-blog-post`` @@ -491,7 +495,7 @@ to the ``{page}`` parameter. +--------------------+-------+-----------------------+ The answer to the problem is to add route *requirements*. The routes in this -example would work perfectly if the ``/blog/{page}`` pattern *only* matched +example would work perfectly if the ``/blog/{page}`` path *only* matched URLs where the ``{page}`` portion is an integer. Fortunately, regular expression requirements can easily be added for each parameter. For example: @@ -500,7 +504,7 @@ requirements can easily be added for each parameter. For example: .. code-block:: yaml blog: - pattern: /blog/{page} + path: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } requirements: page: \d+ @@ -513,7 +517,7 @@ requirements can easily be added for each parameter. For example: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeBlogBundle:Blog:index 1 \d+ @@ -570,7 +574,7 @@ URL: .. code-block:: yaml homepage: - pattern: /{culture} + path: /{culture} defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en } requirements: culture: en|fr @@ -583,7 +587,7 @@ URL: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeDemoBundle:Main:homepage en en|fr @@ -635,16 +639,14 @@ be accomplished with the following route configuration: .. code-block:: yaml contact: - pattern: /contact + path: /contact defaults: { _controller: AcmeDemoBundle:Main:contact } - requirements: - _method: GET + methods: [GET] contact_process: - pattern: /contact + path: /contact defaults: { _controller: AcmeDemoBundle:Main:contactProcess } - requirements: - _method: POST + methods: [POST] .. code-block:: xml @@ -654,14 +656,12 @@ be accomplished with the following route configuration: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeDemoBundle:Main:contact - GET - + AcmeDemoBundle:Main:contactProcess - POST @@ -673,29 +673,36 @@ be accomplished with the following route configuration: $collection = new RouteCollection(); $collection->add('contact', new Route('/contact', array( '_controller' => 'AcmeDemoBundle:Main:contact', - ), array( - '_method' => 'GET', - ))); + ), array(), array(), '', array(), array('GET'))); $collection->add('contact_process', new Route('/contact', array( '_controller' => 'AcmeDemoBundle:Main:contactProcess', - ), array( - '_method' => 'POST', - ))); + ), array(), array(), '', array(), array('POST'))); return $collection; -Despite the fact that these two routes have identical patterns (``/contact``), +.. versionadded:: + The ``methods`` option is added in Symfony2.2. Use the ``_method`` + requirement in older versions. + +Despite the fact that these two routes have identical paths (``/contact``), the first route will match only GET requests and the second route will match only POST requests. This means that you can display the form and submit the form via the same URL, while using distinct controllers for the two actions. .. note:: - If no ``_method`` requirement is specified, the route will match on - *all* methods. -Like the other requirements, the ``_method`` requirement is parsed as a regular -expression. To match ``GET`` *or* ``POST`` requests, you can use ``GET|POST``. + If no ``methods`` are specified, the route will match on *all* methods. + +Adding a Host +~~~~~~~~~~~~~ + +.. versionadded:: 2.2 + Host matching support was added in Symfony 2.2 + +You can also match on the HTTP *host* of the incoming request. For more +information, see :doc:`/components/routing/hostname_pattern` in the Routing +component documentation. .. index:: single: Routing; Advanced example @@ -715,7 +722,7 @@ routing system can be: .. code-block:: yaml article_show: - pattern: /articles/{culture}/{year}/{title}.{_format} + path: /articles/{culture}/{year}/{title}.{_format} defaults: { _controller: AcmeDemoBundle:Article:show, _format: html } requirements: culture: en|fr @@ -730,7 +737,7 @@ routing system can be: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeDemoBundle:Article:show html en|fr @@ -777,6 +784,12 @@ a slash. URLs matching this route might look like: each value of ``_format``. The ``_format`` parameter is a very powerful way to render the same content in different formats. +.. note:: + + Sometimes you want to make certain parts of your routes globally configurable. + Symfony2.1 provides you with a way to do this by leveraging service container + parameters. Read more about this in ":doc:`/cookbook/routing/service_container_parameters`. + Special Routing Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -789,7 +802,12 @@ that are special: each adds a unique piece of functionality inside your applicat * ``_format``: Used to set the request format (:ref:`read more`); -* ``_locale``: Used to set the locale on the session (:ref:`read more`); +* ``_locale``: Used to set the locale on the request (:ref:`read more`); + +.. tip:: + + If you use the ``_locale`` parameter in a route, that value will also + be stored on the session so that subsequent requests keep this same locale. .. index:: single: Routing; Controllers @@ -940,7 +958,7 @@ like this: # src/Acme/HelloBundle/Resources/config/routing.yml acme_hello: - pattern: /hello/{name} + path: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } .. code-block:: xml @@ -952,7 +970,7 @@ like this: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeHelloBundle:Hello:index @@ -977,7 +995,7 @@ Prefixing Imported Routes ~~~~~~~~~~~~~~~~~~~~~~~~~ You can also choose to provide a "prefix" for the imported routes. For example, -suppose you want the ``acme_hello`` route to have a final pattern of ``/admin/hello/{name}`` +suppose you want the ``acme_hello`` route to have a final path of ``/admin/hello/{name}`` instead of simply ``/hello/{name}``: .. configuration-block:: @@ -1011,8 +1029,8 @@ instead of simply ``/hello/{name}``: return $collection; -The string ``/admin`` will now be prepended to the pattern of each route -loaded from the new routing resource. +The string ``/admin`` will now be prepended to the path of each route loaded +from the new routing resource. .. tip:: @@ -1020,6 +1038,15 @@ loaded from the new routing resource. :doc:`FrameworkExtraBundle documentation` to see how. +Adding a Host regex to Imported Routes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.2 + Host matching support was added in Symfony 2.2 + +You can set the host regex on imported routes. For more information, see +:ref:`component-routing-host-imported`. + .. index:: single: Routing; Debugging @@ -1054,6 +1081,17 @@ the route name after the command: $ php app/console router:debug article_show +.. versionadded:: 2.1 + The ``router:match`` command was added in Symfony 2.1 + +You can check which, if any, route matches a path with the ``router:match`` +console command: + +.. code-block:: bash + + $ php app/console router:match /articles/en/2012/article.rss + Route "article_show" matches + .. index:: single: Routing; Generating URLs @@ -1077,8 +1115,8 @@ system. Take the ``blog_show`` example route from earlier:: // /blog/my-blog-post To generate a URL, you need to specify the name of the route (e.g. ``blog_show``) -and any wildcards (e.g. ``slug = my-blog-post``) used in the pattern for -that route. With this information, any URL can easily be generated:: +and any wildcards (e.g. ``slug = my-blog-post``) used in the path for that +route. With this information, any URL can easily be generated:: class MainController extends Controller { diff --git a/book/security.rst b/book/security.rst index cfeb805e363..12c7dfd998e 100644 --- a/book/security.rst +++ b/book/security.rst @@ -55,9 +55,10 @@ authentication (i.e. the old-school username/password box): providers: in_memory: - users: - ryan: { password: ryanpass, roles: 'ROLE_USER' } - admin: { password: kitten, roles: 'ROLE_ADMIN' } + memory: + users: + ryan: { password: ryanpass, roles: 'ROLE_USER' } + admin: { password: kitten, roles: 'ROLE_ADMIN' } encoders: Symfony\Component\Security\Core\User\User: plaintext @@ -84,8 +85,10 @@ authentication (i.e. the old-school username/password box): - - + + + + @@ -110,9 +113,11 @@ authentication (i.e. the old-school username/password box): ), 'providers' => array( 'in_memory' => array( - 'users' => array( - 'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'), - 'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'), + 'memory' => array( + 'users' => array( + 'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'), + 'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'), + ), ), ), ), @@ -293,8 +298,8 @@ First, enable form login under your firewall: pattern: ^/ anonymous: ~ form_login: - login_path: /login - check_path: /login_check + login_path: login + check_path: login_check .. code-block:: xml @@ -310,7 +315,7 @@ First, enable form login under your firewall: - + @@ -324,8 +329,8 @@ First, enable form login under your firewall: 'pattern' => '^/', 'anonymous' => array(), 'form_login' => array( - 'login_path' => '/login', - 'check_path' => '/login_check', + 'login_path' => 'login', + 'check_path' => 'login_check', ), ), ), @@ -352,10 +357,11 @@ First, enable form login under your firewall: 'form_login' => array(), Now, when the security system initiates the authentication process, it will -redirect the user to the login form (``/login`` by default). Implementing -this login form visually is your job. First, create two routes: one that -will display the login form (i.e. ``/login``) and one that will handle the -login form submission (i.e. ``/login_check``): +redirect the user to the login form (``/login`` by default). Implementing this +login form visually is your job. First, the create two routes we used in the +security configuration: the ``login`` route will display the login form (i.e. +``/login``) and the ``login_check`` route will handle the login form +submission (i.e. ``/login_check``): .. configuration-block:: @@ -402,11 +408,14 @@ login form submission (i.e. ``/login_check``): You will *not* need to implement a controller for the ``/login_check`` URL as the firewall will automatically catch and process any form submitted - to this URL. It's optional, but helpful, to create a route so that you - can use it to generate the form submission URL in the login template below. + to this URL. + +.. versionadded:: 2.1 + As of Symfony 2.1, you *must* have routes configured for your ``login_path``, + ``check_path`` ``logout`` keys. These keys can be route names (as shown + in this example) or URLs that have routes configured for them. -Notice that the name of the ``login`` route isn't important. What's important -is that the URL of the route (``/login``) matches the ``login_path`` config +Notice that the name of the ``login`` route matches the``login_path`` config value, as that's where the security system will redirect users that need to login. @@ -550,7 +559,7 @@ see :doc:`/cookbook/security/form_login`. **1. Create the correct routes** - First, be sure that you've defined the ``/login`` and ``/login_check`` + First, be sure that you've defined the ``login`` and ``login_check`` routes correctly and that they correspond to the ``login_path`` and ``check_path`` config values. A misconfiguration here can mean that you're redirected to a 404 page instead of the login page, or that submitting @@ -916,8 +925,6 @@ Now, if the same request comes from ``127.0.0.1``: * The second access rule is not examined as the first rule matched. -.. include:: /book/_security-2012-6431.rst.inc - .. _book-security-securing-channel: Securing by Channel @@ -1053,9 +1060,10 @@ In fact, you've seen this already in the example in this chapter. # ... providers: default_provider: - users: - ryan: { password: ryanpass, roles: 'ROLE_USER' } - admin: { password: kitten, roles: 'ROLE_ADMIN' } + memory: + users: + ryan: { password: ryanpass, roles: 'ROLE_USER' } + admin: { password: kitten, roles: 'ROLE_ADMIN' } .. code-block:: xml @@ -1063,8 +1071,10 @@ In fact, you've seen this already in the example in this chapter. - - + + + + @@ -1075,9 +1085,11 @@ In fact, you've seen this already in the example in this chapter. // ... 'providers' => array( 'default_provider' => array( - 'users' => array( - 'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'), - 'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'), + 'memory' => array( + 'users' => array( + 'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'), + 'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'), + ), ), ), ), @@ -1149,6 +1161,12 @@ custom user class is that it implements the :class:`Symfony\\Component\\Security interface. This means that your concept of a "user" can be anything, as long as it implements this interface. +.. versionadded:: 2.1 + In Symfony 2.1, the ``equals`` method was removed from ``UserInterface``. + If you need to override the default implementation of comparison logic, + implement the new :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface` + interface. + .. note:: The user object will be serialized and saved in the session during requests, @@ -1222,9 +1240,10 @@ do the following: # ... providers: in_memory: - users: - ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' } - admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' } + memory: + users: + ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' } + admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' } encoders: Symfony\Component\Security\Core\User\User: @@ -1238,8 +1257,10 @@ do the following: - - + + + + @@ -1252,9 +1273,11 @@ do the following: // ... 'providers' => array( 'in_memory' => array( - 'users' => array( - 'ryan' => array('password' => 'bb87a29949f3a1ee0559f8a57357487151281386', 'roles' => 'ROLE_USER'), - 'admin' => array('password' => '74913f5cd5f61ec0bcfdb775414c2fb3d161b620', 'roles' => 'ROLE_ADMIN'), + 'memory' => array( + 'users' => array( + 'ryan' => array('password' => 'bb87a29949f3a1ee0559f8a57357487151281386', 'roles' => 'ROLE_USER'), + 'admin' => array('password' => '74913f5cd5f61ec0bcfdb775414c2fb3d161b620', 'roles' => 'ROLE_ADMIN'), + ), ), ), ), @@ -1315,6 +1338,13 @@ it as base64. In other words, the password has been greatly obfuscated so that the hashed password can't be decoded (i.e. you can't determine the password from the hashed password). +.. versionadded:: 2.2 + As of Symfony 2.2 you can also use the :ref:`PBKDF2` + and :ref:`BCrypt` password encoders. + +Determining the Hashed Password +............................... + If you have some sort of registration form for users, you'll need to be able to determine the hashed password so that you can set it on your user. No matter what algorithm you configure for your user object, the hashed password @@ -1339,6 +1369,16 @@ look like:: $user = $this->get('security.context')->getToken()->getUser(); } +In a controller this can be shortcut to: + +.. code-block:: php + + public function indexAction() + { + $user = $this->getUser(); + } + + .. note:: Anonymous users are technically authenticated, meaning that the ``isAuthenticated()`` @@ -1378,10 +1418,12 @@ a new provider that chains the two together: security: providers: chain_provider: - providers: [in_memory, user_db] + chain: + providers: [in_memory, user_db] in_memory: - users: - foo: { password: test } + memory: + users: + foo: { password: test } user_db: entity: { class: Acme\UserBundle\Entity\User, property: username } @@ -1390,11 +1432,15 @@ a new provider that chains the two together: - in_memory - user_db + + in_memory + user_db + - + + + @@ -1407,11 +1453,15 @@ a new provider that chains the two together: $container->loadFromExtension('security', array( 'providers' => array( 'chain_provider' => array( - 'providers' => array('in_memory', 'user_db'), + 'chain' => array( + 'providers' => array('in_memory', 'user_db'), + ), ), 'in_memory' => array( - 'users' => array( - 'foo' => array('password' => 'test'), + 'memory' => array( + 'users' => array( + 'foo' => array('password' => 'test'), + ), ), ), 'user_db' => array( @@ -1438,16 +1488,21 @@ the user from both the ``in_memory`` and ``user_db`` providers. security: providers: main_provider: - users: - foo: { password: test } - entity: { class: Acme\UserBundle\Entity\User, property: username } + memory: + users: + foo: { password: test } + entity: + class: Acme\UserBundle\Entity\User, + property: username .. code-block:: xml - + + + @@ -1458,8 +1513,10 @@ the user from both the ``in_memory`` and ``user_db`` providers. $container->loadFromExtension('security', array( 'providers' => array( 'main_provider' => array( - 'users' => array( - 'foo' => array('password' => 'test'), + 'memory' => array( + 'users' => array( + 'foo' => array('password' => 'test'), + ), ), 'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'), ), @@ -1645,16 +1702,21 @@ them, you can omit them entirely and shorten your configuration: 'logout' => array(), Note that you will *not* need to implement a controller for the ``/logout`` -URL as the firewall takes care of everything. You may, however, want to create +URL as the firewall takes care of everything. You *do*, however, need to create a route so that you can use it to generate the URL: +.. caution:: + + As of Symfony 2.1, you *must* have a route that corresponds to your logout + path. Without this route, logging out will not work. + .. configuration-block:: .. code-block:: yaml # app/config/routing.yml logout: - pattern: /logout + path: /logout .. code-block:: xml @@ -1665,7 +1727,7 @@ a route so that you can use it to generate the URL: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + @@ -1895,6 +1957,61 @@ cookie will be ever created by Symfony2): If you use a form login, Symfony2 will create a cookie even if you set ``stateless`` to ``true``. +Utilities +--------- + +.. versionadded:: 2.2 + The ``StringUtils`` and ``SecureRandom`` classes were added in Symfony 2.2 + +The Symfony Security Component comes with a collection of nice utilities related +to security. These utilities are used by Symfony, but you should also use +them if you want to solve the problem they address. + +Comparing Strings +~~~~~~~~~~~~~~~~~ + +The time it takes to compare two strings depends on their differences. This +can be used by an attacker when the two strings represent a password for +instance; it is known as a `Timing attack`_. + +Internally, when comparing two passwords, Symfony uses a constant-time +algorithm; you can use the same strategy in your own code thanks to the +:class:`Symfony\\Component\\Security\\Core\\Util\\StringUtils` class:: + + use Symfony\Component\Security\Core\Util\StringUtils; + + // is password1 equals to password2? + $bool = StringUtils::equals($password1, $password2); + +Generating a secure Random Number +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Whenever you need to generate a secure random number, you are highly +encouraged to use the Symfony +:class:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom` class:: + + use Symfony\Component\Security\Core\Util\SecureRandom; + + $generator = new SecureRandom(); + $random = $generator->nextBytes(10); + +The +:method:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom::nextBytes` +methods returns a random string composed of the number of characters passed as +an argument (10 in the above example). + +The SecureRandom class works better when OpenSSL is installed but when it's +not available, it falls back to an internal algorithm, which needs a seed file +to work correctly. Just pass a file name to enable it:: + + $generator = new SecureRandom('/some/path/to/store/the/seed.txt'); + $random = $generator->nextBytes(10); + +.. note:: + + You can also access a secure random instance directly from the Symfony + dependency injection container; its name is ``security.secure_random``. + Final Words ----------- @@ -1924,7 +2041,8 @@ Learn more from the Cookbook * :doc:`/cookbook/security/remember_me` .. _`Symfony's security component`: https://github.com/symfony/Security -.. _`JMSSecurityExtraBundle`: http://jmsyst.com/bundles/JMSSecurityExtraBundle/1.0 +.. _`JMSSecurityExtraBundle`: http://jmsyst.com/bundles/JMSSecurityExtraBundle/1.2 .. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle .. _`implement the \Serializable interface`: http://php.net/manual/en/class.serializable.php .. _`functions-online.com`: http://www.functions-online.com/sha1.html +.. _`Timing attack`: http://en.wikipedia.org/wiki/Timing_attack diff --git a/book/service_container.rst b/book/service_container.rst index 196e63b7b21..09ff961441b 100644 --- a/book/service_container.rst +++ b/book/service_container.rst @@ -227,6 +227,23 @@ The end result is exactly the same as before - the difference is only in to look for parameters with those names. When the container is built, it looks up the value of each parameter and uses it in the service definition. +.. versionadded:: 2.1 + Escaping the ``@`` character in YAML parameter values is new in Symfony 2.1.9 + and Symfony 2.2.1. + +.. note:: + + If you want to use a string that starts with an ``@`` sign as a parameter + value (i.e. a very safe mailer password) in a yaml file, you need to escape + it by adding another ``@`` sign (This only applies to the YAML format): + + .. code-block:: yaml + + # app/config/parameters.yml + parameters: + # This will be parsed as string "@securepass" + mailer_password: "@@securepass" + The purpose of parameters is to feed information into services. Of course there was nothing wrong with defining the service without using any parameters. Parameters, however, have several advantages: @@ -408,7 +425,6 @@ invokes the service container extension inside the ``FrameworkBundle``: # app/config/config.yml framework: secret: xxxxxxxxxx - charset: UTF-8 form: true csrf_protection: true router: { resource: "%kernel.root_dir%/config/routing.yml" } @@ -417,7 +433,7 @@ invokes the service container extension inside the ``FrameworkBundle``: .. code-block:: xml - + @@ -429,7 +445,6 @@ invokes the service container extension inside the ``FrameworkBundle``: // app/config/config.php $container->loadFromExtension('framework', array( 'secret' => 'xxxxxxxxxx', - 'charset' => 'UTF-8', 'form' => array(), 'csrf-protection' => array(), 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'), @@ -451,7 +466,7 @@ extension of the ``FrameworkBundle``. Each extension allows you to easily customize the bundle, without worrying about how the internal services are defined. -In this case, the extension allows you to customize the ``charset``, ``error_handler``, +In this case, the extension allows you to customize the ``error_handler``, ``csrf_protection``, ``router`` configuration and much more. Internally, the ``FrameworkBundle`` uses the options specified here to define and configure the services specific to it. The bundle takes care of creating all the necessary diff --git a/book/templating.rst b/book/templating.rst index e84106eaa5a..b81de59e419 100644 --- a/book/templating.rst +++ b/book/templating.rst @@ -376,6 +376,11 @@ When working with template inheritance, here are some tips to keep in mind: Template Naming and Locations ----------------------------- +.. versionadded:: 2.2 + Namespaced path support was added in 2.2, allowing for template names + like ``@AcmeDemo/layout.html.twig``. See :doc:`/cookbook/templating/namespaced_paths` + for more details. + By default, templates can live in two different locations: * ``app/Resources/views/``: The applications ``views`` directory can contain @@ -532,9 +537,7 @@ Including this template from any other template is simple:

Recent Articles

{% for article in articles %} - {% include 'AcmeArticleBundle:Article:articleDetails.html.twig' - with {'article': article} - %} + {{ include('AcmeArticleBundle:Article:articleDetails.html.twig', {'article': article}) }} {% endfor %} {% endblock %} @@ -554,7 +557,7 @@ Including this template from any other template is simple: stop() ?> -The template is included using the ``{% include %}`` tag. Notice that the +The template is included using the ``{{ include() }}`` function. Notice that the template name follows the same typical convention. The ``articleDetails.html.twig`` template uses an ``article`` variable. This is passed in by the ``list.html.twig`` template using the ``with`` command. @@ -565,6 +568,10 @@ template using the ``with`` command. maps (i.e. an array with named keys). If you needed to pass in multiple elements, it would look like this: ``{'foo': foo, 'bar': bar}``. +.. versionadded:: 2.2 + The ``include()`` function is a new Twig feature that's available in + Symfony 2.2. Prior, the ``{% include %}`` tag was used. + .. index:: single: Templating; Embedding action @@ -626,43 +633,8 @@ The ``recentList`` template is perfectly straightforward: (e.g. ``/article/*slug*``). This is a bad practice. In the next section, you'll learn how to do this correctly. -Even though this controller will only be used internally, you'll need to -create a route that points to the controller: - -.. configuration-block:: - - .. code-block:: yaml - - latest_articles: - pattern: /articles/latest/{max} - defaults: { _controller: AcmeArticleBundle:Article:recentArticles } - - .. code-block:: xml - - - - - - - AcmeArticleBundle:Article:recentArticles - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('latest_articles', new Route('/articles/latest/{max}', array( - '_controller' => 'AcmeArticleBundle:Article:recentArticles', - ))); - - return $collection; - -To include the controller, you'll need to refer to it using an absolute url: +To include the controller, you'll need to refer to it using the standard +string syntax for controllers (i.e. **bundle**:**controller**:**action**): .. configuration-block:: @@ -672,7 +644,7 @@ To include the controller, you'll need to refer to it using an absolute url: {# ... #} .. code-block:: html+php @@ -682,16 +654,145 @@ To include the controller, you'll need to refer to it using an absolute url: -.. include:: /book/_security-2012-6431.rst.inc - Whenever you find that you need a variable or a piece of information that you don't have access to in a template, consider rendering a controller. Controllers are fast to execute and promote good code organization and reuse. +Asynchronous Content with hinclude.js +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + hinclude.js support was added in Symfony 2.1 + +Controllers can be embedded asynchronously using the hinclude.js_ javascript library. +As the embedded content comes from another page (or controller for that matter), +Symfony2 uses the standard ``render`` helper to configure ``hinclude`` tags: + +.. configuration-block:: + + .. code-block:: jinja + + {% render url('...') with {}, {'standalone': 'js'} %} + + .. code-block:: php + + render( + new ControllerReference('...'), + array('renderer' => 'hinclude') + ) ?> + + render( + $view['router']->generate('...'), + array('renderer' => 'hinclude') + ) ?> + +.. note:: + + hinclude.js_ needs to be included in your page to work. + +.. note:: + + When using a controller instead of a URL, you must enable the Symfony + ``fragments`` configuration: + + .. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + framework: + # ... + fragments: { path: /_fragment } + + .. code-block:: xml + + + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('framework', array( + // ... + 'fragments' => array('path' => '/_fragment'), + )); + +Default content (while loading or if javascript is disabled) can be set globally +in your application configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + framework: + # ... + templating: + hinclude_default_template: AcmeDemoBundle::hinclude.html.twig + + .. code-block:: xml + + + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('framework', array( + // ... + 'templating' => array( + 'hinclude_default_template' => array('AcmeDemoBundle::hinclude.html.twig'), + ), + )); + +.. versionadded:: 2.2 + Default templates per render function was added in Symfony 2.2 + +You can define default templates per ``render`` function (which will override +any global default template that is defined): + +.. configuration-block:: + + .. code-block:: jinja + + {{ render_hinclude(controller('...'), {'default': 'AcmeDemoBundle:Default:content.html.twig'}) }} + + .. code-block:: php + + render( + new ControllerReference('...'), + array( + 'renderer' => 'hinclude', + 'default' => 'AcmeDemoBundle:Default:content.html.twig', + ) + ) ?> + +Or you can also specify a string to display as the default content: + +.. configuration-block:: + + .. code-block:: jinja + + {{ render_hinclude(controller('...'), {'default': 'Loading...'}) }} + + .. code-block:: php + + render( + new ControllerReference('...'), + array( + 'renderer' => 'hinclude', + 'default' => 'Loading...', + ) + ) ?> + .. index:: single: Templating; Linking to pages @@ -715,12 +816,12 @@ configuration: .. code-block:: yaml _welcome: - pattern: / + path: / defaults: { _controller: AcmeDemoBundle:Welcome:index } .. code-block:: xml - + AcmeDemoBundle:Welcome:index @@ -753,12 +854,12 @@ route: .. code-block:: yaml article_show: - pattern: /article/{slug} + path: /article/{slug} defaults: { _controller: AcmeArticleBundle:Article:show } .. code-block:: xml - + AcmeArticleBundle:Article:show @@ -1334,6 +1435,26 @@ The variables will only be dumped if Twig's ``debug`` setting (in ``config.yml`` is ``true``. By default this means that the variables will be dumped in the ``dev`` environment but not the ``prod`` environment. +Syntax Checking +--------------- + +.. versionadded:: 2.1 + The ``twig:lint`` command was added in Symfony 2.1 + +You can check for syntax errors in Twig templates using the ``twig:lint`` +console command: + +.. code-block:: bash + + # You can check by filename: + $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig + + # or by directory: + $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views + + # or using the bundle name: + $ php app/console twig:lint @AcmeArticleBundle + Template Formats ---------------- @@ -1425,4 +1546,4 @@ Learn more from the Cookbook .. _`tags`: http://twig.sensiolabs.org/doc/tags/index.html .. _`filters`: http://twig.sensiolabs.org/doc/filters/index.html .. _`add your own extensions`: http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension - +.. _`hinclude.js`: http://mnot.github.com/hinclude/ diff --git a/book/testing.rst b/book/testing.rst index 0d63abcd76e..75c1e597415 100644 --- a/book/testing.rst +++ b/book/testing.rst @@ -17,7 +17,8 @@ it has its own excellent `documentation`_. .. note:: - Symfony2 works with PHPUnit 3.5.11 or later. + Symfony2 works with PHPUnit 3.5.11 or later, though version 3.6.4 is + needed to test the Symfony core code itself. Each test - whether it's a unit test or a functional test - is a PHP class that should live in the `Tests/` subdirectory of your bundles. If you follow @@ -430,13 +431,19 @@ HTTP layer. For a list of services available in your application, use the Accessing the Profiler Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -On each request, the Symfony profiler collects and stores a lot of data about -the internal handling of that request. For example, the profiler could be -used to verify that a given page executes less than a certain number of database +On each request, you can enable the Symfony profiler to collect data about the +internal handling of that request. For example, the profiler could be used to +verify that a given page executes less than a certain number of database queries when loading. To get the Profiler for the last request, do the following:: + // enable the profiler for the very next request + $client->enableProfiler(); + + $crawler = $client->request('GET', '/profiler'); + + // get the profile $profile = $client->getProfile(); For specific details on using the profiler inside a test, see the diff --git a/book/translation.rst b/book/translation.rst index b99720f5169..a44347d826c 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -37,7 +37,8 @@ the process has several common steps: #. Create translation resources for each supported locale that translate each message in the application; -#. Determine, set and manage the user's locale in the session. +#. Determine, set and manage the user's locale for the request and optionally + on the user's entire session. .. index:: single: Translations; Configuration @@ -81,7 +82,8 @@ not exist in the user's locale. ``fr_FR`` for instance). If this also fails, it looks for a translation using the fallback locale. -The locale used in translations is the one stored in the user session. +The locale used in translations is the one stored on the request. This is +typically set via a ``_locale`` attribute on your routes (see :ref:`book-translation-locale-url`). .. index:: single: Translations; Basic translation @@ -149,7 +151,8 @@ The Translation Process To actually translate the message, Symfony2 uses a simple process: -* The ``locale`` of the current user, which is stored in the session, is determined; +* The ``locale`` of the current user, which is stored on the request (or + stored as ``_locale`` on the session), is determined; * A catalog of translated messages is loaded from translation resources defined for the ``locale`` (e.g. ``fr_FR``). Messages from the fallback locale are @@ -287,17 +290,25 @@ filesystem and discovered by Symfony, thanks to some conventions. Translation Locations and Naming Conventions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Symfony2 looks for message files (i.e. translations) in two locations: +Symfony2 looks for message files (i.e. translations) in the following locations: -* For messages found in a bundle, the corresponding message files should - live in the ``Resources/translations/`` directory of the bundle; +* the ``/Resources/translations`` directory; -* To override any bundle translations, place message files in the - ``app/Resources/translations`` directory. +* the ``/Resources//translations`` directory; + +* the ``Resources/translations/`` directory of the bundle. + +The locations are listed with the highest priority first. That is you can +override the translation messages of a bundle in any of the top 2 directories. + +The override mechanism works at a key level: only the overridden keys need +to be listed in a higher priority message file. When a key is not found +in a message file, the translator will automatically fall back to the lower +priority message files. The filename of the translations is also important as Symfony2 uses a convention to determine details about the translations. Each message file must be named -according to the following pattern: ``domain.locale.loader``: +according to the following path: ``domain.locale.loader``: * **domain**: An optional way to organize messages into groups (e.g. ``admin``, ``navigation`` or the default ``messages``) - see `Using Message Domains`_; @@ -488,16 +499,29 @@ locale. Handling the User's Locale -------------------------- -The locale of the current user is stored in the session and is accessible -via the ``session`` service:: +The locale of the current user is stored in the request and is accessible +via the ``request`` object:: - $locale = $this->get('session')->getLocale(); + // access the request object in a standard controller + $request = $this->getRequest(); - $this->get('session')->setLocale('en_US'); + $locale = $request->getLocale(); + + $request->setLocale('en_US'); .. index:: single: Translations; Fallback and default locale +It is also possible to store the locale in the session instead of on a per +request basis. If you do this, each subsequent request will have this locale. + +.. code-block:: php + + $this->get('session')->set('_locale', 'en_US'); + +See the :ref:`book-translation-locale-url` section below about setting the +locale via routing. + Fallback and Default Locale ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -505,8 +529,8 @@ If the locale hasn't been set explicitly in the session, the ``fallback_locale`` configuration parameter will be used by the ``Translator``. The parameter defaults to ``en`` (see `Configuration`_). -Alternatively, you can guarantee that a locale is set on the user's session -by defining a ``default_locale`` for the session service: +Alternatively, you can guarantee that a locale is set on each user's request +by defining a ``default_locale`` for the framework: .. configuration-block:: @@ -514,28 +538,33 @@ by defining a ``default_locale`` for the session service: # app/config/config.yml framework: - session: { default_locale: en } + default_locale: en .. code-block:: xml - + en .. code-block:: php // app/config/config.php $container->loadFromExtension('framework', array( - 'session' => array('default_locale' => 'en'), + 'default_locale' => 'en', )); +.. versionadded:: 2.1 + The ``default_locale`` parameter was defined under the session key + originally, however, as of 2.1 this has been moved. This is because the + locale is now set on the request instead of the session. + .. _book-translation-locale-url: The Locale and the URL ~~~~~~~~~~~~~~~~~~~~~~ -Since the locale of the user is stored in the session, it may be tempting +Since you can store the locale of the user in the session, it may be tempting to use the same URL to display a resource in many different languages based on the user's locale. For example, ``http://www.example.com/contact`` could show content in English for one user and French for another user. Unfortunately, @@ -551,14 +580,14 @@ by the routing system using the special ``_locale`` parameter: .. code-block:: yaml contact: - pattern: /{_locale}/contact + path: /{_locale}/contact defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en } requirements: _locale: en|fr|de .. code-block:: xml - + AcmeDemoBundle:Contact:index en en|fr|de @@ -712,7 +741,7 @@ Translations in Templates Most of the time, translation occurs in templates. Symfony2 provides native support for both Twig and PHP templates. -.. _book-translation-twig: +.. _book-translation-tags: Twig Templates ~~~~~~~~~~~~~~ @@ -749,6 +778,8 @@ You can also specify the message domain and pass some additional variables: {0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf] %name%, there are %count% apples {% endtranschoice %} +.. _book-translation-filters: + The ``trans`` and ``transchoice`` filters can be used to translate *variable texts* and complex expressions: @@ -783,6 +814,20 @@ texts* and complex expressions: {{ message|trans|raw }} {{ '

bar

'|trans|raw }} +.. tip:: + + You can set the translation domain for an entire Twig template with a single tag: + + .. code-block:: jinja + + {% trans_default_domain "app" %} + + Note that this only influences the current template, not any "included" + templates (in order to avoid side effects). + +.. versionadded:: 2.1 + The ``trans_default_domain`` tag is new in Symfony2.1 + PHP Templates ~~~~~~~~~~~~~ @@ -802,7 +847,7 @@ The translator service is accessible in PHP templates through the Forcing the Translator Locale ----------------------------- -When translating a message, Symfony2 uses the locale from the user's session +When translating a message, Symfony2 uses the locale from the current request or the ``fallback`` locale if necessary. You can also manually specify the locale to use for translation:: @@ -955,7 +1000,8 @@ steps: files. Symfony2 discovers and processes each file because its name follows a specific convention; -* Manage the user's locale, which is stored in the session. +* Manage the user's locale, which is stored on the request, but can also + be set on the user's session. .. _`i18n`: http://en.wikipedia.org/wiki/Internationalization_and_localization .. _`L10n`: http://en.wikipedia.org/wiki/Internationalization_and_localization diff --git a/book/validation.rst b/book/validation.rst index 0a249c9e99d..42e1ce8ff0b 100644 --- a/book/validation.rst +++ b/book/validation.rst @@ -220,8 +220,8 @@ workflow looks like the following from inside a controller:: $author = new Author(); $form = $this->createForm(new AuthorType(), $author); - if ($request->getMethod() == 'POST') { - $form->bindRequest($request); + if ($request->isMethod('POST')) { + $form->bind($request); if ($form->isValid()) { // the validation passed, do something with the $author object @@ -504,7 +504,8 @@ class to have at least 3 characters. properties: firstName: - NotBlank: ~ - - MinLength: 3 + - Length: + min: 3 .. code-block:: php-annotations @@ -517,7 +518,7 @@ class to have at least 3 characters. { /** * @Assert\NotBlank() - * @Assert\MinLength(3) + * @Assert\Length(min = "3") */ private $firstName; } @@ -528,7 +529,9 @@ class to have at least 3 characters. - 3 + + + @@ -539,7 +542,7 @@ class to have at least 3 characters. // ... use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; - use Symfony\Component\Validator\Constraints\MinLength; + use Symfony\Component\Validator\Constraints\Length; class Author { @@ -548,7 +551,9 @@ class to have at least 3 characters. public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('firstName', new NotBlank()); - $metadata->addPropertyConstraint('firstName', new MinLength(3)); + $metadata->addPropertyConstraint( + 'firstName', + new Length(array("min" => 3))); } } @@ -677,9 +682,10 @@ user registers and when a user updates his/her contact information later: - Email: { groups: [registration] } password: - NotBlank: { groups: [registration] } - - MinLength: { limit: 7, groups: [registration] } + - Length: { min: 7, groups: [registration] } city: - - MinLength: 2 + - Length: + min: 2 .. code-block:: php-annotations @@ -698,12 +704,12 @@ user registers and when a user updates his/her contact information later: /** * @Assert\NotBlank(groups={"registration"}) - * @Assert\MinLength(limit=7, groups={"registration"}) + * @Assert\Length(min=7, groups={"registration"}) */ private $password; /** - * @Assert\MinLength(2) + * @Assert\Length(min = "2") */ private $city; } @@ -725,15 +731,17 @@ user registers and when a user updates his/her contact information later: registration - - + + - 7 + + + @@ -745,7 +753,7 @@ user registers and when a user updates his/her contact information later: use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\NotBlank; - use Symfony\Component\Validator\Constraints\MinLength; + use Symfony\Component\Validator\Constraints\Length; class User { @@ -758,12 +766,14 @@ user registers and when a user updates his/her contact information later: $metadata->addPropertyConstraint('password', new NotBlank(array( 'groups' => array('registration'), ))); - $metadata->addPropertyConstraint('password', new MinLength(array( - 'limit' => 7, - 'groups' => array('registration'), + $metadata->addPropertyConstraint('password', new Length(array( + 'min' => 7, + 'groups' => array('registration') ))); - $metadata->addPropertyConstraint('city', new MinLength(3)); + $metadata->addPropertyConstraint( + 'city', + Length(array("min" => 3))); } } diff --git a/bundles/map.rst.inc b/bundles/map.rst.inc index 626e25e57bd..44424cc6a1d 100644 --- a/bundles/map.rst.inc +++ b/bundles/map.rst.inc @@ -1,8 +1,10 @@ * :doc:`SensioFrameworkExtraBundle ` * :doc:`SensioGeneratorBundle ` * `JMSSecurityExtraBundle`_ +* `JMSDiExtraBundle`_ * :doc:`DoctrineFixturesBundle ` * :doc:`DoctrineMigrationsBundle ` * :doc:`DoctrineMongoDBBundle ` -.. _`JMSSecurityExtraBundle`: http://jmsyst.com/bundles/JMSSecurityExtraBundle/1.0 +.. _`JMSSecurityExtraBundle`: http://jmsyst.com/bundles/JMSSecurityExtraBundle/1.2 +.. _`JMSDiExtraBundle`: http://jmsyst.com/bundles/JMSDiExtraBundle/1.1 diff --git a/components/class_loader.rst b/components/class_loader.rst index f0bd37c0de3..1e7064b1894 100644 --- a/components/class_loader.rst +++ b/components/class_loader.rst @@ -33,6 +33,9 @@ You can install the component in many different ways: Usage ----- +.. versionadded:: 2.1 + The ``useIncludePath`` method was added in Symfony 2.1. + Registering the :class:`Symfony\\Component\\ClassLoader\\UniversalClassLoader` autoloader is straightforward:: @@ -42,6 +45,9 @@ autoloader is straightforward:: $loader = new UniversalClassLoader(); + // You can search the include_path as a last resort. + $loader->useIncludePath(true); + // ... register namespaces and prefixes here - see below $loader->register(); @@ -70,11 +76,11 @@ or :method:`Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerNamespaces` methods:: - $loader->registerNamespace('Symfony', __DIR__.'/vendor/symfony/src'); + $loader->registerNamespace('Symfony', __DIR__.'/vendor/symfony/symfony/src'); $loader->registerNamespaces(array( - 'Symfony' => __DIR__.'/../vendor/symfony/src', - 'Monolog' => __DIR__.'/../vendor/monolog/src', + 'Symfony' => __DIR__.'/../vendor/symfony/symfony/src', + 'Monolog' => __DIR__.'/../vendor/monolog/monolog/src', )); $loader->register(); @@ -85,11 +91,11 @@ or :method:`Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerPrefixes` methods:: - $loader->registerPrefix('Twig_', __DIR__.'/vendor/twig/lib'); + $loader->registerPrefix('Twig_', __DIR__.'/vendor/twig/twig/lib'); $loader->registerPrefixes(array( - 'Swift_' => __DIR__.'/vendor/swiftmailer/lib/classes', - 'Twig_' => __DIR__.'/vendor/twig/lib', + 'Swift_' => __DIR__.'/vendor/swiftmailer/swiftmailer/lib/classes', + 'Twig_' => __DIR__.'/vendor/twig/twig/lib', )); $loader->register(); @@ -104,10 +110,10 @@ for in a location list to ease the vendoring of a sub-set of classes for large projects:: $loader->registerNamespaces(array( - 'Doctrine\\Common' => __DIR__.'/vendor/doctrine-common/lib', - 'Doctrine\\DBAL\\Migrations' => __DIR__.'/vendor/doctrine-migrations/lib', - 'Doctrine\\DBAL' => __DIR__.'/vendor/doctrine-dbal/lib', - 'Doctrine' => __DIR__.'/vendor/doctrine/lib', + 'Doctrine\\Common' => __DIR__.'/vendor/doctrine/common/lib', + 'Doctrine\\DBAL\\Migrations' => __DIR__.'/vendor/doctrine/migrations/lib', + 'Doctrine\\DBAL' => __DIR__.'/vendor/doctrine/dbal/lib', + 'Doctrine' => __DIR__.'/vendor/doctrine/orm/lib', )); $loader->register(); @@ -120,4 +126,4 @@ The order of the registrations is significant in this case. .. _standards: http://symfony.com/PSR0 .. _PEAR: http://pear.php.net/manual/en/standards.php -.. _Packagist: https://packagist.org/packages/symfony/class-loader \ No newline at end of file +.. _Packagist: https://packagist.org/packages/symfony/class-loader diff --git a/components/config/definition.rst b/components/config/definition.rst index 293da6b0b9b..df801e2662f 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -100,11 +100,39 @@ node definition. Node type are available for: * scalar * boolean * array +* enum (new in 2.1) +* integer (new in 2.2) +* float (new in 2.2) * variable (no validation) and are created with ``node($name, $type)`` or their associated shortcut ``xxxxNode($name)`` method. +Numeric node constraints +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.2 + The numeric (float and integer) nodes are new in 2.2 + +Numeric nodes (float and integer) provide two extra constraints - +:method:`Symfony\\Component\\Config\\Definition\\Builder::min` and +:method:`Symfony\\Component\\Config\\Definition\\Builder::max` - +allowing to validate the value:: + + $rootNode + ->children() + ->integerNode('positive_value') + ->min(0) + ->end() + ->floatNode('big_value') + ->max(5E45) + ->end() + ->integerNode('value_inside_a_range') + ->min(-50)->max(50) + ->end() + ->end() + ; + Array nodes ~~~~~~~~~~~ @@ -239,6 +267,35 @@ has a certain value: ->end() ; +Optional Sections +----------------- + +.. versionadded:: 2.2 + The ``canBeEnabled`` and ``canBeDisabled`` methods are new in Symfony 2.2 + +If you have entire sections which are optional and can be enabled/disabled, +you can take advantage of the shortcut +:method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeEnabled` and +:method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeDisabled` methods:: + + $arrayNode + ->canBeEnabled() + ; + + // is equivalent to + + $arrayNode + ->treatFalseLike(array('enabled' => false)) + ->treatTrueLike(array('enabled' => true)) + ->treatNullLike(array('enabled' => true)) + ->children() + ->booleanNode('enabled') + ->defaultFalse() + ; + +The ``canBeDisabled`` method looks about the same except that the section +would be enabled by default. + Merging options --------------- diff --git a/components/console/events.rst b/components/console/events.rst new file mode 100644 index 00000000000..7306aa50d06 --- /dev/null +++ b/components/console/events.rst @@ -0,0 +1,116 @@ +.. index:: + single: Console; Events + +Using Events +============ + +.. versionadded:: 2.3 + Console events were added in Symfony 2.3. + +The Application class of the Console component allows you to optionally hook +into the lifecycle of a console application via events. Instead of reinventing +the wheel, it uses the Symfony EventDispatcher component to do the work:: + + use Symfony\Component\Console\Application; + use Symfony\Component\EventDispatcher\EventDispatcher; + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->run(); + +The ``ConsoleEvents::COMMAND`` Event +------------------------------------ + +**Typical Purposes**: Doing something before any command is run (like logging +which command is going to be executed), or displaying something about the event +to be executed. + +Just before executing any command, the ``ConsoleEvents::COMMAND`` event is +dispatched. Listeners receive a +:class:`Symfony\\Component\\Console\\Event\\ConsoleCommandEvent` event:: + + use Symfony\Component\Console\Event\ConsoleCommandEvent; + use Symfony\Component\Console\ConsoleEvents; + + $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) { + // get the input instance + $input = $event->getInput(); + + // get the output instance + $output = $event->getOutput(); + + // get the command to be executed + $command = $event->getCommand(); + + // write something about the command + $output->writeln(sprintf('Before running command %s', $command->getName())); + + // get the application + $application = $command->getApplication(); + }); + +The ``ConsoleEvents::TERMINATE`` event +-------------------------------------- + +**Typical Purposes**: To perform some cleanup actions after the command has +been executed. + +After the command has been executed, the ``ConsoleEvents::TERMINATE`` event is +dispatched. It can be used to do any actions that need to be executed for all +commands or to cleanup what you initiated in a ``ConsoleEvents::COMMAND`` +listener (like sending logs, closing a database connection, sending emails, +...). A listener might also change the exit code. + +Listeners receive a +:class:`Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent` event:: + + use Symfony\Component\Console\Event\ConsoleTerminateEvent; + use Symfony\Component\Console\ConsoleEvents; + + $dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) { + // get the output + $output = $event->getOutput(); + + // get the command that has been executed + $command = $event->getCommand(); + + // display something + $output->writeln(sprintf('After running command %s', $command->getName())); + + // change the exit code + $event->setExitCode(128); + }); + +.. tip:: + + This event is also dispatched when an exception is thrown by the command. + It is then dispatched just before the ``ConsoleEvents::EXCEPTION`` event. + The exit code received in this case is the exception code. + +The ``ConsoleEvents::EXCEPTION`` event +-------------------------------------- + +**Typical Purposes**: Handle exceptions thrown during the execution of a +command. + +Whenever an exception is thrown by a command, the ``ConsoleEvents::EXCEPTION`` +event is dispatched. A listener can wrap or change the exception or do +anything useful before the exception is thrown by the application. + +Listeners receive a +:class:`Symfony\\Component\\Console\\Event\\ConsoleForExceptionEvent` event:: + + use Symfony\Component\Console\Event\ConsoleForExceptionEvent; + use Symfony\Component\Console\ConsoleEvents; + + $dispatcher->addListener(ConsoleEvents::EXCEPTION, function (ConsoleForExceptionEvent $event) { + $output = $event->getOutput(); + + $output->writeln(sprintf('Oops, exception thrown while running command %s', $command->getName())); + + // get the current exit code (the exception code or the exit code set by a ConsoleEvents::TERMINATE event) + $exitCode = $event->getExitCode(); + + // change the exception to another one + $event->setException(new \LogicException('Caught exception', $exitCode, $event->getException())); + }); diff --git a/components/console/helpers/dialoghelper.rst b/components/console/helpers/dialoghelper.rst index 70a0bcd60d1..90550bf6913 100644 --- a/components/console/helpers/dialoghelper.rst +++ b/components/console/helpers/dialoghelper.rst @@ -53,6 +53,30 @@ The user will be asked "Please enter the name of the bundle". She can type some name which will be returned by the ``ask`` method. If she leaves it empty, the default value (``AcmeDemoBundle`` here) is returned. +Hiding the User's Response +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.2 + The ``askHiddenResponse`` method was added in Symfony 2.2. + +You can also ask a question and hide the response. This is particularly +convenient for passwords:: + + $dialog = $this->getHelperSet()->get('dialog'); + $password = $dialog->askHiddenResponse( + $output, + 'What is the database password?', + false + ); + +.. caution:: + + When you ask for a hidden response, Symfony will use either a binary, change + stty mode or use another trick to hide the response. If none is available, + it will fallback and allow the response to be visible unless you pass ``false`` + as the third argument like in the example above. In this case, a RuntimeException + would be thrown. + Validating the Answer --------------------- @@ -97,4 +121,70 @@ You can set the max number of times to ask in the ``$attempts`` argument. If you reach this max number it will use the default value, which is given in the last argument. Using ``false`` means the amount of attempts is infinite. The user will be asked as long as he provides an invalid answer and will only -be able to proceed if her input is valid. \ No newline at end of file +be able to proceed if her input is valid. + +Hiding the User's Response +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.2 + The ``askHiddenResponseAndValidate`` method was added in Symfony 2.2. + +You can also ask and validate a hidden response:: + + $dialog = $this->getHelperSet()->get('dialog'); + + $validator = function ($value) { + if (trim($value) == '') { + throw new \Exception('The password can not be empty'); + } + }; + + $password = $dialog->askHiddenResponseAndValidate( + $output, + 'Please enter the name of the widget', + $validator, + 20, + false + ); + +If you want to allow the response to be visible if it cannot be hidden for +some reason, pass true as the fifth argument. + +Let the user choose from a list of Answers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.2 + The :method:`Symfony\\Component\\Console\\Helper\\DialogHelper::select` method + was added in Symfony 2.2. + +If you have a predefined set of answers the user can choose from, you +could use the ``ask`` method described above or, to make sure the user +provided a correct answer, the ``askAndValidate`` method. Both have +the disadvantage that you need to handle incorrect values yourself. + +Instead, you can use the +:method:`Symfony\\Component\\Console\\Helper\\DialogHelper::select` +method, which makes sure that the user can only enter a valid string +from a predefined list:: + + $dialog = $app->getHelperSet()->get('dialog'); + $colors = array('red', 'blue', 'yellow'); + + $color = $dialog->select( + $output, + 'Please select your favorite color (default to red)', + $colors, + 0 + ); + $output->writeln('You have just selected: ' . $colors[$color]); + + // ... do something with the color + +The option which should be selected by default is provided with the fourth +parameter. The default is ``null``, which means that no option is the default one. + +If the user enters an invalid string, an error message is shown and the user +is asked to provide the answer another time, until she enters a valid string +or the maximum attempts is reached (which you can define in the fifth +parameter). The default value for the attempts is ``false``, which means infinite +attempts. You can define your own error message in the sixth parameter. diff --git a/components/console/helpers/index.rst b/components/console/helpers/index.rst index 9c0c19464b1..adb2a4bbc8a 100644 --- a/components/console/helpers/index.rst +++ b/components/console/helpers/index.rst @@ -9,6 +9,7 @@ The Console Helpers dialoghelper formatterhelper + progresshelper The Console Components comes with some useful helpers. These helpers contain function to ease some common tasks. diff --git a/components/console/helpers/map.rst.inc b/components/console/helpers/map.rst.inc index d324d8c2aeb..cbc819ad832 100644 --- a/components/console/helpers/map.rst.inc +++ b/components/console/helpers/map.rst.inc @@ -1,2 +1,3 @@ * :doc:`/components/console/helpers/dialoghelper` * :doc:`/components/console/helpers/formatterhelper` +* :doc:`/components/console/helpers/progresshelper` diff --git a/components/console/helpers/progresshelper.rst b/components/console/helpers/progresshelper.rst new file mode 100644 index 00000000000..f77c52490cf --- /dev/null +++ b/components/console/helpers/progresshelper.rst @@ -0,0 +1,82 @@ +.. index:: + single: Console Helpers; Progress Helper + +Progress Helper +=============== + +.. versionadded:: 2.2 + The ``progress`` helper was added in Symfony 2.2. + +.. versionadded:: 2.3 + The ``setCurrent`` method was added in Symfony 2.3. + +When executing longer-running commands, it may be helpful to show progress +information, which updates as your command runs: + +.. image:: /images/components/console/progress.png + +To display progress details, use the :class:`Symfony\\Component\\Console\\Helper\\ProgressHelper`, +pass it a total number of units, and advance the progress as your command executes:: + + $progress = $this->getHelperSet()->get('progress'); + + $progress->start($output, 50); + $i = 0; + while ($i++ < 50) { + // ... do some work + + // advance the progress bar 1 unit + $progress->advance(); + } + + $progress->finish(); + +.. tip:: + + You can also set the current progress by calling the + :method:`Symfony\\Component\\Console\\Helper\\ProgressHelper::setCurrent` + method. + +The appearance of the progress output can be customized as well, with a number +of different levels of verbosity. Each of these displays different possible +items - like percentage completion, a moving progress bar, or current/total +information (e.g. 10/50):: + + $progress->setFormat(ProgressHelper::FORMAT_QUIET); + $progress->setFormat(ProgressHelper::FORMAT_NORMAL); + $progress->setFormat(ProgressHelper::FORMAT_VERBOSE); + $progress->setFormat(ProgressHelper::FORMAT_QUIET_NOMAX); + // the default value + $progress->setFormat(ProgressHelper::FORMAT_NORMAL_NOMAX); + $progress->setFormat(ProgressHelper::FORMAT_VERBOSE_NOMAX); + +You can also control the different characters and the width used for the +progress bar:: + + // the finished part of the bar + $progress->setBarCharacter('='); + // the unfinished part of the bar + $progress->setEmptyBarCharacter(' '); + $progress->setProgressCharacter('|'); + $progress->setBarWidth(50); + +To see other available options, check the API documentation for +:class:`Symfony\\Component\\Console\\Helper\\ProgressHelper`. + +.. caution:: + + For performance reasons, be careful to not set the total number of steps + to a high number. For example, if you're iterating over a large number + of items, consider a smaller "step" number that updates on only some + iterations:: + + $progress->start($output, 500); + $i = 0; + while ($i++ < 50000) { + // ... do some work + + // advance every 100 iterations + if ($i % 100 == 0) { + $progress->advance(); + } + } diff --git a/components/console/index.rst b/components/console/index.rst index 4d0a12e4d70..c814942d018 100644 --- a/components/console/index.rst +++ b/components/console/index.rst @@ -7,5 +7,5 @@ Console introduction usage single_command_tool - + events helpers/index diff --git a/components/console/introduction.rst b/components/console/introduction.rst index 65122ca97b4..a17ac971cec 100755 --- a/components/console/introduction.rst +++ b/components/console/introduction.rst @@ -286,6 +286,7 @@ tools capable of helping you with different tasks: * :doc:`/components/console/helpers/dialoghelper`: interactively ask the user for information * :doc:`/components/console/helpers/formatterhelper`: customize the output colorization +* :doc:`/components/console/helpers/progresshelper`: shows a progress bar Testing Commands ---------------- diff --git a/components/dependency_injection/advanced.rst b/components/dependency_injection/advanced.rst index 31920781b0a..677c3541ce4 100644 --- a/components/dependency_injection/advanced.rst +++ b/components/dependency_injection/advanced.rst @@ -20,9 +20,9 @@ argument for another service. .. note:: - If you use a private service as an argument to more than one other service, - this will result in two different instances being used as the instantiation - of the private service is done inline (e.g. ``new PrivateFooBar()``). + If you use a private service as an argument to only one other service, + this will result in an inlined instantiation (e.g. ``new PrivateFooBar()``) + inside this other service, making it publicly unavailable at runtime. Simply said: A service will be private when you do not want to access it directly from your code. diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index 8a97be2b8bc..3dc1559690b 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -274,6 +274,35 @@ but also load a secondary one only if a certain parameter is set:: .. _components-dependency-injection-compiler-passes: +Prepending Configuration passed to the Extension +------------------------------------------------ + +.. versionadded:: 2.2 + The ability to prepend the configuration of a bundle is new in Symfony 2.2. + +An Extension can prepend the configuration of any Bundle before the ``load()`` +method is called by implementing :class:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface`:: + + use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; + // ... + + class AcmeDemoExtension implements ExtensionInterface, PrependExtensionInterface + { + // ... + + public function prepend() + { + // ... + + $container->prependExtensionConfig($name, $config); + + // ... + } + } + +For more details, see :doc:`/cookbook/bundles/prepend_extension`, which is +specific to the Symfony2 Framework, but contains more details about this feature. + Creating a Compiler Pass ------------------------ diff --git a/components/event_dispatcher/container_aware_dispatcher.rst b/components/event_dispatcher/container_aware_dispatcher.rst new file mode 100644 index 00000000000..a1bfe3e2904 --- /dev/null +++ b/components/event_dispatcher/container_aware_dispatcher.rst @@ -0,0 +1,102 @@ +.. index:: + single: Event Dispatcher; Service container aware + +The Container Aware Event Dispatcher +==================================== + +.. versionadded:: 2.1 + This feature was moved into the EventDispatcher component in Symfony 2.1. + +Introduction +------------ + +The :class:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher` is +a special event dispatcher implementation which is coupled to the service container +that is part of :doc:`the Dependency Injection component`. +It allows services to be specified as event listeners making the event dispatcher +extremely powerful. + +Services are lazy loaded meaning the services attached as listeners will only be +created if an event is dispatched that requires those listeners. + +Setup +----- + +Setup is straightforward by injecting a :class:`Symfony\\Component\\DependencyInjection\\ContainerInterface` +into the :class:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher`:: + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; + + $container = new ContainerBuilder(); + $dispatcher = new ContainerAwareEventDispatcher($container); + +Adding Listeners +---------------- + +The *Container Aware Event Dispatcher* can either load specified services +directly, or services that implement :class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`. + +The following examples assume the service container has been loaded with any +services that are mentioned. + +.. note:: + + Services must be marked as public in the container. + +Adding Services +~~~~~~~~~~~~~~~ + +To connect existing service definitions, use the +:method:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher::addListenerService` +method where the ``$callback`` is an array of ``array($serviceId, $methodName)``:: + + $dispatcher->addListenerService($eventName, array('foo', 'logListener')); + +Adding Subscriber Services +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``EventSubscribers`` can be added using the +:method:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher::addSubscriberService` +method where the first argument is the service ID of the subscriber service, +and the second argument is the the service's class name (which must implement +:class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`) as follows:: + + $dispatcher->addSubscriberService( + 'kernel.store_subscriber', + 'StoreSubscriber' + ); + +The ``EventSubscriberInterface`` will be exactly as you would expect:: + + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + // ... + + class StoreSubscriber implements EventSubscriberInterface + { + public static function getSubscribedEvents() + { + return array( + 'kernel.response' => array( + array('onKernelResponsePre', 10), + array('onKernelResponsePost', 0), + ), + 'store.order' => array('onStoreOrder', 0), + ); + } + + public function onKernelResponsePre(FilterResponseEvent $event) + { + // ... + } + + public function onKernelResponsePost(FilterResponseEvent $event) + { + // ... + } + + public function onStoreOrder(FilterOrderEvent $event) + { + // ... + } + } diff --git a/components/event_dispatcher/generic_event.rst b/components/event_dispatcher/generic_event.rst new file mode 100644 index 00000000000..e7a3dd5be36 --- /dev/null +++ b/components/event_dispatcher/generic_event.rst @@ -0,0 +1,110 @@ +.. index:: + single: Event Dispatcher + +The Generic Event Object +======================== + +.. versionadded:: 2.1 + The ``GenericEvent`` event class was added in Symfony 2.1 + +The base :class:`Symfony\\Component\\EventDispatcher\\Event` class provided by the +``Event Dispatcher`` component is deliberately sparse to allow the creation of +API specific event objects by inheritance using OOP. This allow for elegant and +readable code in complex applications. + +The :class:`Symfony\\Component\\EventDispatcher\\GenericEvent` is available +for convenience for those who wish to use just one event object throughout their +application. It is suitable for most purposes straight out of the box, because +it follows the standard observer pattern where the event object +encapsulates an event 'subject', but has the addition of optional extra +arguments. + +:class:`Symfony\\Component\\EventDispatcher\\GenericEvent` has a simple API in +addition to the base class :class:`Symfony\\Component\\EventDispatcher\\Event` + +* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::__construct`: + Constructor takes the event subject and any arguments; + +* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::getSubject`: + Get the subject; + +* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::setArgument`: + Sets an argument by key; + +* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::setArguments`: + Sets arguments array; + +* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::getArgument`: + Gets an argument by key; + +* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::getArguments`: + Getter for all arguments; + +* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::hasArgument`: + Returns true if the argument key exists; + +The ``GenericEvent`` also implements :phpclass:`ArrayAccess` on the event +arguments which makes it very convenient to pass extra arguments regarding the +event subject. + +The following examples show use-cases to give a general idea of the flexibility. +The examples assume event listeners have been added to the dispatcher. + +Simply passing a subject:: + + use Symfony\Component\EventDispatcher\GenericEvent; + + $event = GenericEvent($subject); + $dispatcher->dispatch('foo', $event); + + class FooListener + { + public function handler(GenericEvent $event) + { + if ($event->getSubject() instanceof Foo) { + // ... + } + } + } + +Passing and processing arguments using the :phpclass:`ArrayAccess` API to access +the event arguments:: + + use Symfony\Component\EventDispatcher\GenericEvent; + + $event = new GenericEvent( + $subject, + array('type' => 'foo', 'counter' => 0) + ); + $dispatcher->dispatch('foo', $event); + + echo $event['counter']; + + class FooListener + { + public function handler(GenericEvent $event) + { + if (isset($event['type']) && $event['type'] === 'foo') { + // ... do something + } + + $event['counter']++; + } + } + +Filtering data:: + + use Symfony\Component\EventDispatcher\GenericEvent; + + $event = new GenericEvent($subject, array('data' => 'foo')); + $dispatcher->dispatch('foo', $event); + + echo $event['data']; + + class FooListener + { + public function filter(GenericEvent $event) + { + strtolower($event['data']); + } + } diff --git a/components/event_dispatcher/index.rst b/components/event_dispatcher/index.rst index 25e78b304ae..4800978d501 100644 --- a/components/event_dispatcher/index.rst +++ b/components/event_dispatcher/index.rst @@ -5,3 +5,5 @@ Event Dispatcher :maxdepth: 2 introduction + generic_event + container_aware_dispatcher diff --git a/components/event_dispatcher/introduction.rst b/components/event_dispatcher/introduction.rst index 82fb8625e1d..5cd4fe3af21 100644 --- a/components/event_dispatcher/introduction.rst +++ b/components/event_dispatcher/introduction.rst @@ -116,9 +116,7 @@ The Dispatcher The dispatcher is the central object of the event dispatcher system. In general, a single dispatcher is created, which maintains a registry of listeners. When an event is dispatched via the dispatcher, it notifies all -listeners registered with that event. - -.. code-block:: php +listeners registered with that event:: use Symfony\Component\EventDispatcher\EventDispatcher; @@ -133,9 +131,7 @@ Connecting Listeners To take advantage of an existing event, you need to connect a listener to the dispatcher so that it can be notified when the event is dispatched. A call to the dispatcher ``addListener()`` method associates any valid PHP callable to -an event: - -.. code-block:: php +an event:: $listener = new AcmeListener(); $dispatcher->addListener('foo.action', array($listener, 'onFooAction')); @@ -162,9 +158,7 @@ The ``addListener()`` method takes up to three arguments: method or a class method. So far, you've seen how PHP objects can be registered as listeners. You - can also register PHP `Closures`_ as event listeners: - - .. code-block:: php + can also register PHP `Closures`_ as event listeners:: use Symfony\Component\EventDispatcher\Event; @@ -175,9 +169,7 @@ The ``addListener()`` method takes up to three arguments: Once a listener is registered with the dispatcher, it waits until the event is notified. In the above example, when the ``foo.action`` event is dispatched, the dispatcher calls the ``AcmeListener::onFooAction`` method and passes the -``Event`` object as the single argument: - -.. code-block:: php +``Event`` object as the single argument:: use Symfony\Component\EventDispatcher\Event; @@ -196,9 +188,7 @@ is passed to the listener. This gives the listener access to special information about the event. Check the documentation or implementation of each event to determine the exact ``Symfony\Component\EventDispatcher\Event`` instance that's being passed. For example, the ``kernel.event`` event passes an -instance of ``Symfony\Component\HttpKernel\Event\FilterResponseEvent``: - -.. code-block:: php +instance of ``Symfony\Component\HttpKernel\Event\FilterResponseEvent``:: use Symfony\Component\HttpKernel\Event\FilterResponseEvent; @@ -229,9 +219,7 @@ The Static ``Events`` Class Suppose you want to create a new Event - ``store.order`` - that is dispatched each time an order is created inside your application. To keep things organized, start by creating a ``StoreEvents`` class inside your application -that serves to define and document your event: - -.. code-block:: php +that serves to define and document your event:: namespace Acme\StoreBundle; @@ -267,9 +255,7 @@ accomplish this, you'll create a new class that extends ``Symfony\Component\EventDispatcher\Event``. In this example, each listener will need access to some pretend ``Order`` -object. Create an ``Event`` class that makes this possible: - -.. code-block:: php +object. Create an ``Event`` class that makes this possible:: namespace Acme\StoreBundle\Event; @@ -300,9 +286,7 @@ Dispatch the Event The :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch` method notifies all listeners of the given event. It takes two arguments: the name of the event to dispatch and the ``Event`` instance to pass to each -listener of that event: - -.. code-block:: php +listener of that event:: use Acme\StoreBundle\StoreEvents; use Acme\StoreBundle\Order; @@ -319,9 +303,7 @@ listener of that event: Notice that the special ``FilterOrderEvent`` object is created and passed to the ``dispatch`` method. Now, any listener to the ``store.order`` event will receive the ``FilterOrderEvent`` and have access to the ``Order`` object via -the ``getOrder`` method: - -.. code-block:: php +the ``getOrder`` method:: // some listener class that's been registered for "STORE_ORDER" event use Acme\StoreBundle\Event\FilterOrderEvent; @@ -332,51 +314,6 @@ the ``getOrder`` method: // do something to or with the order } -Passing along the Event Dispatcher Object -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you have a look at the ``EventDispatcher`` class, you will notice that the -class does not act as a Singleton (there is no ``getInstance()`` static method). -That is intentional, as you might want to have several concurrent event -dispatchers in a single PHP request. But it also means that you need a way to -pass the dispatcher to the objects that need to connect or notify events. - -The best practice is to inject the event dispatcher object into your objects, -aka dependency injection. - -You can use constructor injection:: - - use Symfony\Component\EventDispatcher\EventDispatcherInterface; - - class Foo - { - protected $dispatcher = null; - - public function __construct(EventDispatcherInterface $dispatcher) - { - $this->dispatcher = $dispatcher; - } - } - -Or setter injection:: - - use Symfony\Component\EventDispatcher\EventDispatcherInterface; - - class Foo - { - protected $dispatcher = null; - - public function setEventDispatcher(EventDispatcherInterface $dispatcher) - { - $this->dispatcher = $dispatcher; - } - } - -Choosing between the two is really a matter of taste. Many tend to prefer the -constructor injection as the objects are fully initialized at construction -time. But when you have a long list of dependencies, using setter injection -can be the way to go, especially for optional dependencies. - .. index:: single: Event Dispatcher; Event subscribers @@ -395,9 +332,7 @@ events it should subscribe to. It implements the :class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface` interface, which requires a single static method called ``getSubscribedEvents``. Take the following example of a subscriber that -subscribes to the ``kernel.response`` and ``store.order`` events: - -.. code-block:: php +subscribes to the ``kernel.response`` and ``store.order`` events:: namespace Acme\StoreBundle\Event; @@ -443,9 +378,7 @@ This is very similar to a listener class, except that the class itself can tell the dispatcher which events it should listen to. To register a subscriber with the dispatcher, use the :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::addSubscriber` -method: - -.. code-block:: php +method:: use Acme\StoreBundle\Event\StoreSubscriber; @@ -476,9 +409,7 @@ from being called. In other words, the listener needs to be able to tell the dispatcher to stop all propagation of the event to future listeners (i.e. to not notify any more listeners). This can be accomplished from inside a listener via the -:method:`Symfony\\Component\\EventDispatcher\\Event::stopPropagation` method: - -.. code-block:: php +:method:`Symfony\\Component\\EventDispatcher\\Event::stopPropagation` method:: use Acme\StoreBundle\Event\FilterOrderEvent; @@ -492,6 +423,180 @@ listener via the Now, any listeners to ``store.order`` that have not yet been called will *not* be called. +It is possible to detect if an event was stopped by using the +:method:`Symfony\\Component\\EventDispatcher\\Event::isPropagationStopped` method +which returns a boolean value:: + + $dispatcher->dispatch('foo.event', $event); + if ($event->isPropagationStopped()) { + // ... + } + +.. index:: + single: Event Dispatcher; Event Dispatcher aware events and listeners + +.. _event_dispatcher-dispatcher-aware-events: + +EventDispatcher aware Events and Listeners +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ``Event`` object contains a reference to the invoking dispatcher since Symfony 2.1 + +The ``EventDispatcher`` always injects a reference to itself in the passed event +object. This means that all listeners have direct access to the +``EventDispatcher`` object that notified the listener via the passed ``Event`` +object's :method:`Symfony\\Component\\EventDispatcher\\Event::getDispatcher` +method. + +This can lead to some advanced applications of the ``EventDispatcher`` including +letting listeners dispatch other events, event chaining or even lazy loading of +more listeners into the dispatcher object. Examples follow: + +Lazy loading listeners:: + + use Symfony\Component\EventDispatcher\Event; + use Acme\StoreBundle\Event\StoreSubscriber; + + class Foo + { + private $started = false; + + public function myLazyListener(Event $event) + { + if (false === $this->started) { + $subscriber = new StoreSubscriber(); + $event->getDispatcher()->addSubscriber($subscriber); + } + + $this->started = true; + + // ... more code + } + } + +Dispatching another event from within a listener:: + + use Symfony\Component\EventDispatcher\Event; + + class Foo + { + public function myFooListener(Event $event) + { + $event->getDispatcher()->dispatch('log', $event); + + // ... more code + } + } + +While this above is sufficient for most uses, if your application uses multiple +``EventDispatcher`` instances, you might need to specifically inject a known +instance of the ``EventDispatcher`` into your listeners. This could be done +using constructor or setter injection as follows: + +Constructor injection:: + + use Symfony\Component\EventDispatcher\EventDispatcherInterface; + + class Foo + { + protected $dispatcher = null; + + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + } + +Or setter injection:: + + use Symfony\Component\EventDispatcher\EventDispatcherInterface; + + class Foo + { + protected $dispatcher = null; + + public function setEventDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + } + +Choosing between the two is really a matter of taste. Many tend to prefer the +constructor injection as the objects are fully initialized at construction +time. But when you have a long list of dependencies, using setter injection +can be the way to go, especially for optional dependencies. + +.. index:: + single: Event Dispatcher; Dispatcher shortcuts + +.. _event_dispatcher-shortcuts: + +Dispatcher Shortcuts +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + ``EventDispatcher::dispatch()`` method returns the event since Symfony 2.1. + +The :method:`EventDispatcher::dispatch` +method always returns an :class:`Symfony\\Component\\EventDispatcher\\Event` +object. This allows for various shortcuts. For example if one does not need +a custom event object, one can simply rely on a plain +:class:`Symfony\\Component\\EventDispatcher\\Event` object. You do not even need +to pass this to the dispatcher as it will create one by default unless you +specifically pass one:: + + $dispatcher->dispatch('foo.event'); + +Moreover, the EventDispatcher always returns whichever event object that was +dispatched, i.e. either the event that was passed or the event that was +created internally by the dispatcher. This allows for nice shortcuts:: + + if (!$dispatcher->dispatch('foo.event')->isPropagationStopped()) { + // ... + } + +Or:: + + $barEvent = new BarEvent(); + $bar = $dispatcher->dispatch('bar.event', $barEvent)->getBar(); + +Or:: + + $response = $dispatcher->dispatch('bar.event', new BarEvent())->getBar(); + +and so on... + +.. index:: + single: Event Dispatcher; Event name introspection + +.. _event_dispatcher-event-name-introspection: + +Event Name Introspection +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + Added event name to the ``Event`` object since Symfony 2.1 + +Since the ``EventDispatcher`` already knows the name of the event when dispatching +it, the event name is also injected into the +:class:`Symfony\\Component\\EventDispatcher\\Event` objects, making it available +to event listeners via the :method:`Symfony\\Component\\EventDispatcher\\Event::getName` +method. + +The event name, (as with any other data in a custom event object) can be used as +part of the listener's processing logic:: + + use Symfony\Component\EventDispatcher\Event; + + class Foo + { + public function myEventListener(Event $event) + { + echo $event->getName(); + } + } + .. _Observer: http://en.wikipedia.org/wiki/Observer_pattern .. _Closures: http://php.net/manual/en/functions.anonymous.php .. _PHP callable: http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback diff --git a/components/filesystem.rst b/components/filesystem.rst new file mode 100644 index 00000000000..6e5ca5140fa --- /dev/null +++ b/components/filesystem.rst @@ -0,0 +1,244 @@ +.. index:: + single: Filesystem + +The Filesystem Component +======================== + + The Filesystem components provides basic utilities for the filesystem. + +.. versionadded:: 2.1 + The Filesystem Component is new to Symfony 2.1. Previously, the ``Filesystem`` + class was located in the ``HttpKernel`` component. + +Installation +------------ + +You can install the component in many different ways: + +* Use the official Git repository (https://github.com/symfony/Filesystem); +* Install it via Composer (``symfony/filesystem`` on `Packagist`_). + +Usage +----- + +The :class:`Symfony\\Component\\Filesystem\\Filesystem` class is the unique +endpoint for filesystem operations:: + + use Symfony\Component\Filesystem\Filesystem; + use Symfony\Component\Filesystem\Exception\IOException; + + $fs = new Filesystem(); + + try { + $fs->mkdir('/tmp/random/dir/' . mt_rand()); + } catch (IOException $e) { + echo "An error occurred while creating your directory"; + } + +.. note:: + + Methods :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir`, + :method:`Symfony\\Component\\Filesystem\\Filesystem::exists`, + :method:`Symfony\\Component\\Filesystem\\Filesystem::touch`, + :method:`Symfony\\Component\\Filesystem\\Filesystem::remove`, + :method:`Symfony\\Component\\Filesystem\\Filesystem::chmod`, + :method:`Symfony\\Component\\Filesystem\\Filesystem::chown` and + :method:`Symfony\\Component\\Filesystem\\Filesystem::chgrp` can receive a + string, an array or any object implementing :phpclass:`Traversable` as + the target argument. + + +Mkdir +~~~~~ + +Mkdir creates directory. On posix filesystems, directories are created with a +default mode value `0777`. You can use the second argument to set your own mode:: + + $fs->mkdir('/tmp/photos', 0700); + +.. note:: + + You can pass an array or any :phpclass:`Traversable` object as the first + argument. + +Exists +~~~~~~ + +Exists checks for the presence of all files or directories and returns false if a +file is missing:: + + // this directory exists, return true + $fs->exists('/tmp/photos'); + + // rabbit.jpg exists, bottle.png does not exists, return false + $fs->exists(array('rabbit.jpg', 'bottle.png')); + +.. note:: + + You can pass an array or any :phpclass:`Traversable` object as the first + argument. + +Copy +~~~~ + +This method is used to copy files. If the target already exists, the file is +copied only if the source modification date is later than the target. This +behavior can be overridden by the third boolean argument:: + + // works only if image-ICC has been modified after image.jpg + $fs->copy('image-ICC.jpg', 'image.jpg'); + + // image.jpg will be overridden + $fs->copy('image-ICC.jpg', 'image.jpg', true); + +Touch +~~~~~ + +Touch sets access and modification time for a file. The current time is used by +default. You can set your own with the second argument. The third argument is +the access time:: + + // set modification time to the current timestamp + $fs->touch('file.txt'); + // set modification time 10 seconds in the future + $fs->touch('file.txt', time() + 10); + // set access time 10 seconds in the past + $fs->touch('file.txt', time(), time() - 10); + +.. note:: + + You can pass an array or any :phpclass:`Traversable` object as the first + argument. + +Chown +~~~~~ + +Chown is used to change the owner of a file. The third argument is a boolean +recursive option:: + + // set the owner of the lolcat video to www-data + $fs->chown('lolcat.mp4', 'www-data'); + // change the owner of the video directory recursively + $fs->chown('/video', 'www-data', true); + +.. note:: + + You can pass an array or any :phpclass:`Traversable` object as the first + argument. + +Chgrp +~~~~~ + +Chgrp is used to change the group of a file. The third argument is a boolean +recursive option:: + + // set the group of the lolcat video to nginx + $fs->chgrp('lolcat.mp4', 'nginx'); + // change the group of the video directory recursively + $fs->chgrp('/video', 'nginx', true); + + +.. note:: + + You can pass an array or any :phpclass:`Traversable` object as the first + argument. + +Chmod +~~~~~ + +Chmod is used to change the mode of a file. The fourth argument is a boolean +recursive option:: + + // set the mode of the video to 0600 + $fs->chmod('video.ogg', 0600); + // change the mod of the src directory recursively + $fs->chmod('src', 0700, 0000, true); + +.. note:: + + You can pass an array or any :phpclass:`Traversable` object as the first + argument. + +Remove +~~~~~~ + +Remove let's you remove files, symlink, directories easily:: + + $fs->remove(array('symlink', '/path/to/directory', 'activity.log')); + +.. note:: + + You can pass an array or any :phpclass:`Traversable` object as the first + argument. + +Rename +~~~~~~ + +Rename is used to rename files and directories:: + + //rename a file + $fs->rename('/tmp/processed_video.ogg', '/path/to/store/video_647.ogg'); + //rename a directory + $fs->rename('/tmp/files', '/path/to/store/files'); + +symlink +~~~~~~~ + +Creates a symbolic link from the target to the destination. If the filesystem +does not support symbolic links, a third boolean argument is available:: + + // create a symbolic link + $fs->symlink('/path/to/source', '/path/to/destination'); + // duplicate the source directory if the filesystem + // does not support symbolic links + $fs->symlink('/path/to/source', '/path/to/destination', true); + +makePathRelative +~~~~~~~~~~~~~~~~ + +Return the relative path of a directory given another one:: + + // returns '../' + $fs->makePathRelative( + '/var/lib/symfony/src/Symfony/', + '/var/lib/symfony/src/Symfony/Component' + ); + // returns 'videos' + $fs->makePathRelative('/tmp', '/tmp/videos'); + +mirror +~~~~~~ + +Mirrors a directory:: + + $fs->mirror('/path/to/source', '/path/to/target'); + +isAbsolutePath +~~~~~~~~~~~~~~ + +isAbsolutePath returns true if the given path is absolute, false otherwise:: + + // return true + $fs->isAbsolutePath('/tmp'); + // return true + $fs->isAbsolutePath('c:\\Windows'); + // return false + $fs->isAbsolutePath('tmp'); + // return false + $fs->isAbsolutePath('../dir'); + +Error Handling +-------------- + +Whenever something wrong happens, an exception implementing +:class:`Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface` is +thrown. + +.. note:: + + Prior to version 2.1, :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir` + returned a boolean and did not throw exceptions. As of 2.1, a + :class:`Symfony\\Component\\Filesystem\\Exception\\IOException` is + thrown if a directory creation fails. + +.. _`Packagist`: https://packagist.org/packages/symfony/filesystem diff --git a/components/finder.rst b/components/finder.rst index 3a51077dbfa..ab1cda43e82 100644 --- a/components/finder.rst +++ b/components/finder.rst @@ -82,6 +82,15 @@ Search in several locations by chaining calls to $finder->files()->in(__DIR__)->in('/elsewhere'); +.. versionadded:: 2.2 + Wildcard support was added in version 2.2. + +Use wildcard characters to search in the directories matching a pattern:: + + $finder->in('src/Symfony/*/*/Resources'); + +Each pattern has to resolve to at least one directory path. + Exclude directories from matching with the :method:`Symfony\\Component\\Finder\\Finder::exclude` method:: @@ -170,6 +179,55 @@ The ``notName()`` method excludes files matching a pattern:: $finder->files()->notName('*.rb'); +File Contents +~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ``contains()`` and ``notContains()`` methods were added in version 2.1 + +Restrict files by contents with the +:method:`Symfony\\Component\\Finder\\Finder::contains` method:: + + $finder->files()->contains('lorem ipsum'); + +The ``contains()`` method accepts strings or regexes:: + + $finder->files()->contains('/lorem\s+ipsum$/i'); + +The ``notContains()`` method excludes files containing given pattern:: + + $finder->files()->notContains('dolor sit amet'); + +Path +~~~~ + +.. versionadded:: 2.2 + The ``path()`` and ``notPath()`` methods were added in version 2.2. + +Restrict files and directories by path with the +:method:`Symfony\\Component\\Finder\\Finder::path` method:: + + $finder->path('some/special/dir'); + +On all platforms slash (i.e. ``/``) should be used as the directory separator. + +The ``path()`` method accepts a string or a regular expression:: + + $finder->path('foo/bar'); + $finder->path('/^foo\/bar/'); + +Internally, strings are converted into regular expressions by escaping slashes +and adding delimiters: + +.. code-block:: text + + dirname ===> /dirname/ + a/b/c ===> /a\/b\/c/ + +The :method:`Symfony\\Component\\Finder\\Finder::notPath` method excludes files by path:: + + $finder->notPath('other/dir'); + File Size ~~~~~~~~~ @@ -182,8 +240,11 @@ Restrict by a size range by chaining calls:: $finder->files()->size('>= 1K')->size('<= 2K'); -The comparison operator can be any of the following: ``>``, ``>=``, ``<``, '<=', -'=='. +The comparison operator can be any of the following: ``>``, ``>=``, ``<``, ``<=``, +``==``, ``!=``. + +.. versionadded:: 2.1 + The operator ``!=`` was added in version 2.1. The target value may use magnitudes of kilobytes (``k``, ``ki``), megabytes (``m``, ``mi``), or gigabytes (``g``, ``gi``). Those suffixed with an ``i`` use @@ -232,6 +293,25 @@ it is called with the file as a :class:`Symfony\\Component\\Finder\\SplFileInfo` instance. The file is excluded from the result set if the Closure returns ``false``. +Reading contents of returned files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + Method ``getContents()`` have been introduced in version 2.1. + +The contents of returned files can be read with +:method:`Symfony\\Component\\Finder\\SplFileInfo::getContents`:: + + use Symfony\Component\Finder\Finder; + + $finder = new Finder(); + $finder->files()->in(__DIR__); + + foreach ($finder as $file) { + $contents = $file->getContents(); + ... + } + .. _strtotime: http://www.php.net/manual/en/datetime.formats.php .. _protocol: http://www.php.net/manual/en/wrappers.php .. _Streams: http://www.php.net/streams diff --git a/components/http_foundation/index.rst b/components/http_foundation/index.rst index 8086085daaa..9937c960776 100644 --- a/components/http_foundation/index.rst +++ b/components/http_foundation/index.rst @@ -5,4 +5,7 @@ HTTP Foundation :maxdepth: 2 introduction + sessions + session_configuration + session_testing trusting_proxies diff --git a/components/http_foundation/introduction.rst b/components/http_foundation/introduction.rst index 33aabafcffa..08ee28d970e 100644 --- a/components/http_foundation/introduction.rst +++ b/components/http_foundation/introduction.rst @@ -240,6 +240,25 @@ by using the following methods: * :method:`Symfony\\Component\\HttpFoundation\\Request::getCharsets`: returns the list of accepted charsets ordered by descending quality; +.. versionadded:: 2.2 + The :class:`Symfony\\Component\\HttpFoundation\\AcceptHeader` class is new in Symfony 2.2. + +If you need to get full access to parsed data from ``Accept``, ``Accept-Language``, +``Accept-Charset`` or ``Accept-Encoding``, you can use +:class:`Symfony\\Component\\HttpFoundation\\AcceptHeader` utility class:: + + use Symfony\Component\HttpFoundation\AcceptHeader; + + $accept = AcceptHeader::fromString($request->headers->get('Accept')); + if ($accept->has('text/html')) { + $item = $accept->get('html'); + $charset = $item->getAttribute('charset', 'utf-8'); + $quality = $item->getQuality(); + } + + // accepts items are sorted by descending quality + $accepts = AcceptHeader::fromString($request->headers->get('Accept'))->all(); + Accessing other Data ~~~~~~~~~~~~~~~~~~~~ @@ -365,6 +384,48 @@ To redirect the client to another URL, you can use the $response = new RedirectResponse('http://example.com/'); +Streaming a Response +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + Support for streamed responses was added in Symfony 2.1. + +The :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` class allows +you to stream the Response back to the client. The response content is +represented by a PHP callable instead of a string:: + + use Symfony\Component\HttpFoundation\StreamedResponse; + + $response = new StreamedResponse(); + $response->setCallback(function () { + echo 'Hello World'; + flush(); + sleep(2); + echo 'Hello World'; + flush(); + }); + $response->send(); + +Downloading Files +~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ``makeDisposition`` method was added in Symfony 2.1. + +When uploading a file, you must add a ``Content-Disposition`` header to your +response. While creating this header for basic file downloads is easy, using +non-ASCII filenames is more involving. The +:method:`Symfony\\Component\\HttpFoundation\\Response::makeDisposition` +abstracts the hard work behind a simple API:: + + use Symfony\Component\HttpFoundation\ResponseHeaderBag; + + $d = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'foo.pdf'); + + $response->headers->set('Content-Disposition', $d); + +.. _component-http-foundation-json-response: + Creating a JSON Response ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -380,10 +441,35 @@ right content and headers. A JSON response might look like this:: ))); $response->headers->set('Content-Type', 'application/json'); +.. versionadded:: 2.1 + The :class:`Symfony\\Component\\HttpFoundation\\JsonResponse` class was added in Symfony 2.1. + +There is also a helpful :class:`Symfony\\Component\\HttpFoundation\\JsonResponse` +class, which can make this even easier:: + + use Symfony\Component\HttpFoundation\JsonResponse; + + $response = new JsonResponse(); + $response->setData(array( + 'data' => 123 + )); + +This encodes your array of data to JSON and sets the ``Content-Type`` header +to ``application/json``. If you're using JSONP, you can set the callback +function that the data should be passed to:: + + $response->setCallback('handleResponse'); + +In this case, the ``Content-Type`` header will be ``text/javascript`` and +the response content will look like this: + +.. code-block:: javascript + + handleResponse({'data': 123}); + Session ------- -TBD -- This part has not been written yet as it will probably be refactored -soon in Symfony 2.1. +The session information is in its own document: :doc:`/components/http_foundation/sessions`. .. _Packagist: https://packagist.org/packages/symfony/http-foundation diff --git a/components/http_foundation/session_configuration.rst b/components/http_foundation/session_configuration.rst new file mode 100644 index 00000000000..f445f740b76 --- /dev/null +++ b/components/http_foundation/session_configuration.rst @@ -0,0 +1,263 @@ +.. index:: + single: HTTP + single: HttpFoundation, Sessions + +Configuring Sessions and Save Handlers +====================================== + +This section deals with how to configure session management and fine tune it +to your specific needs. This documentation covers save handlers, which +store and retrieve session data, and configuring session behaviour. + +Save Handlers +~~~~~~~~~~~~~ + +The PHP session workflow has 6 possible operations that may occur. The normal +session follows `open`, `read`, `write` and `close`, with the possibility of +`destroy` and `gc` (garbage collection which will expire any old sessions: `gc` +is called randomly according to PHP's configuration and if called, it is invoked +after the `open` operation). You can read more about this at +`php.net/session.customhandler`_ + + +Native PHP Save Handlers +------------------------ + +So-called 'native' handlers, are save handlers which are either compiled into +PHP or provided by PHP extensions, such as PHP-Sqlite, PHP-Memcached and so on. + +All native save handlers are internal to PHP and as such, have no public facing API. +They must be configured by PHP ini directives, usually ``session.save_path`` and +potentially other driver specific directives. Specific details can be found in +docblock of the ``setOptions()`` method of each class. + +While native save handlers can be activated by directly using +``ini_set('session.save_handler', $name);``, Symfony2 provides a convenient way to +activate these in the same way as custom handlers. + +Symfony2 provides drivers for the following native save handler as an example: + + * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler` + +Example usage:: + + use Symfony\Component\HttpFoundation\Session\Session; + use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; + use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; + + $storage = new NativeSessionStorage(array(), new NativeFileSessionHandler()); + $session = new Session($storage); + +.. note:: + + With the exception of the ``files`` handler which is built into PHP and always available, + the availability of the other handlers depends on those PHP extensions being active at runtime. + +.. note:: + + Native save handlers provide a quick solution to session storage, however, in complex systems + where you need more control, custom save handlers may provide more freedom and flexibility. + Symfony2 provides several implementations which you may further customise as required. + + +Custom Save Handlers +-------------------- + +Custom handlers are those which completely replace PHP's built in session save +handlers by providing six callback functions which PHP calls internally at +various points in the session workflow. + +Symfony2 HttpFoundation provides some by default and these can easily serve as +examples if you wish to write your own. + + * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler` + * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler` + * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler` + * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler` + * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler` + +Example usage:: + + use Symfony\Component\HttpFoundation\Session\Session; + use Symfony\Component\HttpFoundation\Session\Storage\SessionStorage; + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + $storage = new NativeSessionStorage(array(), new PdoSessionHandler()); + $session = new Session($storage); + + +Configuring PHP Sessions +~~~~~~~~~~~~~~~~~~~~~~~~ + +The :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage` +can configure most of the PHP ini configuration directives which are documented +at `php.net/session.configuration`_. + +To configure these settings, pass the keys (omitting the initial ``session.`` part +of the key) as a key-value array to the ``$options`` constructor argument. +Or set them via the +:method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage::setOptions` +method. + +For the sake of clarity, some key options are explained in this documentation. + +Session Cookie Lifetime +~~~~~~~~~~~~~~~~~~~~~~~ + +For security, session tokens are generally recommended to be sent as session cookies. +You can configure the lifetime of session cookies by specifying the lifetime +(in seconds) using the ``cookie_lifetime`` key in the constructor's ``$options`` +argument in :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`. + +Setting a ``cookie_lifetime`` to ``0`` will cause the cookie to live only as +long as the browser remains open. Generally, ``cookie_lifetime`` would be set to +a relatively large number of days, weeks or months. It is not uncommon to set +cookies for a year or more depending on the application. + +Since session cookies are just a client-side token, they are less important in +controlling the fine details of your security settings which ultimately can only +be securely controlled from the server side. + +.. note:: + + The ``cookie_lifetime`` setting is the number of seconds the cookie should live + for, it is not a Unix timestamp. The resulting session cookie will be stamped + with an expiry time of ``time()``+``cookie_lifetime`` where the time is taken + from the server. + +Configuring Garbage Collection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a session opens, PHP will call the ``gc`` handler randomly according to the +probability set by ``session.gc_probability`` / ``session.gc_divisor``. For +example if these were set to ``5/100`` respectively, it would mean a probability +of 5%. Similarly, ``3/4`` would mean a 3 in 4 chance of being called, i.e. 75%. + +If the garbage collection handler is invoked, PHP will pass the value stored in +the PHP ini directive ``session.gc_maxlifetime``. The meaning in this context is +that any stored session that was saved more than ``maxlifetime`` ago should be +deleted. This allows one to expire records based on idle time. + +You can configure these settings by passing ``gc_probability``, ``gc_divisor`` +and ``gc_maxlifetime`` in an array to the constructor of +:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage` +or to the :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage::setOptions` +method. + +Session Lifetime +~~~~~~~~~~~~~~~~ + +When a new session is created, meaning Symfony2 issues a new session cookie +to the client, the cookie will be stamped with an expiry time. This is +calculated by adding the PHP runtime configuration value in +``session.cookie_lifetime`` with the current server time. + +.. note:: + + PHP will only issue a cookie once. The client is expected to store that cookie + for the entire lifetime. A new cookie will only be issued when the session is + destroyed, the browser cookie is deleted, or the session ID is regenerated + using the ``migrate()`` or ``invalidate()`` methods of the ``Session`` class. + + The initial cookie lifetime can be set by configuring ``NativeSessionStorage`` + using the ``setOptions(array('cookie_lifetime' => 1234))`` method. + +.. note:: + + A cookie lifetime of ``0`` means the cookie expires when the browser is closed. + +Session Idle Time/Keep Alive +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are often circumstances where you may want to protect, or minimize +unauthorized use of a session when a user steps away from their terminal while +logged in by destroying the session after a certain period of idle time. For +example, it is common for banking applications to log the user out after just +5 to 10 minutes of inactivity. Setting the cookie lifetime here is not +appropriate because that can be manipulated by the client, so we must do the expiry +on the server side. The easiest way is to implement this via garbage collection +which runs reasonably frequently. The cookie ``lifetime`` would be set to a +relatively high value, and the garbage collection ``maxlifetime`` would be set +to destroy sessions at whatever the desired idle period is. + +The other option is to specifically checking if a session has expired after the +session is started. The session can be destroyed as required. This method of +processing can allow the expiry of sessions to be integrated into the user +experience, for example, by displaying a message. + +Symfony2 records some basic meta-data about each session to give you complete +freedom in this area. + +Session meta-data +~~~~~~~~~~~~~~~~~ + +Sessions are decorated with some basic meta-data to enable fine control over the +security settings. The session object has a getter for the meta-data, +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getMetadataBag` which +exposes an instance of :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag`:: + + $session->getMetadataBag()->getCreated(); + $session->getMetadataBag()->getLastUsed(); + +Both methods return a Unix timestamp (relative to the server). + +This meta-data can be used to explicitly expire a session on access, e.g.:: + + $session->start(); + if (time() - $session->getMetadataBag()->getLastUsed() > $maxIdleTime) { + $session->invalidate(); + throw new SessionExpired(); // redirect to expired session page + } + +It is also possible to tell what the ``cookie_lifetime`` was set to for a +particular cookie by reading the ``getLifetime()`` method:: + + $session->getMetadataBag()->getLifetime(); + +The expiry time of the cookie can be determined by adding the created +timestamp and the lifetime. + +PHP 5.4 compatibility +~~~~~~~~~~~~~~~~~~~~~ + +Since PHP 5.4.0, :phpclass:`SessionHandler` and :phpclass:`SessionHandlerInterface` +are available. Symfony 2.1 provides forward compatibility for the :phpclass:`SessionHandlerInterface` +so it can be used under PHP 5.3. This greatly improves inter-operability with other +libraries. + +:phpclass:`SessionHandler` is a special PHP internal class which exposes native save +handlers to PHP user-space. + +In order to provide a solution for those using PHP 5.4, Symfony2 has a special +class called :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler` +which under PHP 5.4, extends from `\SessionHandler` and under PHP 5.3 is just a +empty base class. This provides some interesting opportunities to leverage +PHP 5.4 functionality if it is available. + +Save Handler Proxy +~~~~~~~~~~~~~~~~~~ + +There are two kinds of save handler class proxies which inherit from +:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\AbstractProxy`: +they are :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeProxy` +and :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerProxy`. + +:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage` +automatically injects storage handlers into a save handler proxy unless already +wrapped by one. + +:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeProxy` +is used automatically under PHP 5.3 when internal PHP save handlers are specified +using the `Native*SessionHandler` classes, while +:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerProxy` +will be used to wrap any custom save handlers, that implement :phpclass:`SessionHandlerInterface`. + +Under PHP 5.4 and above, all session handlers implement :phpclass:`SessionHandlerInterface` +including `Native*SessionHandler` classes which inherit from :phpclass:`SessionHandler`. + +The proxy mechanism allows you to get more deeply involved in session save handler +classes. A proxy for example could be used to encrypt any session transaction +without knowledge of the specific save handler. + +.. _`php.net/session.customhandler`: http://php.net/session.customhandler +.. _`php.net/session.configuration`: http://php.net/session.configuration diff --git a/components/http_foundation/session_testing.rst b/components/http_foundation/session_testing.rst new file mode 100644 index 00000000000..7a938b924b9 --- /dev/null +++ b/components/http_foundation/session_testing.rst @@ -0,0 +1,59 @@ +.. index:: + single: HTTP + single: HttpFoundation, Sessions + +Testing with Sessions +===================== + +Symfony2 is designed from the ground up with code-testability in mind. In order +to make your code which utilizes session easily testable we provide two separate +mock storage mechanisms for both unit testing and functional testing. + +Testing code using real sessions is tricky because PHP's workflow state is global +and it is not possible to have multiple concurrent sessions in the same PHP +process. + +The mock storage engines simulate the PHP session workflow without actually +starting one allowing you to test your code without complications. You may also +run multiple instances in the same PHP process. + +The mock storage drivers do not read or write the system globals +`session_id()` or `session_name()`. Methods are provided to simulate this if +required: + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionStorageInterface::getId`: Gets the + session ID. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionStorageInterface::setId`: Sets the + session ID. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionStorageInterface::getName`: Gets the + session name. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionStorageInterface::setName`: Sets the + session name. + +Unit Testing +------------ + +For unit testing where it is not necessary to persist the session, you should +simply swap out the default storage engine with +:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage`:: + + use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; + use Symfony\Component\HttpFoundation\Session\Session; + + $session = new Session(new MockArraySessionStorage()); + +Functional Testing +------------------ + +For functional testing where you may need to persist session data across +separate PHP processes, simply change the storage engine to +:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage`:: + + use Symfony\Component\HttpFoundation\Session\Session; + use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; + + $session = new Session(new MockFileSessionStorage()); + diff --git a/components/http_foundation/sessions.rst b/components/http_foundation/sessions.rst new file mode 100644 index 00000000000..dbd855f4bf0 --- /dev/null +++ b/components/http_foundation/sessions.rst @@ -0,0 +1,335 @@ +.. index:: + single: HTTP + single: HttpFoundation, Sessions + +Session Management +================== + +The Symfony2 HttpFoundation Component has a very powerful and flexible session +subsystem which is designed to provide session management through a simple +object-oriented interface using a variety of session storage drivers. + +.. versionadded:: 2.1 + The :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface` interface, + as well as a number of other changes, are new as of Symfony 2.1. + +Sessions are used via the simple :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` +implementation of :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface` interface. + +Quick example:: + + use Symfony\Component\HttpFoundation\Session\Session; + + $session = new Session(); + $session->start(); + + // set and get session attributes + $session->set('name', 'Drak'); + $session->get('name'); + + // set flash messages + $session->getFlashBag()->add('notice', 'Profile updated'); + + // retrieve messages + foreach ($session->getFlashBag()->get('notice', array()) as $message) { + echo "
$message
"; + } + +.. note:: + + Symfony sessions are designed to replace several native PHP functions. + Applications should avoid using ``session_start()``, ``session_regenerate_id()``, + ``session_id()``, ``session_name()``, and ``session_destroy()`` and instead + use the APIs in the following section. + +.. note:: + + While it is recommended to explicitly start a session, a sessions will actually + start on demand, that is, if any session request is made to read/write session + data. + +.. caution:: + + Symfony sessions are incompatible with PHP ini directive ``session.auto_start = 1`` + This directive should be turned off in ``php.ini``, in the webserver directives or + in ``.htaccess``. + +Session API +~~~~~~~~~~~ + +The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` class implements +:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`. + +The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` has a simple API +as follows divided into a couple of groups. + +Session workflow + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::start`: + Starts the session - do not use ``session_start()``. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::migrate`: + Regenerates the session ID - do not use ``session_regenerate_id()``. + This method can optionally change the lifetime of the new cookie that will + be emitted by calling this method. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::invalidate`: + Clears all session data and regenerates session ID. Do not use ``session_destroy()``. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getId`: Gets the + session ID. Do not use ``session_id()``. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setId`: Sets the + session ID. Do not use ``session_id()``. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getName`: Gets the + session name. Do not use ``session_name()``. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setName`: Sets the + session name. Do not use ``session_name()``. + +Session attributes + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::set`: + Sets an attribute by key; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::get`: + Gets an attribute by key; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::all`: + Gets all attributes as an array of key => value; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::has`: + Returns true if the attribute exists; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::keys`: + Returns an array of stored attribute keys; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::replace`: + Sets multiple attributes at once: takes a keyed array and sets each key => value pair. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::remove`: + Deletes an attribute by key; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::clear`: + Clear all attributes; + +The attributes are stored internally in an "Bag", a PHP object that acts like +an array. A few methods exist for "Bag" management: + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::registerBag`: + Registers a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getBag`: + Gets a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` by + bag name. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getFlashBag`: + Gets the :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface`. + This is just a shortcut for convenience. + +Session meta-data + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getMetadataBag`: + Gets the :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag` + which contains information about the session. + + +Session Data Management +~~~~~~~~~~~~~~~~~~~~~~~ + +PHP's session management requires the use of the ``$_SESSION`` super-global, +however, this interferes somewhat with code testability and encapsulation in a +OOP paradigm. To help overcome this, Symfony2 uses 'session bags' linked to the +session to encapsulate a specific dataset of 'attributes' or 'flash messages'. + +This approach also mitigates namespace pollution within the ``$_SESSION`` +super-global because each bag stores all its data under a unique namespace. +This allows Symfony2 to peacefully co-exist with other applications or libraries +that might use the ``$_SESSION`` super-global and all data remains completely +compatible with Symfony2's session management. + +Symfony2 provides 2 kinds of storage bags, with two separate implementations. +Everything is written against interfaces so you may extend or create your own +bag types if necessary. + +:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` has +the following API which is intended mainly for internal purposes: + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getStorageKey`: + Returns the key which the bag will ultimately store its array under in ``$_SESSION``. + Generally this value can be left at its default and is for internal use. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::initialize`: + This is called internally by Symfony2 session storage classes to link bag data + to the session. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getName`: + Returns the name of the session bag. + + +Attributes +~~~~~~~~~~ + +The purpose of the bags implementing the :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface` +is to handle session attribute storage. This might include things like user ID, +and remember me login settings or other user based state information. + +* :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag` + This is the standard default implementation. + +* :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag` + This implementation allows for attributes to be stored in a structured namespace. + +Any plain `key => value` storage system is limited in the extent to which +complex data can be stored since each key must be unique. You can achieve +namespacing by introducing a naming convention to the keys so different parts of +your application could operate without clashing. For example, `module1.foo` and +`module2.foo`. However, sometimes this is not very practical when the attributes +data is an array, for example a set of tokens. In this case, managing the array +becomes a burden because you have to retrieve the array then process it and +store it again:: + + $tokens = array('tokens' => array('a' => 'a6c1e0b6', + 'b' => 'f4a7b1f3')); + +So any processing of this might quickly get ugly, even simply adding a token to +the array:: + + $tokens = $session->get('tokens'); + $tokens['c'] = $value; + $session->set('tokens', $tokens); + +With structured namespacing, the key can be translated to the array +structure like this using a namespace character (defaults to `/`):: + + $session->set('tokens/c', $value); + +This way you can easily access a key within the stored array directly and easily. + +:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface` +has a simple API + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set`: + Sets an attribute by key; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::get`: + Gets an attribute by key; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::all`: + Gets all attributes as an array of key => value; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::has`: + Returns true if the attribute exists; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::keys`: + Returns an array of stored attribute keys; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::replace`: + Sets multiple attributes at once: takes a keyed array and sets each key => value pair. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove`: + Deletes an attribute by key; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::clear`: + Clear the bag; + + +Flash messages +~~~~~~~~~~~~~~ + +The purpose of the :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface` +is to provide a way of setting and retrieving messages on a per session basis. +The usual workflow for flash messages would be set in an request, and displayed +after a page redirect. For example, a user submits a form which hits an update +controller, and after processing the controller redirects the page to either the +updated page or an error page. Flash messages set in the previous page request +would be displayed immediately on the subsequent page load for that session. +This is however just one application for flash messages. + +* :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag` + This implementation messages set in one page-load will + be available for display only on the next page load. These messages will auto + expire regardless of if they are retrieved or not. + +* :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag` + In this implementation, messages will remain in the session until + they are explicitly retrieved or cleared. This makes it possible to use ESI + caching. + +:class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface` +has a simple API + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::add`: + Adds a flash message to the stack of specified type; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::set`: + Sets flashes by type; This method conveniently takes both singles messages as + a ``string`` or multiple messages in an ``array``. + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::get`: + Gets flashes by type and clears those flashes from the bag; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::setAll`: + Sets all flashes, accepts a keyed array of arrays ``type => array(messages)``; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::all`: + Gets all flashes (as a keyed array of arrays) and clears the flashes from the bag; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peek`: + Gets flashes by type (read only); + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peekAll`: + Gets all flashes (read only) as keyed array of arrays; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::has`: + Returns true if the type exists, false if not; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::keys`: + Returns an array of the stored flash types; + +* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::clear`: + Clears the bag; + +For simple applications it is usually sufficient to have one flash message per +type, for example a confirmation notice after a form is submitted. However, +flash messages are stored in a keyed array by flash ``$type`` which means your +application can issue multiple messages for a given type. This allows the API +to be used for more complex messaging in your application. + +Examples of setting multiple flashes:: + + use Symfony\Component\HttpFoundation\Session\Session; + + $session = new Session(); + $session->start(); + + // add flash messages + $session->getFlashBag()->add( + 'warning', + 'Your config file is writable, it should be set read-only' + ); + $session->getFlashBag()->add('error', 'Failed to update name'); + $session->getFlashBag()->add('error', 'Another error'); + +Displaying the flash messages might look like this: + +Simple, display one type of message:: + + // display warnings + foreach ($session->getFlashBag()->get('warning', array()) as $message) { + echo "
$message
"; + } + + // display errors + foreach ($session->getFlashBag()->get('error', array()) as $message) { + echo "
$message
"; + } + +Compact method to process display all flashes at once:: + + foreach ($session->getFlashBag()->all() as $type => $messages) { + foreach ($messages as $message) { + echo "
$message
\n"; + } + } diff --git a/components/http_kernel/introduction.rst b/components/http_kernel/introduction.rst index d3100265fbd..5822096e075 100644 --- a/components/http_kernel/introduction.rst +++ b/components/http_kernel/introduction.rst @@ -107,6 +107,9 @@ listeners to the events discussed below:: // echo the content and send the headers $response->send(); + // triggers the kernel.terminate event + $kernel->terminate($request, $response); + See ":ref:`http-kernel-working-example`" for a more concrete implementation. For general information on adding listeners to the events below, see @@ -148,7 +151,7 @@ the :ref:`kernel.response` event. :align: center Other listeners simply initialize things or add more information to the request. -For example, a listener might determine and set the locale on the Session +For example, a listener might determine and set the locale on the ``Request`` object. Another common listener is routing. A router listener may process the ``Request`` @@ -441,6 +444,45 @@ method, which sends the headers and prints the ``Response`` content. serializes the current user's information into the session so that it can be reloaded on the next request. +8) The ``kernel.terminate`` event +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ``kernel.terminate`` event is new to Symfony 2.1. + +**Typical Purposes**: To perform some "heavy" action after the response has +been streamed to the user + +:ref:`Kernel Events Information Table` + +The final event of the HttpKernel process is ``kernel.terminate`` and is unique +because it occurs *after* the ``HttpKernel::handle`` method, and after the +response is sent to the user. Recall from above, then the code that uses +the kernel, ends like this:: + + // echo the content and send the headers + $response->send(); + + // triggers the kernel.terminate event + $kernel->terminate($request, $response); + +As you can see, by calling ``$kernel->terminate`` after sending the response, +you will trigger the ``kernel.terminate`` event where you can perform certain +actions that you may have delayed in order to return the response as quickly +as possible to the client (e.g. sending emails). + +.. note:: + + Using the ``kernel.terminate`` event is optional, and should only be + called if your kernel implements :class:`Symfony\\Component\\HttpKernel\\TerminableInterface`. + +.. sidebar:: ``kernel.terminate`` in the Symfony Framework + + If you use the ``SwiftmailerBundle`` with Symfony2 and use ``memory`` + spooling, then the :class:`Symfony\\Bundle\\SwiftmailerBundle\\EventListener\\EmailSenderListener` + is activated, which actually delivers any emails that you scheduled to + send during the request. + .. _component-http-kernel-kernel-exception: Handling Exceptions:: the ``kernel.exception`` event @@ -537,6 +579,8 @@ each event has their own event object: +-------------------+-------------------------------+-------------------------------------------------------------------------------------+ | kernel.response | ``KernelEvents::RESPONSE`` | :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent` | +-------------------+-------------------------------+-------------------------------------------------------------------------------------+ +| kernel.terminate | ``KernelEvents::TERMINATE`` | :class:`Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent` | ++-------------------+-------------------------------+-------------------------------------------------------------------------------------+ | kernel.exception | ``KernelEvents::EXCEPTION`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent` | +-------------------+-------------------------------+-------------------------------------------------------------------------------------+ @@ -582,6 +626,8 @@ a built-in ControllerResolver that can be used to create a working example:: $response = $kernel->handle($request); $response->send(); + $kernel->terminate($request, $response); + Sub Requests ------------ diff --git a/components/index.rst b/components/index.rst index 14e73dddf1a..8b4abe24f8d 100644 --- a/components/index.rst +++ b/components/index.rst @@ -12,14 +12,17 @@ The Components dom_crawler dependency_injection/index event_dispatcher/index + filesystem finder http_foundation/index http_kernel/index locale process + property_access/index routing/index security/index serializer + stopwatch templating yaml/index diff --git a/components/locale.rst b/components/locale.rst index 965a4fa2b61..526a97221c8 100644 --- a/components/locale.rst +++ b/components/locale.rst @@ -65,4 +65,8 @@ When using the ClassLoader component following code is sufficient to supplement $locales = Locale::getDisplayLocales('en'); $localeCodes = Locale::getLocales(); + // Get ICU versions + $icuVersion = Locale::getIntlIcuVersion(); + $icuDataVersion = Locale::getIcuDataVersion(); + .. _Packagist: https://packagist.org/packages/symfony/locale diff --git a/components/map.rst.inc b/components/map.rst.inc index fefb0427902..c9084b6f897 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -16,6 +16,7 @@ * :doc:`/components/console/introduction` * :doc:`/components/console/usage` * :doc:`/components/console/single_command_tool` + * :doc:`/components/console/events` * :doc:`/components/console/helpers/index` * **CSS Selector** @@ -43,7 +44,13 @@ * :doc:`/components/event_dispatcher/index` * :doc:`/components/event_dispatcher/introduction` + * :doc:`/components/event_dispatcher/container_aware_dispatcher` + * :doc:`/components/event_dispatcher/generic_event` +* **Filesystem** + + * :doc:`/components/filesystem` + * **Finder** * :doc:`/components/finder` @@ -51,6 +58,9 @@ * :doc:`/components/http_foundation/index` * :doc:`/components/http_foundation/introduction` + * :doc:`/components/http_foundation/sessions` + * :doc:`/components/http_foundation/session_configuration` + * :doc:`/components/http_foundation/session_testing` * :doc:`/components/http_foundation/trusting_proxies` * :doc:`/components/http_kernel/index` @@ -65,14 +75,23 @@ * :doc:`/components/process` +* :doc:`/components/property_access/index` + + * :doc:`/components/property_access/introduction` + * :doc:`/components/routing/index` * :doc:`/components/routing/introduction` + * :doc:`/components/routing/hostname_pattern` * **Serializer** * :doc:`/components/serializer` +* **Stopwatch** + + * :doc:`/components/stopwatch` + * :doc:`/components/security/index` * :doc:`/components/security/introduction` diff --git a/components/process.rst b/components/process.rst index 99aa8732161..057a266a531 100644 --- a/components/process.rst +++ b/components/process.rst @@ -36,6 +36,15 @@ The :method:`Symfony\\Component\\Process\\Process::run` method takes care of the subtle differences between the different platforms when executing the command. +.. versionadded:: 2.2 + The ``getIncrementalOutput()`` and ``getIncrementalErrorOutput()`` methods were added in Symfony 2.2. + +The ``getOutput()`` method always return the whole content of the standard +output of the command and ``getErrorOutput()`` the content of the error +output. Alternatively, the :method:`Symfony\\Component\\Process\\Process::getIncrementalOutput` +and :method:`Symfony\\Component\\Process\\Process::getIncrementalErrorOutput` +methods returns the new outputs since the last call. + When executing a long running command (like rsync-ing files to a remote server), you can give feedback to the end user in real-time by passing an anonymous function to the @@ -63,4 +72,15 @@ instead:: ); $process->run(); -.. _Packagist: https://packagist.org/packages/symfony/process \ No newline at end of file +.. versionadded:: 2.1 + The ``ProcessBuilder`` class has been as of 2.1. + +To make your code work better on all platforms, you might want to use the +:class:`Symfony\\Component\\Process\\ProcessBuilder` class instead:: + + use Symfony\Component\Process\ProcessBuilder; + + $builder = new ProcessBuilder(array('ls', '-lsa')); + $builder->getProcess()->run(); + +.. _Packagist: https://packagist.org/packages/symfony/process diff --git a/components/property_access/index.rst b/components/property_access/index.rst new file mode 100644 index 00000000000..c40373aaac1 --- /dev/null +++ b/components/property_access/index.rst @@ -0,0 +1,7 @@ +Property Access +=============== + +.. toctree:: + :maxdepth: 2 + + introduction diff --git a/components/property_access/introduction.rst b/components/property_access/introduction.rst new file mode 100644 index 00000000000..f4d4de5cb23 --- /dev/null +++ b/components/property_access/introduction.rst @@ -0,0 +1,264 @@ +.. index:: + single: PropertyAccess + single: Components; PropertyAccess + +The PropertyAccess Component +============================ + + The PropertyAccess component provides function to read and write from/to an + object or array using a simple string notation. + +.. versionadded:: 2.2 + The PropertyAccess Component is new to Symfony 2.2. Previously, the + ``PropertyPath`` class was located in the ``Form`` component. + +Installation +------------ + +You can install the component in two different ways: + +* Use the official Git repository (https://github.com/symfony/PropertyAccess); +* :doc:`Install it via Composer` (``symfony/property-access`` on `Packagist`_). + +Usage +----- + +The entry point of this component is the +:method:`PropertyAccess::getPropertyAccessor` +factory. This factory will create a new instance of the +:class:`Symfony\\Component\\PropertyAccess\\PropertyAccessor` class with the +default configuration:: + + use Symfony\Component\PropertyAccess\PropertyAccess; + + $accessor = PropertyAccess::getPropertyAccessor(); + +Reading from Arrays +------------------- + +You can read an array with the +:method:`PropertyAccessor::getValue` +method. This is done using the index notation that is used in PHP:: + + // ... + $person = array( + 'first_name' => 'Wouter', + ); + + echo $accessor->getValue($person, '[first_name]'); // 'Wouter' + echo $accessor->getValue($person, '[age]'); // null + +As you can see, the method will return ``null`` if the index does not exists. + +You can also use multi dimensional arrays:: + + // ... + $persons = array( + array( + 'first_name' => 'Wouter', + ), + array( + 'first_name' => 'Ryan', + ) + ); + + echo $accessor->getValue($persons, '[0][first_name]'); // 'Wouter' + echo $accessor->getValue($persons, '[1][first_name]'); // 'Ryan' + +Reading from Objects +-------------------- + +The ``getValue`` method is a very robust method, and you can see all of its +features when working with objects. + +Accessing public Properties +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To read from properties, use the "dot" notation:: + + // ... + $person = new Person(); + $person->firstName = 'Wouter'; + + echo $accessor->getValue($person, 'firstName'); // 'Wouter' + + $child = new Person(); + $child->firstName = 'Bar'; + $person->children = array($child); + + echo $accessor->getValue($person, 'children[0].firstName'); // 'Bar' + +.. caution:: + + Accessing public properties is the last option used by ``PropertyAccessor``. + It tries to access the value using the below methods first before using + the property directly. For example, if you have a public property that + has a getter method, it will use the getter. + +Using Getters +~~~~~~~~~~~~~ + +The ``getValue`` method also supports reading using getters. The method will +be created using common naming conventions for getters. It camelizes the +property name (``first_name`` becomes ``FirstName``) and prefixes it with +``get``. So the actual method becomes ``getFirstName``:: + + // ... + class Person + { + private $firstName = 'Wouter'; + + public function getFirstName() + { + return $this->firstName; + } + } + + $person = new Person(); + + echo $accessor->getValue($person, 'first_name'); // 'Wouter' + +Using Hassers/Issers +~~~~~~~~~~~~~~~~~~~~ + +And it doesn't even stop there. If there is no getter found, the accessor will +look for an isser or hasser. This method is created using the same way as +getters, this means that you can do something like this:: + + // ... + class Person + { + private $author = true; + private $children = array(); + + public function isAuthor() + { + return $this->author; + } + + public function hasChildren() + { + return 0 !== count($this->children); + } + } + + $person = new Person(); + + if ($accessor->getValue($person, 'author')) { + echo 'He is an author'; + } + if ($accessor->getValue($person, 'children')) { + echo 'He has children'; + } + +This will produce: ``He is an author`` + +Magic Methods +~~~~~~~~~~~~~ + +At last, ``getValue`` can use the magic ``__get`` method too:: + + // ... + class Person + { + private $children = array( + 'wouter' => array(...), + ); + + public function __get($id) + { + return $this->children[$id]; + } + } + + $person = new Person(); + + echo $accessor->getValue($person, 'Wouter'); // array(...) + +Writing to Arrays +----------------- + +The ``PropertyAccessor`` class can do more than just read an array, it can +also write to an array. This can be achieved using the +:method:`PropertyAccessor::setValue` +method:: + + // ... + $person = array(); + + $accessor->setValue($person, '[first_name]', 'Wouter'); + + echo $accessor->getValue($person, '[first_name]'); // 'Wouter' + // or + // echo $person['first_name']; // 'Wouter' + +Writing to Objects +------------------ + +The ``setValue`` method has the same features as the ``getValue`` method. You +can use setters, the magic ``__set`` or properties to set values:: + + // ... + class Person + { + public $firstName; + private $lastName; + private $children = array(); + + public function setLastName($name) + { + $this->lastName = $name; + } + + public function __set($property, $value) + { + $this->$property = $value; + } + + // ... + } + + $person = new Person(); + + $accessor->setValue($person, 'firstName', 'Wouter'); + $accessor->setValue($person, 'lastName', 'de Jong'); + $accessor->setValue($person, 'children', array(new Person())); + + echo $person->firstName; // 'Wouter' + echo $person->getLastName(); // 'de Jong' + echo $person->children; // array(Person()); + +Mixing Objects and Arrays +------------------------- + +You can also mix objects and arrays:: + + // ... + class Person + { + public $firstName; + private $children = array(); + + public function setChildren($children) + { + return $this->children; + } + + public function getChildren() + { + return $this->children; + } + } + + $person = new Person(); + + $accessor->setValue($person, 'children[0]', new Person); + // equal to $person->getChildren()[0] = new Person() + + $accessor->setValue($person, 'children[0].firstName', 'Wouter'); + // equal to $person->getChildren()[0]->firstName = 'Wouter' + + echo 'Hello '.$accessor->getValue($person, 'children[0].firstName'); // 'Wouter' + // equal to $person->getChildren()[0]->firstName + +.. _Packagist: https://packagist.org/packages/symfony/property-access diff --git a/components/routing/hostname_pattern.rst b/components/routing/hostname_pattern.rst new file mode 100644 index 00000000000..38bc0f143eb --- /dev/null +++ b/components/routing/hostname_pattern.rst @@ -0,0 +1,165 @@ +.. index:: + single: Routing; Matching on Hostname + +How to match a route based on the Host +====================================== + +.. versionadded:: 2.2 + Host matching support was added in Symfony 2.2 + +You can also match on the HTTP *host* of the incoming request. + +.. configuration-block:: + + .. code-block:: yaml + + mobile_homepage: + path: / + host: m.example.com + defaults: { _controller: AcmeDemoBundle:Main:mobileHomepage } + + homepage: + path: / + defaults: { _controller: AcmeDemoBundle:Main:homepage } + + .. code-block:: xml + + + + + + + AcmeDemoBundle:Main:mobileHomepage + + + + AcmeDemoBundle:Main:homepage + + + + .. code-block:: php + + use Symfony\Component\Routing\RouteCollection; + use Symfony\Component\Routing\Route; + + $collection = new RouteCollection(); + $collection->add('mobile_homepage', new Route('/', array( + '_controller' => 'AcmeDemoBundle:Main:mobileHomepage', + ), array(), array(), 'm.example.com')); + + $collection->add('homepage', new Route('/', array( + '_controller' => 'AcmeDemoBundle:Main:homepage', + ))); + + return $collection; + +Both routes match the same path ``/``, however the first one will match +only if the host is ``m.example.com``. + +Placeholders and Requirements in Hostname Patterns +-------------------------------------------------- + +If you're using the :doc:`DependencyInjection Component` +(or the full Symfony2 Framework), then you can use +:ref:`service container parameters` as +variables anywhere in your routes. + +You can avoid hardcoding the domain name by using a placeholder and a requirement. +The ``%domain%`` in requirements is replaced by the value of the ``domain`` +dependency injection container parameter. + +.. configuration-block:: + + .. code-block:: yaml + + mobile_homepage: + path: / + host: m.{domain} + defaults: { _controller: AcmeDemoBundle:Main:mobileHomepage } + requirements: + domain: %domain% + + homepage: + path: / + defaults: { _controller: AcmeDemoBundle:Main:homepage } + + .. code-block:: xml + + + + + + + AcmeDemoBundle:Main:mobileHomepage + %domain% + + + + AcmeDemoBundle:Main:homepage + + + + .. code-block:: php + + use Symfony\Component\Routing\RouteCollection; + use Symfony\Component\Routing\Route; + + $collection = new RouteCollection(); + $collection->add('mobile_homepage', new Route('/', array( + '_controller' => 'AcmeDemoBundle:Main:mobileHomepage', + ), array( + 'domain' => '%domain%', + ), array(), 'm.{domain}')); + + $collection->add('homepage', new Route('/', array( + '_controller' => 'AcmeDemoBundle:Main:homepage', + ))); + + return $collection; + +.. _component-routing-host-imported: + +Adding a Host Regex to Imported Routes +-------------------------------------------- + +You can set a host regex on imported routes: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/routing.yml + acme_hello: + resource: "@AcmeHelloBundle/Resources/config/routing.yml" + host: "hello.example.com" + + .. code-block:: xml + + + + + + + + + + .. code-block:: php + + // app/config/routing.php + use Symfony\Component\Routing\RouteCollection; + + $collection = new RouteCollection(); + $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"), '', array(), array(), array(), 'hello.example.com'); + + return $collection; + +The host ``hello.example.com`` will be set on each route loaded from the new +routing resource. diff --git a/components/routing/index.rst b/components/routing/index.rst index 33610e31730..b7f4d40386b 100644 --- a/components/routing/index.rst +++ b/components/routing/index.rst @@ -5,3 +5,4 @@ Routing :maxdepth: 2 introduction + hostname_pattern diff --git a/components/routing/introduction.rst b/components/routing/introduction.rst index 539061f62b1..8be2af7af92 100644 --- a/components/routing/introduction.rst +++ b/components/routing/introduction.rst @@ -70,9 +70,9 @@ which holds the name of the matched route. Defining routes ~~~~~~~~~~~~~~~ -A full route definition can contain up to four parts: +A full route definition can contain up to seven parts: -1. The URL pattern route. This is matched against the URL passed to the `RequestContext`, +1. The URL path route. This is matched against the URL passed to the `RequestContext`, and can contain named wildcard placeholders (e.g. ``{placeholders}``) to match dynamic parts in the URL. @@ -85,13 +85,27 @@ placeholders as regular expressions. 4. An array of options. These contain internal settings for the route and are the least commonly needed. +5. A host. This is matched against the host of the request. See + :doc:`/components/routing/hostname_pattern` for more details. + +6. An array of schemes. These enforce a certain HTTP scheme (``http``, ``https``). + +7. An array of methods. These enforce a certain HTTP request method (``HEAD``, + ``GET``, ``POST``, ...). + +.. versionadded:: 2.2 + Host matching support was added in Symfony 2.2 + Take the following route, which combines several of these ideas:: $route = new Route( '/archive/{month}', // path array('controller' => 'showArchive'), // default values - array('month' => '[0-9]{4}-[0-9]{2}'), // requirements - array() // options + array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements + array(), // options + '{subdomain}.example.com', // host + array(), // schemes + array() // methods ); // ... @@ -99,8 +113,9 @@ Take the following route, which combines several of these ideas:: $parameters = $matcher->match('/archive/2012-01'); // array( // 'controller' => 'showArchive', - // 'month' => '2012-01', - // '_route' => ... + // 'month' => '2012-01', + // 'subdomain' => 'www', + // '_route' => ... // ) $parameters = $matcher->match('/archive/foo'); @@ -110,21 +125,6 @@ In this case, the route is matched by ``/archive/2012-01``, because the ``{month wildcard matches the regular expression wildcard given. However, ``/archive/foo`` does *not* match, because "foo" fails the month wildcard. -Besides the regular expression constraints there are two special requirements -you can define: - -* ``_method`` enforces a certain HTTP request method (``HEAD``, ``GET``, ``POST``, ...) -* ``_scheme`` enforces a certain HTTP scheme (``http``, ``https``) - -For example, the following route would only accept requests to /foo with -the POST method and a secure connection:: - - $route = new Route( - '/foo', - array(), - array('_method' => 'post', '_scheme' => 'https' ) - ); - .. tip:: If you want to match all urls which start with a certain path and end in an @@ -136,27 +136,34 @@ the POST method and a secure connection:: array('suffix' => '.*') ); - Using Prefixes ~~~~~~~~~~~~~~ You can add routes or other instances of :class:`Symfony\\Component\\Routing\\RouteCollection` to *another* collection. This way you can build a tree of routes. Additionally you can define a prefix, -default requirements and default options to all routes of a subtree:: +default requirements, default options and host to all routes of a subtree with +the :method:`Symfony\\Component\\Routing\\RouteCollection::addPrefix` method:: $rootCollection = new RouteCollection(); $subCollection = new RouteCollection(); $subCollection->add(...); $subCollection->add(...); - - $rootCollection->addCollection( - $subCollection, - '/prefix', - array('_scheme' => 'https') + $subCollection->addPrefix( + '/prefix', // prefix + array(), // requirements + array(), // options + 'admin.example.com', // host + array('https') // schemes ); + $rootCollection->addCollection($subCollection); + +.. versionadded:: 2.2 + The ``addPrefix`` method is added in Symfony2.2. This was part of the + ``addCollection`` method in older versions. + Set the Request Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -209,9 +216,9 @@ a certain route:: .. note:: - If you have defined the ``_scheme`` requirement, an absolute URL is generated - if the scheme of the current :class:`Symfony\\Component\\Routing\\RequestContext` - does not match the requirement. + If you have defined a scheme, an absolute URL is generated if the scheme + of the current :class:`Symfony\\Component\\Routing\\RequestContext` does + not match the requirement. Load Routes from a File ~~~~~~~~~~~~~~~~~~~~~~~ @@ -233,11 +240,11 @@ If you're using the ``YamlFileLoader``, then route definitions look like this: # routes.yml route1: - pattern: /foo + path: /foo defaults: { _controller: 'MyController::fooAction' } route2: - pattern: /foo/bar + path: /foo/bar defaults: { _controller: 'MyController::foobarAction' } To load this file, you can use the following code. This assumes that your diff --git a/components/serializer.rst b/components/serializer.rst index 27c9d4aa0d8..0c70eb54d0e 100644 --- a/components/serializer.rst +++ b/components/serializer.rst @@ -11,6 +11,9 @@ The Serializer Component In order to do so, the Serializer Component follows the following simple schema. +.. _component-serializer-encoders: +.. _component-serializer-normalizers: + .. image:: /images/components/serializer/serializer_workflow.png As you can see in the picture above, an array is used as a man in @@ -42,7 +45,7 @@ which Encoders and Normalizer are going to be available:: use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - $encoders = array('xml' => new XmlEncoder(), 'json' => new JsonEncoder()); + $encoders = array(new XmlEncoder(), new JsonEncoder()); $normalizers = array(new GetSetMethodNormalizer()); $serializer = new Serializer($normalizers, $encoders); diff --git a/components/stopwatch.rst b/components/stopwatch.rst new file mode 100644 index 00000000000..af5f98e823b --- /dev/null +++ b/components/stopwatch.rst @@ -0,0 +1,101 @@ +.. index:: + single: Stopwatch + single: Components; Stopwatch + +The Stopwatch Component +======================= + + Stopwatch component provides a way to profile code. + +.. versionadded:: 2.2 + The Stopwatch Component is new to Symfony 2.2. Previously, the ``Stopwatch`` + class was located in the ``HttpKernel`` component (and was new in 2.1). + +Installation +------------ + +You can install the component in two different ways: + +* Use the official Git repository (https://github.com/symfony/Stopwatch); +* :doc:`Install it via Composer` (``symfony/stopwatch`` on `Packagist`_). + +Usage +----- + +The Stopwatch component provides an easy and consistent way to measure execution +time of certain parts of code so that you don't constantly have to parse +microtime by yourself. Instead, use the simple +:class:`Symfony\\Component\\Stopwatch\\Stopwatch` class:: + + use Symfony\Component\Stopwatch\Stopwatch; + + $stopwatch = new Stopwatch(); + // Start event named 'eventName' + $stopwatch->start('eventName'); + // ... some code goes here + $event = $stopwatch->stop('eventName'); + +You can also provide a category name to an event:: + + $stopwatch->start('eventName', 'categoryName'); + +You can consider categories as a way of tagging events. For example, the +Symfony Profiler tool uses categories to nicely color-code different events. + +Periods +------- + +As you know from the real world, all stopwatches come with two buttons: +one to start and stop the stopwatch, and another to measure the lap time. +This is exactly what the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap`` +method does:: + + $stopwatch = new Stopwatch(); + // Start event named 'foo' + $stopwatch->start('foo'); + // ... some code goes here + $stopwatch->lap('foo'); + // ... some code goes here + $stopwatch->lap('foo'); + // ... some other code goes here + $event = $stopwatch->stop('foo'); + +Lap information is stored as "periods" within the event. To get lap information +call:: + + $event->getPeriods(); + +In addition to periods, you can get other useful information from the event object. +For example:: + + $event->getCategory(); // Returns the category the event was started in + $event->getOrigin(); // Returns the event start time in milliseconds + $event->ensureStopped(); // Stops all periods not already stopped + $event->getStartTime(); // Returns the start time of the very first period + $event->getEndTime(); // Returns the end time of the very last period + $event->getDuration(); // Returns the event duration, including all periods + $event->getMemory(); // Returns the max memory usage of all periods + +Sections +-------- + +Sections are a way to logically split the timeline into groups. You can see +how Symfony uses sections to nicely visualize the framework lifecycle in the +Symfony Profiler tool. Here is a basic usage example using sections:: + + $stopwatch = new Stopwatch(); + + $stopwatch->openSection(); + $stopwatch->start('parsing_config_file', 'filesystem_operations'); + $stopwatch->stopSection('routing'); + + $events = $stopwatch->getSectionEvents('routing'); + +You can reopen a closed section by calling the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::openSection`` +method and specifying the id of the section to be reopened:: + + $stopwatch->openSection('routing'); + $stopwatch->start('building_config_tree'); + $stopwatch->stopSection('routing'); + +.. _Packagist: https://packagist.org/packages/symfony/stopwatch diff --git a/components/using_components.rst b/components/using_components.rst index ad333f1f62c..174b041facc 100644 --- a/components/using_components.rst +++ b/components/using_components.rst @@ -6,7 +6,7 @@ How to Install and Use the Symfony2 Components ============================================== If you're starting a new project (or already have a project) that will use -one or more components, the easiest way to integrate everything is with Composer. +one or more components, the easiest way to integrate everything is with `Composer`_. Composer is smart enough to download the component(s) that you need and take care of autoloading so that you can begin using the libraries immediately. @@ -24,7 +24,7 @@ Using the Finder Component { "require": { - "symfony/finder": "2.1.*" + "symfony/finder": "2.2.*" } } @@ -33,13 +33,15 @@ may also need to adjust the version (e.g. ``2.1.1`` or ``2.2.*``). You can research the component names and versions at `packagist.org`_. -**3.** Download the vendor libraries and generate the ``vendor/autoload.php`` file: +**3.** `Install composer`_ if you don't already have it present on your system: + +**4.** Download the vendor libraries and generate the ``vendor/autoload.php`` file: .. code-block:: bash $ php composer.phar install -**4.** Write your code: +**5.** Write your code: Once Composer has downloaded the component(s), all you need to do is include the ``vendor/autoload.php`` file that was generated by Composer. This file @@ -48,6 +50,7 @@ immediately:: // File: src/script.php + // update this to the path to the "vendor/" directory, relative to this file require_once '../vendor/autoload.php'; use Symfony\Component\Finder\Finder; @@ -66,9 +69,9 @@ immediately:: { "require": { - "symfony/finder": "2.1.*", - "symfony/dom-crawler": "2.1.*", - "symfony/css-selector": "2.1.*" + "symfony/finder": "2.2.*", + "symfony/dom-crawler": "2.2.*", + "symfony/css-selector": "2.2.*" } } @@ -78,7 +81,7 @@ immediately:: { "require": { - "symfony/symfony": "2.1.*" + "symfony/symfony": "2.2.*" } } @@ -93,4 +96,6 @@ documentation to find out more about how to use it. And have fun! +.. _Composer: http://getcomposer.org +.. _Install composer: http://getcomposer.org/download/ .. _packagist.org: https://packagist.org/ \ No newline at end of file diff --git a/components/yaml/introduction.rst b/components/yaml/introduction.rst index 43bcb5bc95f..37bbf6eaeec 100644 --- a/components/yaml/introduction.rst +++ b/components/yaml/introduction.rst @@ -131,15 +131,8 @@ When loading a YAML file, it is sometimes better to use the The :method:`Symfony\\Component\\Yaml\\Yaml::parse` static method takes a YAML string or a file containing YAML. Internally, it calls the -:method:`Symfony\\Component\\Yaml\\Parser::parse` method, but with some added -bonuses: - -* It executes the YAML file as if it was a PHP file, so that you can embed PHP - commands in YAML files; - -* When a file cannot be parsed, it automatically adds the file name to the - error message, simplifying debugging when your application is loading - several YAML files. +:method:`Symfony\\Component\\Yaml\\Parser::parse` method, but enhances the +error if something goes wrong by adding the filename to the message. Writing YAML Files ~~~~~~~~~~~~~~~~~~ diff --git a/contributing/code/patches.rst b/contributing/code/patches.rst index 599d29409ff..352c2382625 100644 --- a/contributing/code/patches.rst +++ b/contributing/code/patches.rst @@ -14,8 +14,8 @@ Before working on Symfony2, setup a friendly environment with the following software: * Git; -* PHP version 5.3.2 or above; -* PHPUnit 3.5.11 or above. +* PHP version 5.3.3 or above; +* PHPUnit 3.6.4 or above. Configure Git ~~~~~~~~~~~~~ @@ -254,8 +254,8 @@ You can now make a pull request on the ``symfony/symfony`` Github repository. .. tip:: - Take care to point your pull request towards ``symfony:2.0`` if you want - the core team to pull a bugfix based on the 2.0 branch. + Take care to point your pull request towards ``symfony:2.1`` if you want + the core team to pull a bugfix based on the 2.1 branch. To ease the core team work, always include the modified components in your pull request message, like in: diff --git a/contributing/code/tests.rst b/contributing/code/tests.rst index a7795519cef..873f3c382eb 100644 --- a/contributing/code/tests.rst +++ b/contributing/code/tests.rst @@ -7,7 +7,7 @@ Symfony2 test suite to check that you have not broken anything. PHPUnit ------- -To run the Symfony2 test suite, `install`_ PHPUnit 3.5.11 or later first: +To run the Symfony2 test suite, `install`_ PHPUnit 3.6.4 or later first: .. code-block:: bash @@ -29,22 +29,44 @@ The test suite needs the following third-party libraries: * Twig * Monolog -To install them all, run the `vendors` script: +To install them all, use `Composer`_: + +Step 1: Get `Composer`_ + +.. code-block:: bash + + curl -s http://getcomposer.org/installer | php + +Make sure you download ``composer.phar`` in the same folder where +the ``composer.json`` file is located. + +Step 2: Install vendors .. code-block:: bash - $ php vendors.php install + $ php composer.phar --dev install .. note:: Note that the script takes some time to finish. +.. note:: + + If you don't have ``curl`` installed, you can also just download the ``installer`` + file manually at http://getcomposer.org/installer. Place this file into your + project and then run: + + .. code-block:: bash + + $ php installer + $ php composer.phar --dev install + After installation, you can update the vendors to their latest version with the follow command: .. code-block:: bash - $ php vendors.php update + $ php composer.phar --dev update Running ------- @@ -94,3 +116,4 @@ browser. dependencies installed. .. _install: http://www.phpunit.de/manual/current/en/installation.html +.. _`Composer`: http://getcomposer.org/ diff --git a/contributing/documentation/translations.rst b/contributing/documentation/translations.rst index e4d05225f68..88ea2112c25 100644 --- a/contributing/documentation/translations.rst +++ b/contributing/documentation/translations.rst @@ -21,6 +21,7 @@ for. Here is the list of the official *master* repositories: * *Italian*: https://github.com/garak/symfony-docs-it * *Japanese*: https://github.com/symfony-japan/symfony-docs-ja * *Polish*: https://github.com/ampluso/symfony-docs-pl +* *Portuguese (Brazilian)*: https://github.com/andreia/symfony-docs-pt-BR * *Romanian*: https://github.com/sebio/symfony-docs-ro * *Russian*: https://github.com/avalanche123/symfony-docs-ru * *Spanish*: https://github.com/gitnacho/symfony-docs-es diff --git a/cookbook/assetic/asset_management.rst b/cookbook/assetic/asset_management.rst index d74d28b20c4..e47b60e8b87 100644 --- a/cookbook/assetic/asset_management.rst +++ b/cookbook/assetic/asset_management.rst @@ -401,10 +401,6 @@ the following change in your ``config_dev.yml`` file: 'use_controller' => false, )); -.. note:: - - You'll also have to remove the ``_assetic`` route in your ``app/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: diff --git a/cookbook/bundles/extension.rst b/cookbook/bundles/extension.rst index e658d96eecd..6ede7320fab 100644 --- a/cookbook/bundles/extension.rst +++ b/cookbook/bundles/extension.rst @@ -481,6 +481,7 @@ that an unsupported option was passed:: public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); // ... @@ -496,6 +497,66 @@ normalization and advanced merging. You can read more about this in :doc:`the Co You can also see it action by checking out some of the core Configuration classes, such as the one from the `FrameworkBundle Configuration`_ or the `TwigBundle Configuration`_. +Modifying the configuration of another Bundle +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have multiple bundles that depend on each other, it may be useful +to allow one ``Extension`` class to modify the configuration passed to another +bundle's ``Extension`` class, as if the end-developer has actually placed that +configuration in his/her ``app/config/config.yml`` file. + +For more details, see :doc:`/cookbook/bundles/prepend_extension`. + +Default Configuration Dump +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ``config:dump-reference`` command was added in Symfony 2.1 + +The ``config:dump-reference`` command allows a bundle's default configuration to +be output to the console in yaml. + +As long as your bundle's configuration is located in the standard location +(``YourBundle\DependencyInjection\Configuration``) and does not have a +``__construct()`` it will work automatically. If you have something +different, your ``Extension`` class must override the +:method:`Extension::getConfiguration() ` +method and return an instance of your +``Configuration``. + +Comments and examples can be added to your configuration nodes using the +``->info()`` and ``->example()`` methods:: + + // src/Acme/HelloBundle/DependencyExtension/Configuration.php + namespace Acme\HelloBundle\DependencyInjection; + + use Symfony\Component\Config\Definition\Builder\TreeBuilder; + use Symfony\Component\Config\Definition\ConfigurationInterface; + + class Configuration implements ConfigurationInterface + { + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('acme_hello'); + + $rootNode + ->children() + ->scalarNode('my_type') + ->defaultValue('bar') + ->info('what my_type configures') + ->example('example setting') + ->end() + ->end() + ; + + return $treeBuilder; + } + } + +This text appears as yaml comments in the output of the ``config:dump-reference`` +command. + .. index:: pair: Convention; Configuration diff --git a/cookbook/bundles/index.rst b/cookbook/bundles/index.rst index 4a38dc56c62..c1ccf326f05 100644 --- a/cookbook/bundles/index.rst +++ b/cookbook/bundles/index.rst @@ -9,3 +9,4 @@ Bundles override remove extension + prepend_extension diff --git a/cookbook/bundles/prepend_extension.rst b/cookbook/bundles/prepend_extension.rst new file mode 100644 index 00000000000..74f1500c8f4 --- /dev/null +++ b/cookbook/bundles/prepend_extension.rst @@ -0,0 +1,135 @@ +.. index:: + single: Configuration; Semantic + single: Bundle; Extension configuration + +How to simplify configuration of multiple Bundles +================================================= + +When building reusable and extensible applications, developers are often +faced with a choice: either create a single large Bundle or multiple smaller +Bundles. Creating a single Bundle has the draw back that it's impossible for +users to choose to remove functionality they are not using. Creating multiple +Bundles has the draw back that configuration becomes more tedious and settings +often need to be repeated for various Bundles. + +Using the below approach, it is possible to remove the disadvantage of the +multiple Bundle approach by enabling a single Extension to prepend the settings +for any Bundle. It can use the settings defined in the ``app/config/config.yml`` +to prepend settings just as if they would have been written explicitly by the +user in the application configuration. + +For example, this could be used to configure the entity manager name to use in +multiple Bundles. Or it can be used to enable an optional feature that depends +on another Bundle being loaded as well. + +To give an Extension the power to do this, it needs to implement +:class:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface`:: + + // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php + namespace Acme\HelloBundle\DependencyInjection; + + use Symfony\Component\HttpKernel\DependencyInjection\Extension; + use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; + use Symfony\Component\DependencyInjection\ContainerBuilder; + + class AcmeHelloExtension extends Extension implements PrependExtensionInterface + { + // ... + + public function prepend(ContainerBuilder $container) + { + // ... + } + } + +Inside the :method:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface::prepend` +method, developers have full access to the :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder` +instance just before the :method:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface::load` +method is called on each of the registered Bundle Extensions. In order to +prepend settings to a Bundle extension developers can use the +:method:`Symfony\\Component\\DependencyInjection\\ContainerBuilder::prependExtensionConfig` +method on the :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder` +instance. As this method only prepends settings, any other settings done explicitly +inside the ``app/config/config.yml`` would override these prepended settings. + +The following example illustrates how to prepend +a configuration setting in multiple Bundles as well as disable a flag in multiple Bundles +in case a specific other Bundle is not registered:: + + public function prepend(ContainerBuilder $container) + { + // get all Bundles + $bundles = $container->getParameter('kernel.bundles'); + // determine if AcmeGoodbyeBundle is registered + if (!isset($bundles['AcmeGoodbyeBundle'])) { + // disable AcmeGoodbyeBundle in Bundles + $config = array('use_acme_goodbye' => false); + foreach ($container->getExtensions() as $name => $extension) { + switch ($name) { + case 'acme_something': + case 'acme_other': + // set use_acme_goodbye to false in the config of acme_something and acme_other + // note that if the user manually configured use_acme_goodbye to true in the + // app/config/config.yml then the setting would in the end be true and not false + $container->prependExtensionConfig($name, $config); + break; + } + } + } + + // process the configuration of AcmeHelloExtension + $configs = $container->getExtensionConfig($this->getAlias()); + // use the Configuration class to generate a config array with the settings ``acme_hello`` + $config = $this->processConfiguration(new Configuration(), $configs); + + // check if entity_manager_name is set in the ``acme_hello`` configuration + if (isset($config['entity_manager_name'])) { + // prepend the acme_something settings with the entity_manager_name + $config = array('entity_manager_name' => $config['entity_manager_name']); + $container->prependExtensionConfig('acme_something', $config); + } + } + +The above would be the equivalent of writing the following into the ``app/config/config.yml`` +in case ``AcmeGoodbyeBundle`` is not registered and the ``entity_manager_name`` setting +for ``acme_hello`` is set to ``non_default``: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + + acme_something: + # ... + use_acme_goodbye: false + entity_manager_name: non_default + + acme_other: + # ... + use_acme_goodbye: false + + .. code-block:: xml + + + + + non_default + + + + + .. code-block:: php + + // app/config/config.php + + $container->loadFromExtension('acme_something', array( + ..., + 'use_acme_goodbye' => false, + 'entity_manager_name' => 'non_default', + )); + $container->loadFromExtension('acme_other', array( + ..., + 'use_acme_goodbye' => false, + )); + diff --git a/cookbook/configuration/apache_router.rst b/cookbook/configuration/apache_router.rst index 6c33bc52185..beac6121416 100644 --- a/cookbook/configuration/apache_router.rst +++ b/cookbook/configuration/apache_router.rst @@ -60,13 +60,13 @@ To test that it's working, let's create a very basic route for demo bundle: # app/config/routing.yml hello: - pattern: /hello/{name} + path: /hello/{name} defaults: { _controller: AcmeDemoBundle:Demo:hello } .. code-block:: xml - + AcmeDemoBundle:Demo:hello diff --git a/cookbook/configuration/override_dir_structure.rst b/cookbook/configuration/override_dir_structure.rst index 20d5184446f..31aeb973847 100644 --- a/cookbook/configuration/override_dir_structure.rst +++ b/cookbook/configuration/override_dir_structure.rst @@ -91,6 +91,19 @@ may need to modify the paths inside these files:: require_once __DIR__.'/../Symfony/app/bootstrap.php.cache'; require_once __DIR__.'/../Symfony/app/AppKernel.php'; +Since Symfony 2.1 (in which Composer is introduced), you also need to change +the ``extra.symfony-web-dir`` option in the ``composer.json`` file: + +.. code-block:: json + + { + ... + "extra": { + ... + "symfony-web-dir": "my_new_web_dir" + } + } + .. tip:: Some shared hosts have a ``public_html`` web directory root. Renaming diff --git a/cookbook/configuration/pdo_session_storage.rst b/cookbook/configuration/pdo_session_storage.rst index ba9c490f6a2..6c60dbc8188 100644 --- a/cookbook/configuration/pdo_session_storage.rst +++ b/cookbook/configuration/pdo_session_storage.rst @@ -1,7 +1,7 @@ .. index:: single: Session; Database Storage -How to use PdoSessionStorage to store Sessions in the Database +How to use PdoSessionHandler to store Sessions in the Database ============================================================== The default session storage of Symfony2 writes the session information to @@ -10,10 +10,17 @@ values instead of files, because databases are easier to use and scale in a multi-webserver environment. Symfony2 has a built-in solution for database session storage called -:class:`Symfony\\Component\\HttpFoundation\\SessionStorage\\PdoSessionStorage`. +:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler`. To use it, you just need to change some parameters in ``config.yml`` (or the configuration format of your choice): +.. versionadded:: 2.1 + In Symfony2.1 the class and namespace are slightly modified. You can now + find the session storage classes in the `Session\\Storage` namespace: + ``Symfony\Component\HttpFoundation\Session\Storage``. Also + note that in Symfony2.1 you should configure ``handler_id`` not ``storage_id`` like in Symfony2.0. + Below, you'll notice that ``%session.storage.options%`` is not used anymore. + .. configuration-block:: .. code-block:: yaml @@ -22,7 +29,7 @@ configuration format of your choice): framework: session: # ... - storage_id: session.storage.pdo + handler_id: session.handler.pdo parameters: pdo.db_options: @@ -39,15 +46,15 @@ configuration format of your choice): user: myuser password: mypassword - session.storage.pdo: - class: Symfony\Component\HttpFoundation\SessionStorage\PdoSessionStorage - arguments: [@pdo, %session.storage.options%, %pdo.db_options%] + session.handler.pdo: + class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler + arguments: [@pdo, %pdo.db_options%] .. code-block:: xml - + @@ -66,9 +73,8 @@ configuration format of your choice): mypassword - + - %session.storage.options% %pdo.db_options% @@ -82,9 +88,8 @@ configuration format of your choice): $container->loadFromExtension('framework', array( ..., 'session' => array( - // ... - - 'storage_id' => 'session.storage.pdo', + // ..., + 'handler_id' => 'session.handler.pdo', ), )); @@ -102,12 +107,11 @@ configuration format of your choice): )); $container->setDefinition('pdo', $pdoDefinition); - $storageDefinition = new Definition('Symfony\Component\HttpFoundation\SessionStorage\PdoSessionStorage', array( + $storageDefinition = new Definition('Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler', array( new Reference('pdo'), - '%session.storage.options%', '%pdo.db_options%', )); - $container->setDefinition('session.storage.pdo', $storageDefinition); + $container->setDefinition('session.handler.pdo', $storageDefinition); * ``db_table``: The name of the session table in your database * ``db_id_col``: The name of the id column in your session table (VARCHAR(255) or larger) @@ -192,16 +196,16 @@ For MSSQL, the statement might look like the following: .. code-block:: sql CREATE TABLE [dbo].[session]( - [session_id] [nvarchar](255) NOT NULL, - [session_value] [ntext] NOT NULL, + [session_id] [nvarchar](255) NOT NULL, + [session_value] [ntext] NOT NULL, [session_time] [int] NOT NULL, - PRIMARY KEY CLUSTERED( - [session_id] ASC - ) WITH ( - PAD_INDEX = OFF, - STATISTICS_NORECOMPUTE = OFF, - IGNORE_DUP_KEY = OFF, - ALLOW_ROW_LOCKS = ON, - ALLOW_PAGE_LOCKS = ON - ) ON [PRIMARY] + PRIMARY KEY CLUSTERED( + [session_id] ASC + ) WITH ( + PAD_INDEX = OFF, + STATISTICS_NORECOMPUTE = OFF, + IGNORE_DUP_KEY = OFF, + ALLOW_ROW_LOCKS = ON, + ALLOW_PAGE_LOCKS = ON + ) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/cookbook/console/sending_emails.rst b/cookbook/console/sending_emails.rst index d6a6e4a558e..c184e915326 100644 --- a/cookbook/console/sending_emails.rst +++ b/cookbook/console/sending_emails.rst @@ -15,7 +15,56 @@ way of saying that you need to configure your environment so that it knows what URL it should use when generating URLs. There are two ways of configuring the request context: at the application level -(only available in Symfony 2.1+) and per Command. +and per Command. + +Configuring the Request Context globally +---------------------------------------- + +.. versionadded:: 2.1 + The ``host`` and ``scheme`` parameters are available since Symfony 2.1 + +.. versionadded: 2.2 + The ``base_url`` parameter is available since Symfony 2.2 + +To configure the Request Context - which is used by the URL Generator - you can +redefine the parameters it uses as default values to change the default host +(localhost) and scheme (http). Starting with Symfony 2.2 you can also configure +the base path if Symfony is not running in the root directory. + +Note that this does not impact URLs generated via normal web requests, since those +will override the defaults. + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/parameters.yml + parameters: + router.request_context.host: example.org + router.request_context.scheme: https + router.request_context.base_url: my/path + + .. code-block:: xml + + + + + + + + example.org + https + my/path + + + + .. code-block:: php + + // app/config/config_test.php + $container->setParameter('router.request_context.host', 'example.org'); + $container->setParameter('router.request_context.scheme', 'https'); + $container->setParameter('router.request_context.base_url', 'my/path'); Configuring the Request Context per Command ------------------------------------------- @@ -33,6 +82,7 @@ service and override its settings:: $context = $this->getContainer()->get('router')->getContext(); $context->setHost('example.com'); $context->setScheme('https'); + $context->setBaseUrl('my/path'); // ... your code here } @@ -64,4 +114,5 @@ commands and uses a different spooling method. Taking care of the spooling is only needed when memory spooling is used. If you are using file spooling (or no spooling at all), there is no need - to flush the queue manually within the command. \ No newline at end of file + to flush the queue manually within the command. + diff --git a/cookbook/controller/error_pages.rst b/cookbook/controller/error_pages.rst index a82f81b9ba0..b289a5f4682 100644 --- a/cookbook/controller/error_pages.rst +++ b/cookbook/controller/error_pages.rst @@ -50,6 +50,14 @@ end-user, create a new template located at +.. caution:: + + You **must not** use ``is_granted`` in your error pages (or layout used + by your error pages), because the router runs before the firewall. If + the router throws an exception (for instance, when the route does not + match), then using ``is_granted`` will throw a further exception. You + can use ``is_granted`` safely by saying ``{% if app.security and is_granted('...') %}``. + .. tip:: If you're not familiar with Twig, don't worry. Twig is a simple, powerful @@ -90,7 +98,7 @@ Symfony uses the following algorithm to determine which template to use: To see the full list of default error templates, see the ``Resources/views/Exception`` directory of the ``TwigBundle``. In a standard Symfony2 installation, the ``TwigBundle`` can be found at - ``vendor/symfony/src/Symfony/Bundle/TwigBundle``. Often, the easiest way + ``vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle``. Often, the easiest way to customize an error page is to copy it from the ``TwigBundle`` into ``app/Resources/TwigBundle/views/Exception`` and then modify it. diff --git a/cookbook/controller/service.rst b/cookbook/controller/service.rst index 3291ce3a1a2..8466bc802ab 100644 --- a/cookbook/controller/service.rst +++ b/cookbook/controller/service.rst @@ -22,7 +22,7 @@ value: .. code-block:: yaml my_controller: - pattern: / + path: / defaults: { _controller: my_controller:indexAction } To use a controller in this way, it must be defined in the service container diff --git a/cookbook/debugging.rst b/cookbook/debugging.rst index 2b84571036f..cadd1303667 100644 --- a/cookbook/debugging.rst +++ b/cookbook/debugging.rst @@ -30,14 +30,12 @@ The ``app_dev.php`` front controller reads as follows by default:: // ... - require_once __DIR__.'/../app/bootstrap.php.cache'; + $loader = require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; - use Symfony\Component\HttpFoundation\Request; - $kernel = new AppKernel('dev', true); $kernel->loadClassCache(); - $kernel->handle(Request::createFromGlobals())->send(); + $request = Request::createFromGlobals(); To make your debugger happier, disable all PHP class caches by removing the call to ``loadClassCache()`` and by replacing the require statements like @@ -45,16 +43,15 @@ below:: // ... - // require_once __DIR__.'/../app/bootstrap.php.cache'; - require_once __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; - require_once __DIR__.'/../app/autoload.php'; + // $loader = require_once __DIR__.'/../app/bootstrap.php.cache'; + $loader = require_once __DIR__.'/../app/autoload.php'; require_once __DIR__.'/../app/AppKernel.php'; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel('dev', true); // $kernel->loadClassCache(); - $kernel->handle(Request::createFromGlobals())->send(); + $request = Request::createFromGlobals(); .. tip:: diff --git a/cookbook/deployment-tools.rst b/cookbook/deployment-tools.rst index 9b136ef31d5..237ba05e9e8 100644 --- a/cookbook/deployment-tools.rst +++ b/cookbook/deployment-tools.rst @@ -16,7 +16,7 @@ Symfony2 Deployment Basics The typical steps taken while deploying a Symfony2 application include: #. Upload your modified code to the live server; -#. Update your vendor dependencies (typically done via ``bin/vendors``, and may +#. Update your vendor dependencies (typically done via Composer, and may be done before uploading); #. Running database migrations or similar tasks to update any changed data structures; #. Clearing (and perhaps more importantly, warming up) your cache. @@ -71,7 +71,7 @@ Common Post-Deployment Tasks After deploying your actual source code, there are a number of common things you'll need to do: -A) Configure your ``app/config/parameters.ini`` file +A) Configure your ``app/config/parameters.yml`` file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This file should be customized on each system. The method you use to @@ -88,7 +88,12 @@ as your normally do: .. code-block:: bash - $ php bin/vendors install + $ php composer.phar install --optimize-autoloader + +.. tip:: + + The ``--optimize-autoloader`` flag makes Composer's autoloader more + performant by building a "class map". C) Clear your Symfony cache ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -99,7 +104,16 @@ Make sure you clear (and warm-up) your Symfony cache: $ php app/console cache:clear --env=prod --no-debug -D) Other things! +D) Dump your Assetic assets +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you're using Assetic, you'll also want to dump your assets: + +.. code-block:: bash + + $ php app/console assetic:dump --env=prod --no-debug + +E) Other things! ~~~~~~~~~~~~~~~~ There may be lots of other things that you need to do, depending on your @@ -107,8 +121,7 @@ setup: * Running any database migrations * Clearing your APC cache -* Dumping your Assetic assets (taken care of already in ``cache:clear``) -* Running ``assets:install`` (taken care of already in ``bin/vendors``) +* Running ``assets:install`` (taken care of already in ``composer.phar install``) * Add/edit CRON jobs * Pushing assets to a CDN * ... @@ -126,7 +139,7 @@ are simple and more complex tools and one can make the deployment as easy (or sophisticated) as your environment requires. Don't forget that deploying your application also involves updating any dependency -(typically via ``bin/vendors``), migrating your database, clearing your cache and +(typically via Composer), migrating your database, clearing your cache and other potential things like pushing assets to a CDN (see `Common Post-Deployment Tasks`_). The Tools diff --git a/cookbook/doctrine/custom_dql_functions.rst b/cookbook/doctrine/custom_dql_functions.rst index 257cd2e9659..9c863055c08 100644 --- a/cookbook/doctrine/custom_dql_functions.rst +++ b/cookbook/doctrine/custom_dql_functions.rst @@ -82,4 +82,4 @@ In Symfony, you can register your custom DQL functions as follows: ), )); -.. _`DQL User Defined Functions`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/cookbook/dql-user-defined-functions.html +.. _`DQL User Defined Functions`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/dql-user-defined-functions.html diff --git a/cookbook/doctrine/file_uploads.rst b/cookbook/doctrine/file_uploads.rst index 2b47b8d4cf0..16816db6a4c 100644 --- a/cookbook/doctrine/file_uploads.rst +++ b/cookbook/doctrine/file_uploads.rst @@ -120,9 +120,10 @@ look like this:: Next, create this property on your ``Document`` class and add some validation rules:: -use Symfony\Component\HttpFoundation\File\UploadedFile; + use Symfony\Component\HttpFoundation\File\UploadedFile; // ... + class Document { /** @@ -237,10 +238,10 @@ The following controller shows you how to handle the entire process:: ->getForm() ; - if ($this->getRequest()->getMethod() === 'POST') { - $form->bindRequest($this->getRequest()); + if ($this->getRequest()->isMethod('POST')) { + $form->bind($this->getRequest()); if ($form->isValid()) { - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $em->persist($document); $em->flush(); @@ -288,7 +289,7 @@ a new ``upload()`` method on the ``Document`` class, which you'll create in a moment to handle the file upload:: if ($form->isValid()) { - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $document->upload(); @@ -322,7 +323,7 @@ object, which is what's returned after a ``file`` field is submitted:: $this->path = $this->getFile()->getClientOriginalName(); // clean up the file property as you won't need it anymore - $this->setFile(null); + $this->file = null; } Using Lifecycle Callbacks @@ -408,7 +409,7 @@ Next, refactor the ``Document`` class to take advantage of these callbacks:: // the entity from being persisted to the database on error $this->getFile()->move($this->getUploadRootDir(), $this->path); - $this->setFile(null); + // check if we have an old image if (isset($this->temp)) { @@ -417,6 +418,7 @@ Next, refactor the ``Document`` class to take advantage of these callbacks:: // clear the temp image path $this->temp = null; } + $this->file = null; } /** @@ -438,7 +440,7 @@ Now that the moving of the file is handled atomically by the entity, the call to ``$document->upload()`` should be removed from the controller:: if ($form->isValid()) { - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $em->persist($document); $em->flush(); diff --git a/cookbook/doctrine/index.rst b/cookbook/doctrine/index.rst index 930a8262608..170a5f2d718 100644 --- a/cookbook/doctrine/index.rst +++ b/cookbook/doctrine/index.rst @@ -11,4 +11,5 @@ Doctrine reverse_engineering multiple_entity_managers custom_dql_functions + resolve_target_entity registration_form diff --git a/cookbook/doctrine/registration_form.rst b/cookbook/doctrine/registration_form.rst index 20bf7152d73..9e735ee877a 100644 --- a/cookbook/doctrine/registration_form.rst +++ b/cookbook/doctrine/registration_form.rst @@ -94,11 +94,12 @@ Next, create the form for the ``User`` model:: namespace Acme\AccountBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; class UserType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('email', 'email'); $builder->add('plainPassword', 'repeated', array( @@ -108,9 +109,11 @@ Next, create the form for the ``User`` model:: )); } - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array('data_class' => 'Acme\AccountBundle\Entity\User'); + $resolver->setDefaults(array( + 'data_class' => 'Acme\AccountBundle\Entity\User' + )); } public function getName() @@ -148,6 +151,7 @@ Start by creating a simple class which represents the "registration":: { /** * @Assert\Type(type="Acme\AccountBundle\Entity\User") + * @Assert\Valid() */ protected $user; @@ -184,11 +188,11 @@ Next, create the form for this ``Registration`` model:: namespace Acme\AccountBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; class RegistrationType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('user', new UserType()); $builder->add( @@ -260,7 +264,7 @@ the validation and saves the data into the database:: $form = $this->createForm(new RegistrationType(), new Registration()); - $form->bindRequest($this->getRequest()); + $form->bind($this->getRequest()); if ($form->isValid()) { $registration = $form->getData(); diff --git a/cookbook/doctrine/resolve_target_entity.rst b/cookbook/doctrine/resolve_target_entity.rst new file mode 100644 index 00000000000..9fbe7b7176c --- /dev/null +++ b/cookbook/doctrine/resolve_target_entity.rst @@ -0,0 +1,162 @@ +.. index:: + single: Doctrine; Resolving target entities + single: Doctrine; Define relationships with abstract classes and interfaces + +How to Define Relationships with Abstract Classes and Interfaces +================================================================ + +.. versionadded:: 2.1 + The ResolveTargetEntityListener is new to Doctrine 2.2, which was first + packaged with Symfony 2.1. + +One of the goals of bundles is to create discreet bundles of functionality +that do not have many (if any) dependencies, allowing you to use that +functionality in other applications without including unnecessary items. + +Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``, +that functions by intercepting certain calls inside Doctrine and rewriting +``targetEntity`` parameters in your metadata mapping at runtime. It means that +in your bundle you are able to use an interface or abstract class in your +mappings and expect correct mapping to a concrete entity at runtime. + +This functionality allows you to define relationships between different entities +without making them hard dependencies. + +Background +---------- + +Suppose you have an `InvoiceBundle` which provides invoicing functionality +and a `CustomerBundle` that contains customer management tools. You want +to keep these separated, because they can be used in other systems without +each other, but for your application you want to use them together. + +In this case, you have an ``Invoice`` entity with a relationship to a +non-existent object, an ``InvoiceSubjectInterface``. The goal is to get +the ``ResolveTargetEntityListener`` to replace any mention of the interface +with a real object that implements that interface. + +Set up +------ + +Let's use the following basic entities (which are incomplete for brevity) +to explain how to set up and use the RTEL. + +A Customer entity:: + + // src/Acme/AppBundle/Entity/Customer.php + + namespace Acme\AppBundle\Entity; + + use Doctrine\ORM\Mapping as ORM; + use Acme\CustomerBundle\Entity\Customer as BaseCustomer; + use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; + + /** + * @ORM\Entity + * @ORM\Table(name="customer") + */ + class Customer extends BaseCustomer implements InvoiceSubjectInterface + { + // In our example, any methods defined in the InvoiceSubjectInterface + // are already implemented in the BaseCustomer + } + +An Invoice entity:: + + // src/Acme/InvoiceBundle/Entity/Invoice.php + + namespace Acme\InvoiceBundle\Entity; + + use Doctrine\ORM\Mapping AS ORM; + use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; + + /** + * Represents an Invoice. + * + * @ORM\Entity + * @ORM\Table(name="invoice") + */ + class Invoice + { + /** + * @ORM\ManyToOne(targetEntity="Acme\InvoiceBundle\Model\InvoiceSubjectInterface") + * @var InvoiceSubjectInterface + */ + protected $subject; + } + +An InvoiceSubjectInterface:: + + // src/Acme/InvoiceBundle/Model/InvoiceSubjectInterface.php + + namespace Acme\InvoiceBundle\Model; + + /** + * An interface that the invoice Subject object should implement. + * In most circumstances, only a single object should implement + * this interface as the ResolveTargetEntityListener can only + * change the target to a single object. + */ + interface InvoiceSubjectInterface + { + // List any additional methods that your InvoiceBundle + // will need to access on the subject so that you can + // be sure that you have access to those methods. + + /** + * @return string + */ + public function getName(); + } + +Next, you need to configure the listener, which tells the DoctrineBundle +about the replacement: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + doctrine: + # .... + orm: + # .... + resolve_target_entities: + Acme\InvoiceBundle\Model\InvoiceSubjectInterface: Acme\AppBundle\Entity\Customer + + .. code-block:: xml + + + + + + + + Acme\AppBundle\Entity\Customer + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('doctrine', array( + 'orm' => array( + // ... + 'resolve_target_entities' => array( + 'Acme\InvoiceBundle\Model\InvoiceSubjectInterface' => 'Acme\AppBundle\Entity\Customer', + ), + ), + )); + +Final Thoughts +-------------- + +With the ``ResolveTargetEntityListener``, you are able to decouple your +bundles, keeping them usable by themselves, but still being able to +define relationships between different objects. By using this method, +your bundles will end up being easier to maintain independently. diff --git a/cookbook/doctrine/reverse_engineering.rst b/cookbook/doctrine/reverse_engineering.rst index c3ae948f75a..7ac4b458208 100644 --- a/cookbook/doctrine/reverse_engineering.rst +++ b/cookbook/doctrine/reverse_engineering.rst @@ -47,7 +47,7 @@ to a post record thanks to a foreign key constraint. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; Before diving into the recipe, be sure your database connection parameters are -correctly setup in the ``app/config/parameters.ini`` file (or wherever your +correctly setup in the ``app/config/parameters.yml`` file (or wherever your database configuration is kept) and that you have initialized a bundle that will host your future entity class. In this tutorial it's assumed that an ``AcmeBlogBundle`` exists and is located under the ``src/Acme/BlogBundle`` @@ -177,4 +177,4 @@ The last command generated all getters and setters for your two ``BlogPost`` and ``BlogComment`` entity class properties. The generated entities are now ready to be used. Have fun! -.. _`Doctrine tools documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/tools.html#reverse-engineering +.. _`Doctrine tools documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html#reverse-engineering diff --git a/cookbook/email/email.rst b/cookbook/email/email.rst index a1665cf98ec..9718aa67f14 100644 --- a/cookbook/email/email.rst +++ b/cookbook/email/email.rst @@ -88,7 +88,7 @@ The following configuration attributes are available: * ``auth_mode`` (``plain``, ``login``, or ``cram-md5``) * ``spool`` - * ``type`` (how to queue the messages, only ``file`` is supported currently) + * ``type`` (how to queue the messages, ``file`` or ``memory`` is supported, see :doc:`/cookbook/email/spool`) * ``path`` (where to store the messages) * ``delivery_address`` (an email address where to send ALL emails) * ``disable_delivery`` (set to true to disable delivery completely) diff --git a/cookbook/email/spool.rst b/cookbook/email/spool.rst index f059616925b..edea9482730 100644 --- a/cookbook/email/spool.rst +++ b/cookbook/email/spool.rst @@ -12,10 +12,51 @@ page to load while the email is sending. This can be avoided by choosing to "spool" the emails instead of sending them directly. This means that ``Swiftmailer`` does not attempt to send the email but instead saves the message to somewhere such as a file. Another process can then read from the spool and take care -of sending the emails in the spool. Currently only spooling to file is supported +of sending the emails in the spool. Currently only spooling to file or memory is supported by ``Swiftmailer``. -In order to use the spool, use the following configuration: +Spool using memory +------------------ + +When you use spooling to store the emails to memory, they will get sent right +before the kernel terminates. This means the email only gets sent if the whole +request got executed without any unhandled Exception or any errors. To configure +swiftmailer with the memory option, use the following configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + swiftmailer: + # ... + spool: { type: memory } + + .. code-block:: xml + + + + + + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('swiftmailer', array( + ..., + 'spool' => array('type' => 'memory') + )); + +Spool using a file +------------------ + +In order to use the spool with a file, use the following configuration: .. configuration-block:: diff --git a/cookbook/email/testing.rst b/cookbook/email/testing.rst index 9b18d6c6a1e..7386618303d 100644 --- a/cookbook/email/testing.rst +++ b/cookbook/email/testing.rst @@ -41,6 +41,10 @@ to get information about the messages send on the previous request:: public function testMailIsSentAndContentIsOk() { $client = static::createClient(); + + // Enable the profiler for the next request (it does nothing if the profiler is not available) + $client->enableProfiler(); + $crawler = $client->request('POST', '/path/to/above/action'); $mailCollector = $client->getProfile()->getCollector('swiftmailer'); diff --git a/cookbook/form/create_custom_field_type.rst b/cookbook/form/create_custom_field_type.rst index d7b6600d1ee..709d9e3a6f9 100644 --- a/cookbook/form/create_custom_field_type.rst +++ b/cookbook/form/create_custom_field_type.rst @@ -24,21 +24,21 @@ for form fields, which is ``\Form\Type``. Make sure the field extend namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; class GenderType extends AbstractType { - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'choices' => array( 'm' => 'Male', 'f' => 'Female', - ), - ); + ) + )); } - public function getParent(array $options) + public function getParent() { return 'choice'; } @@ -70,17 +70,17 @@ important: set) the ``multiple`` attribute on the ``select`` field. See `Creating a Template for the Field`_ for more details. -* ``getDefaultOptions()`` - This defines options for your form type that +* ``setDefaultOptions()`` - This defines options for your form type that can be used in ``buildForm()`` and ``buildView()``. There are a lot of - options common to all fields (see `FieldType`_), but you can create any - others that you need here. + options common to all fields (see :doc:`/reference/forms/types/form`), + but you can create any others that you need here. .. tip:: If you're creating a field that consists of many fields, then be sure to set your "parent" type as ``form`` or something that extends ``form``. Also, if you need to modify the "view" of any of your child types from - your parent type, use the ``buildViewBottomUp()`` method. + your parent type, use the ``finishView()`` method. The ``getName()`` method returns an identifier which should be unique in your application. This is used in various places, such as when customizing @@ -191,11 +191,11 @@ new instance of the type in one of your forms:: namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; class AuthorType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('gender_code', new GenderType(), array( 'empty_value' => 'Choose a gender', @@ -294,6 +294,10 @@ method to ``GenderType``, which receives the gender configuration:: // src/Acme/DemoBundle/Form/Type/GenderType.php namespace Acme\DemoBundle\Form\Type; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + // ... + // ... class GenderType extends AbstractType { @@ -304,11 +308,11 @@ method to ``GenderType``, which receives the gender configuration:: $this->genderChoices = $genderChoices; } - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'choices' => $this->genderChoices, - ); + )); } // ... @@ -320,11 +324,14 @@ configuration, using the field is now much easier:: // src/Acme/DemoBundle/Form/Type/AuthorType.php namespace Acme\DemoBundle\Form\Type; + + use Symfony\Component\Form\FormBuilderInterface; + // ... class AuthorType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('gender_code', 'gender', array( 'empty_value' => 'Choose a gender', diff --git a/cookbook/form/create_form_type_extension.rst b/cookbook/form/create_form_type_extension.rst index 95983a33b69..e6ac99b922d 100644 --- a/cookbook/form/create_form_type_extension.rst +++ b/cookbook/form/create_form_type_extension.rst @@ -83,11 +83,9 @@ to override one of the following methods: * ``buildView()`` -* ``getDefaultOptions()`` +* ``setDefaultOptions()`` -* ``getAllowedOptionValues()`` - -* ``buildViewBottomUp()`` +* ``finishView()`` For more information on what those methods do, you can refer to the :doc:`Creating Custom Field Types` @@ -180,7 +178,7 @@ database):: Your form type extension class will need to do two things in order to extend the ``file`` form type: -#. Override the ``getDefaultOptions`` method in order to add an image_path +#. Override the ``setDefaultOptions`` method in order to add an image_path option; #. Override the ``buildForm`` and ``buildView`` methods in order to pass the image url to the view. @@ -194,10 +192,10 @@ it in the view:: namespace Acme\DemoBundle\Form\Extension; use Symfony\Component\Form\AbstractTypeExtension; - use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Util\PropertyPath; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; class ImageTypeExtension extends AbstractTypeExtension { @@ -214,43 +212,32 @@ it in the view:: /** * Add the image_path option * - * @param array $options + * @param OptionsResolverInterface $resolver */ - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array('image_path' => null); - } - - /** - * Store the image_path option as a builder attribute - * - * @param FormBuilder $builder - * @param array $options - */ - public function buildForm(FormBuilder $builder, array $options) - { - if (null !== $options['image_path']) { - $builder->setAttribute('image_path', $options['image_path']); - } + $resolver->setOptional(array('image_path')); } /** * Pass the image url to the view * - * @param FormView $view + * @param FormView $view * @param FormInterface $form + * @param array $options */ - public function buildView(FormView $view, FormInterface $form) + public function buildView(FormView $view, FormInterface $form, array $options) { - if ($form->hasAttribute('image_path')) { + if (array_key_exists('image_path', $options)) { $parentData = $form->getParent()->getData(); - if (null != $parentData) { - $propertyPath = new PropertyPath($form->getAttribute('image_path')); + if (null !== $parentData) { + $propertyPath = new PropertyPath($options['image_path']); $imageUrl = $propertyPath->getValue($parentData); } else { - $imageUrl = null; + $imageUrl = null; } + // set an "image_url" variable that will be available when rendering this field $view->set('image_url', $imageUrl); } @@ -279,7 +266,7 @@ Specifically, you need to override the ``file_widget`` block: {% block file_widget %} {% spaceless %} - {{ block('field_widget') }} + {{ block('form_widget') }} {% if image_url is not null %} {% endif %} @@ -313,11 +300,11 @@ next to the file field. For example:: namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; class MediaType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name', 'text') diff --git a/cookbook/form/data_transformers.rst b/cookbook/form/data_transformers.rst index 893ad451bee..5c7ed6c3438 100644 --- a/cookbook/form/data_transformers.rst +++ b/cookbook/form/data_transformers.rst @@ -104,14 +104,15 @@ Now that you have the transformer built, you just need to add it to your issue field in some form. You can also use transformers without creating a new custom form type - by calling ``prependNormTransformer`` (or ``appendClientTransformer`` - see - `Norm and Client Transformers`_) on any field builder:: + by calling ``addModelTransformer`` (or ``addViewTransformer`` - see + `Model and View Transformers`_) on any field builder:: + use Symfony\Component\Form\FormBuilderInterface; use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer; class TaskType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { // ... @@ -122,10 +123,27 @@ issue field in some form. // add a normal text field, but add your transformer to it $builder->add( $builder->create('issue', 'text') - ->prependNormTransformer($transformer) + ->addModelTransformer($transformer) ); } + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Acme\TaskBundle\Entity\Task', + )); + + $resolver->setRequired(array( + 'em', + )); + + $resolver->setAllowedTypes(array( + 'em' => 'Doctrine\Common\Persistence\ObjectManager', + )); + + // ... + } + // ... } @@ -154,51 +172,59 @@ its error message can be controlled with the ``invalid_message`` field option. // THIS IS WRONG - TRANSFORMER WILL BE APPLIED TO THE ENTIRE FORM // see above example for correct code $builder->add('issue', 'text') - ->prependNormTransformer($transformer); + ->addModelTransformer($transformer); + +Model and View Transformers +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Norm and Client Transformers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 2.1 + The names and method of the transformers were changed in Symfony 2.1. + ``prependNormTransformer`` became ``addModelTransformer`` and ``appendClientTransformer`` + became ``addViewTransformer``. -In the above example, the transformer was used as a "norm" transformer. +In the above example, the transformer was used as a "model" transformer. In fact, there are two different type of transformers and three different types of underlying data. +.. image:: /images/cookbook/form/DataTransformersTypes.png + :align: center + In any form, the 3 different types of data are: -1) **App data** - This is the data in the format used in your application -(e.g. an ``Issue`` object). If you call ``Form::getData`` or ``Form::setData``, -you're dealing with the "app" data. +1) **Model data** - This is the data in the format used in your application +(e.g. an ``Issue`` object). If you call ``Form::getData`` or ``Form::setData``, +you're dealing with the "model" data. 2) **Norm Data** - This is a normalized version of your data, and is commonly -the same as your "app" data (though not in this example). It's not commonly +the same as your "model" data (though not in our example). It's not commonly used directly. -3) **Client Data** - This is the format that's used to fill in the form fields +3) **View Data** - This is the format that's used to fill in the form fields themselves. It's also the format in which the user will submit the data. When -you call ``Form::bind($data)``, the ``$data`` is in the "client" data format. +you call ``Form::bind($data)``, the ``$data`` is in the "view" data format. The 2 different types of transformers help convert to and from each of these types of data: -**Norm transformers**: - - ``transform``: "app data" => "norm data" - - ``reverseTransform``: "norm data" => "app data" +**Model transformers**: + - ``transform``: "model data" => "norm data" + - ``reverseTransform``: "norm data" => "model data" -**Client transformers**: - - ``transform``: "norm data" => "client data" - - ``reverseTransform``: "client data" => "norm data" +**View transformers**: + - ``transform``: "norm data" => "view data" + - ``reverseTransform``: "view data" => "norm data" Which transformer you need depends on your situation. -To use the client transformer, call ``appendClientTransformer``. +To use the view transformer, call ``addViewTransformer``. -So why use the norm transformer? --------------------------------- +So why use the model transformer? +--------------------------------- In this example, the field is a ``text`` field, and a text field is always -expected to be a simple, scalar format in the "norm" and "client" formats. For -this reason, the most appropriate transformer was the "norm" transformer -(which converts to/from the *norm* format - string issue number - to the *app* +expected to be a simple, scalar format in the "norm" and "view" formats. For +this reason, the most appropriate transformer was the "model" transformer +(which converts to/from the *norm* format - string issue number - to the *model* format - Issue object). The difference between the transformers is subtle and you should always think @@ -225,9 +251,10 @@ First, create the custom field type class:: namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer; use Doctrine\Common\Persistence\ObjectManager; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; class IssueSelectorType extends AbstractType { @@ -244,20 +271,20 @@ First, create the custom field type class:: $this->om = $om; } - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $transformer = new IssueToNumberTransformer($this->om); - $builder->prependNormTransformer($transformer); + $builder->addModelTransformer($transformer); } - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'invalid_message' => 'The selected issue does not exist', - ); + )); } - public function getParent(array $options) + public function getParent() { return 'text'; } @@ -307,11 +334,11 @@ it's quite easy:: namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; class TaskType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('task') diff --git a/cookbook/form/dynamic_form_modification.rst b/cookbook/form/dynamic_form_modification.rst index 4e676bb717c..43c4602ba97 100644 --- a/cookbook/form/dynamic_form_modification.rst +++ b/cookbook/form/dynamic_form_modification.rst @@ -11,11 +11,11 @@ of what a bare form class looks like:: namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; class ProductType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('name'); $builder->add('price'); @@ -58,16 +58,16 @@ to an Event Subscriber:: namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber; class ProductType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { - $subscriber = new AddNameFieldSubscriber($builder->getFormFactory()); - $builder->addEventSubscriber($subscriber); $builder->add('price'); + + $builder->addEventSubscriber(new AddNameFieldSubscriber()); } public function getName() @@ -76,10 +76,6 @@ to an Event Subscriber:: } } -The event subscriber is passed the FormFactory object in its constructor so -that your new subscriber is capable of creating the form widget once it is -notified of the dispatched event during form creation. - .. _`cookbook-forms-inside-subscriber-class`: Inside the Event Subscriber Class @@ -87,25 +83,23 @@ Inside the Event Subscriber Class The goal is to create a "name" field *only* if the underlying Product object is new (e.g. hasn't been persisted to the database). Based on that, the subscriber -might look like the following:: +might look like the following: + +.. versionadded:: 2.2 + The ability to pass a string into :method:`FormInterface::add` + was added in Symfony 2.2. + +.. code-block:: php // src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php namespace Acme\DemoBundle\Form\EventListener; - use Symfony\Component\Form\Event\DataEvent; - use Symfony\Component\Form\FormFactoryInterface; - use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; class AddNameFieldSubscriber implements EventSubscriberInterface { - private $factory; - - public function __construct(FormFactoryInterface $factory) - { - $this->factory = $factory; - } - public static function getSubscribedEvents() { // Tells the dispatcher that you want to listen on the form.pre_set_data @@ -113,7 +107,7 @@ might look like the following:: return array(FormEvents::PRE_SET_DATA => 'preSetData'); } - public function preSetData(DataEvent $event) + public function preSetData(FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); @@ -129,7 +123,7 @@ might look like the following:: // check if the product object is "new" if (!$data->getId()) { - $form->add($this->factory->createNamed('text', 'name')); + $form->add('name', 'text'); } } } @@ -146,20 +140,399 @@ The ``FormEvents::PRE_SET_DATA`` line actually resolves to the string ``form.pre The `FormEvents class`_ serves an organizational purpose. It is a centralized location in which you can find all of the various form events available. -While this example could have used the ``form.set_data`` event or even the ``form.post_set_data`` -events just as effectively, by using ``form.pre_set_data`` you guarantee that +While this example could have used the ``form.post_set_data`` +event just as effectively, by using ``form.pre_set_data`` you guarantee that the data being retrieved from the ``Event`` object has in no way been modified -by any other subscribers or listeners. This is because ``form.pre_set_data`` -passes a `DataEvent`_ object instead of the `FilterDataEvent`_ object passed -by the ``form.set_data`` event. `DataEvent`_, unlike its child `FilterDataEvent`_, -lacks a setData() method. +by any other subscribers or listeners because ``form.pre_set_data`` is the +first form event dispatched. .. note:: You may view the full list of form events via the `FormEvents class`_, found in the form bundle. +How to Dynamically Generate Forms based on user data +==================================================== + +Sometimes you want a form to be generated dynamically based not only on data +from this form (see :doc:`Dynamic form generation`) +but also on something else. For example depending on the user currently using +the application. If you have a social website where a user can only message +people who are his friends on the website, then the current user doesn't need to +be included as a field of your form, but a "choice list" of whom to message +should only contain users that are the current user's friends. + +Creating the form type +---------------------- + +Using an event listener, our form could be built like this:: + + // src/Acme/DemoBundle/FormType/FriendMessageFormType.php + namespace Acme\DemoBundle\FormType; + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\Form\FormEvents; + use Symfony\Component\Form\FormEvent; + use Symfony\Component\Security\Core\SecurityContext; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + use Acme\DemoBundle\FormSubscriber\UserListener; + + class FriendMessageFormType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('subject', 'text') + ->add('body', 'textarea') + ; + $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){ + // ... add a choice list of friends of the current application user + }); + } + + public function getName() + { + return 'acme_friend_message'; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + } + } + +The problem is now to get the current application user and create a choice field +that would contain only this user's friends. + +Luckily it is pretty easy to inject a service inside of the form. This can be +done in the constructor. + +.. code-block:: php + + private $securityContext; + + public function __construct(SecurityContext $securityContext) + { + $this->securityContext = $securityContext; + } + +.. note:: + + You might wonder, now that we have access to the User (through) the security + context, why don't we just use that inside of the buildForm function and + still use a listener? + This is because doing so in the buildForm method would result in the whole + form type being modified and not only one form instance. + +Customizing the form type +------------------------- + +Now that we have all the basics in place, we can put everything in place and add +our listener:: + + // src/Acme/DemoBundle/FormType/FriendMessageFormType.php + class FriendMessageFormType extends AbstractType + { + private $securityContext; + + public function __construct(SecurityContext $securityContext) + { + $this->securityContext = $securityContext; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('subject', 'text') + ->add('body', 'textarea') + ; + $user = $this->securityContext->getToken()->getUser(); + $factory = $builder->getFormFactory(); + + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + function(FormEvent $event) use($user, $factory){ + $form = $event->getForm(); + $userId = $user->getId(); + + $formOptions = array( + 'class' => 'Acme\DemoBundle\Document\User', + 'multiple' => false, + 'expanded' => false, + 'property' => 'fullName', + 'query_builder' => function(DocumentRepository $dr) use ($userId) { + return $dr->createQueryBuilder()->field('friends.$id')->equals(new \MongoId($userId)); + }, + ); + + $form->add($factory->createNamed('friend', 'document', null, $formOptions)); + } + ); + } + + public function getName() + { + return 'acme_friend_message'; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + } + } + +Using the form +-------------- + +Our form is now ready to use. We have two possible ways to use it inside of a +controller. Either by creating it everytime and remembering to pass the security +context, or by defining it as a service. This is the option we will show here. + +To define your form as a service, you simply add the configuration to your +configuration. + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + acme.form.friend_message: + class: Acme\DemoBundle\FormType\FriendMessageType + arguments: [@security.context] + tags: + - { name: form.type, alias: acme_friend_message} + + .. code-block:: xml + + + + + + + + + + .. code-block:: php + + // app/config/config.php + $definition = new Definition('Acme\DemoBundle\FormType\FriendMessageType'); + $definition->addTag('form.type', array('alias' => 'acme_friend_message')); + $container->setDefinition( + 'acme.form.friend_message', + $definition, + array('security.context') + ); + +By adding the form as a service, we make sure that this form can now be used +simply from anywhere. If you need to add it to another form, you will just need +to use:: + + $builder->add('message', 'acme_friend_message'); + +If you wish to create it from within a controller or any other service that has +access to the form factory, you then use:: + + // src/AcmeDemoBundle/Controller/FriendMessageController.php + public function friendMessageAction() + { + $form = $this->get('form.factory')->create('acme_friend_message'); + $form = $form->createView(); + + return compact('form'); + } + +Dynamic generation for submitted forms +====================================== + +An other case that can appear is that you want to customize the form specific to +the data that was submitted by the user. If we take as an example a registration +form for sports gatherings. Some events will allow you to specify your preferred +position on the field. This would be a choice field for example. However the +possible choices will depend on each sport. Football will have attack, defense, +goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. We +will need the correct options to be set in order for validation to pass. + +The meetup is passed as an entity hidden field to the form. So we can access each +sport like this:: + + // src/Acme/DemoBundle/FormType/SportMeetupType.php + class SportMeetupType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('number_of_people', 'text') + ->add('discount_coupon', 'text') + ; + $factory = $builder->getFormFactory(); + + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + function(FormEvent $event) use($user, $factory){ + $form = $event->getForm(); + $event->getData()->getSport()->getAvailablePositions(); + + // ... proceed with customizing the form based on available positions + } + ); + } + } + + +While generating this kind of form to display it to the user for the first time, +we can just as previously, use a simple listener and all goes fine. + +When considering form submission, things are usually a bit different because +subscribing to PRE_SET_DATA will only return us an empty ``SportMeetup`` object. +That object will then be populated with the data sent by the user when there is a +call to ``$form->bind($request)``. + +On a form, we can usually listen to the following events:: + + * ``PRE_SET_DATA`` + * ``POST_SET_DATA`` + * ``PRE_BIND`` + * ``BIND`` + * ``POST_BIND`` + +When listening to bind and post-bind, it's already "too late" to make changes to +the form. But pre-bind is fine. There is however a big difference in what +``$event->getData()`` will return for each of these events as pre-bind will return +an array instead of an object. This is the raw data submitted by the user. + +This can be used to get the SportMeetup's id and retrieve it from the database, +given we have a reference to our object manager (if using doctrine). So we have +an event subscriber that listens to two different events, requires some +external services and customizes our form. In such a situation, it seems cleaner +to define this as a service rather than use closure like in the previous example. + +Our subscriber would now look like:: + + class RegistrationSportListener implements EventSubscriberInterface + { + /** + * @var FormFactoryInterface + */ + private $factory; + + /** + * @var DocumentManager + */ + private $om; + + /** + * @param factory FormFactoryInterface + */ + public function __construct(FormFactoryInterface $factory, ObjectManager $om) + { + $this->factory = $factory; + $this->om = $om; + } + + public static function getSubscribedEvents() + { + return [ + FormEvents::PRE_BIND => 'preBind', + FormEvents::PRE_SET_DATA => 'preSetData', + ]; + } + + /** + * @param event DataEvent + */ + public function preSetData(DataEvent $event) + { + $meetup = $event->getData()->getMeetup(); + + // Before binding the form, the "meetup" will be null + if (null === $meetup) { + return; + } + + $form = $event->getForm(); + $positions = $meetup->getSport()->getPostions(); + + $this->customizeForm($form, $positions); + } + + public function preBind(DataEvent $event) + { + $data = $event->getData(); + $id = $data['event']; + $meetup = $this->om + ->getRepository('Acme\SportBundle\Document\Event') + ->find($id); + if($meetup === null){ + $msg = 'The event %s could not be found for you registration'; + throw new \Exception(sprintf($msg, $id)); + } + $form = $event->getForm(); + $positions = $meetup->getSport()->getPositions(); + + $this->customizeForm($form, $positions); + } + + protected function customizeForm($form, $positions) + { + // ... customize the form according to the positions + } + } + +We can see that we need to listen on these two events and have different callbacks +only because in two different scenarios, the data that we can use is given in a +different format. Other than that, this class always performs exactly the same +things on a given form. + +Now that we have this set up, we need to create our services: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + acme.form.sport_meetup: + class: Acme\SportBundle\FormType\RegistrationType + arguments: [@acme.form.meetup_registration_listener] + tags: + - { name: form.type, alias: acme_meetup_registration } + acme.form.meetup_registration_listener + class: Acme\SportBundle\Form\RegistrationSportListener + arguments: [@form.factory, @doctrine] + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // app/config/config.php + $definition = new Definition('Acme\SportBundle\FormType\RegistrationType'); + $definition->addTag('form.type', array('alias' => 'acme_meetup_registration')); + $container->setDefinition( + 'acme.form.meetup_registration_listener', + $definition, + array('security.context') + ); + $definition = new Definition('Acme\SportBundle\Form\RegistrationSportListener'); + $container->setDefinition( + 'acme.form.meetup_registration_listener', + $definition, + array('form.factory', 'doctrine') + ); + +And this should tie everything together. We can now retrieve our form from the +controller, display it to a user, and validate it with the right choice options +set for every possible kind of sport that our users are registering for. + .. _`DataEvent`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php .. _`FormEvents class`: https://github.com/symfony/Form/blob/master/FormEvents.php .. _`Form class`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php -.. _`FilterDataEvent`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/FilterDataEvent.php diff --git a/cookbook/form/form_collections.rst b/cookbook/form/form_collections.rst index f451904edb6..d224635ccee 100755 --- a/cookbook/form/form_collections.rst +++ b/cookbook/form/form_collections.rst @@ -89,20 +89,21 @@ can be modified by the user:: namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TagType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('name'); } - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Tag', - ); + )); } public function getName() @@ -122,22 +123,23 @@ Notice that you embed a collection of ``TagType`` forms using the namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TaskType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('description'); $builder->add('tags', 'collection', array('type' => new TagType())); } - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Task', - ); + )); } public function getName() @@ -176,8 +178,8 @@ In your controller, you'll now initialize a new instance of ``TaskType``:: $form = $this->createForm(new TaskType(), $task); // process the form on POST - if ('POST' === $request->getMethod()) { - $form->bindRequest($request); + if ($request->isMethod('POST')) { + $form->bind($request); if ($form->isValid()) { // ... maybe do some form processing, like saving the Task and Tag objects } @@ -284,8 +286,10 @@ add the ``allow_add`` option to your collection field:: // src/Acme/TaskBundle/Form/Type/TaskType.php // ... + + use Symfony\Component\Form\FormBuilderInterface; - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('description'); @@ -317,7 +321,7 @@ new "tag" forms. To render it, make the following change to your template: .. code-block:: html+php -
    +
      ...
    @@ -343,7 +347,7 @@ On the rendered page, the result will look something like this: .. code-block:: html -
      +
        The goal of this section will be to use JavaScript to read this attribute and dynamically add new tag forms when the user clicks a "Add a tag" link. @@ -384,10 +388,13 @@ will be show next): The ``addTagForm`` function's job will be to use the ``data-prototype`` attribute to dynamically add a new form when this link is clicked. The ``data-prototype`` -HTML contains the tag ``text`` input element with a name of ``task[tags][$$name$$][name]`` -and id of ``task_tags_$$name$$_name``. The ``$$name`` is a little "placeholder", +HTML contains the tag ``text`` input element with a name of ``task[tags][__name__][name]`` +and id of ``task_tags___name___name``. The ``__name__`` is a little "placeholder", which you'll replace with a unique, incrementing number (e.g. ``task[tags][3][name]``). +.. versionadded:: 2.1 + The placeholder was changed from ``$$name$$`` to ``__name__`` in Symfony 2.1 + The actual code needed to make this all work can vary quite a bit, but here's one example: @@ -400,9 +407,9 @@ one example: // get the new index var index = collectionHolder.data('index'); - // Replace '$$name$$' in the prototype's HTML to + // Replace '__name__' in the prototype's HTML to // instead be a number based on how many items we have - var newForm = prototype.replace(/\$\$name\$\$/g, index); + var newForm = prototype.replace(/__name__/g, index); // increase the index with one for the next item collectionHolder.data('index', index + 1); @@ -531,8 +538,9 @@ Start by adding the ``allow_delete`` option in the form Type:: // src/Acme/TaskBundle/Form/Type/TaskType.php // ... + use Symfony\Component\Form\FormBuilderInterface; - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('description'); @@ -618,7 +626,7 @@ the relationship between the removed ``Tag`` and ``Task`` object. public function editAction($id, Request $request) { - $em = $this->getDoctrine()->getEntityManager(); + $em = $this->getDoctrine()->getManager(); $task = $em->getRepository('AcmeTaskBundle:Task')->find($id); if (!$task) { @@ -634,8 +642,8 @@ the relationship between the removed ``Tag`` and ``Task`` object. $editForm = $this->createForm(new TaskType(), $task); - if ('POST' === $request->getMethod()) { - $editForm->bindRequest($this->getRequest()); + if ($request->isMethod('POST')) { + $editForm->bind($this->getRequest()); if ($editForm->isValid()) { diff --git a/cookbook/form/form_customization.rst b/cookbook/form/form_customization.rst index 0b6b3923c01..eb41d37ec1f 100644 --- a/cookbook/form/form_customization.rst +++ b/cookbook/form/form_customization.rst @@ -83,8 +83,8 @@ What are Form Themes? --------------------- Symfony uses form fragments - a small piece of a template that renders just -one part of a form - to render every part of a form - - field labels, errors, -``input`` text fields, ``select`` tags, etc +one part of a form - to render each part of a form - field labels, errors, +``input`` text fields, ``select`` tags, etc. The fragments are defined as blocks in Twig and as template files in PHP. @@ -100,7 +100,7 @@ to render every part of a form. In the next section you will learn how to customize a theme by overriding some or all of its fragments. -For example, when the widget of a ``integer`` type field is rendered, an ``input`` +For example, when the widget of an ``integer`` type field is rendered, an ``input`` ``number`` field is generated .. configuration-block:: @@ -119,15 +119,15 @@ renders: -Internally, Symfony uses the ``integer_widget`` fragment to render the field. +Internally, Symfony uses the ``integer_widget`` fragment to render the field. This is because the field type is ``integer`` and you're rendering its ``widget`` (as opposed to its ``label`` or ``errors``). In Twig that would default to the block ``integer_widget`` from the `form_div_layout.html.twig`_ template. -In PHP it would rather be the ``integer_widget.html.php`` file located in ``FrameworkBundle/Resources/views/Form`` -folder. +In PHP it would rather be the ``integer_widget.html.php`` file located in +the ``FrameworkBundle/Resources/views/Form`` folder. The default implementation of the ``integer_widget`` fragment looks like this: @@ -138,33 +138,33 @@ The default implementation of the ``integer_widget`` fragment looks like this: {# form_div_layout.html.twig #} {% block integer_widget %} {% set type = type|default('number') %} - {{ block('field_widget') }} + {{ block('form_widget_simple') }} {% endblock integer_widget %} .. code-block:: html+php - renderBlock('field_widget', array('type' => isset($type) ? $type : "number")) ?> + block($form, 'form_widget_simple', array('type' => isset($type) ? $type : "number")) ?> -As you can see, this fragment itself renders another fragment - ``field_widget``: +As you can see, this fragment itself renders another fragment - ``form_widget_simple``: .. configuration-block:: .. code-block:: html+jinja {# form_div_layout.html.twig #} - {% block field_widget %} + {% block form_widget_simple %} {% set type = type|default('text') %} - - {% endblock field_widget %} + + {% endblock form_widget_simple %} .. code-block:: html+php - + " - value="escape($value) ?>" - renderBlock('attributes') ?> + type="escape($type) : 'text' ?>" + value="escape($value) ?>" + block($form, 'widget_attributes') ?> /> The point is, the fragments dictate the HTML output of each part of a form. To @@ -192,11 +192,11 @@ this folder. just input ``text`` fields, you should customize the ``text_errors`` fragment. More commonly, however, you'll want to customize how errors are displayed - across *all* fields. You can do this by customizing the ``field_errors`` + across *all* fields. You can do this by customizing the ``form_errors`` fragment. This takes advantage of field type inheritance. Specifically, - since the ``text`` type extends from the ``field`` type, the form component + since the ``text`` type extends from the ``form`` type, the form component will first look for the type-specific fragment (e.g. ``text_errors``) before - falling back to its parent fragment name if it doesn't exist (e.g. ``field_errors``). + falling back to its parent fragment name if it doesn't exist (e.g. ``form_errors``). For more information on this topic, see :ref:`form-template-blocks`. @@ -242,7 +242,7 @@ directly in the template that's actually rendering the form. {% block integer_widget %}
        {% set type = type|default('number') %} - {{ block('field_widget') }} + {{ block('form_widget_simple') }}
        {% endblock %} @@ -278,7 +278,7 @@ can now re-use the form customization across many templates: {% block integer_widget %}
        {% set type = type|default('number') %} - {{ block('field_widget') }} + {{ block('form_widget_simple') }}
        {% endblock %} @@ -314,7 +314,7 @@ file in order to customize the ``integer_widget`` fragment.
        - renderBlock('field_widget', array('type' => isset($type) ? $type : "number")) ?> + block($form, 'form_widget_simple', array('type' => isset($type) ? $type : "number")) ?>
        Now that you've created the customized form template, you need to tell Symfony @@ -608,7 +608,7 @@ which part of the field is being customized. For example: {% block _product_name_widget %}
        - {{ block('field_widget') }} + {{ block('form_widget_simple') }}
        {% endblock %} @@ -624,7 +624,7 @@ which part of the field is being customized. For example:
        - echo $view['form']->renderBlock('field_widget') ?> + echo $view['form']->block('form_widget_simple') ?>
        Here, the ``_product_name_widget`` fragment defines the template to use for the @@ -708,36 +708,32 @@ By default, the errors are rendered inside an unordered list:
      To override how errors are rendered for *all* fields, simply copy, paste -and customize the ``field_errors`` fragment. +and customize the ``form_errors`` fragment. .. configuration-block:: .. code-block:: html+jinja - {# fields_errors.html.twig #} - {% block field_errors %} + {# form_errors.html.twig #} + {% block form_errors %} {% spaceless %} {% if errors|length > 0 %} -
        +
          {% for error in errors %} -
        • {{ error.messageTemplate|trans(error.messageParameters, 'validators') }}
        • +
        • {{ error.message }}
        • {% endfor %}
        {% endif %} {% endspaceless %} - {% endblock field_errors %} + {% endblock form_errors %} .. code-block:: html+php - + -
          +
            -
          • trans( - $error->getMessageTemplate(), - $error->getMessageParameters(), - 'validators' - ) ?>
          • +
          • getMessage() ?>
          @@ -763,7 +759,7 @@ to just one field) are rendered separately, usually at the top of your form: To customize *only* the markup used for these errors, follow the same directions as above, but now call the block ``form_errors`` (Twig) / the file ``form_errors.html.php`` (PHP). Now, when errors for the ``form`` type are rendered, your customized -fragment will be used instead of the default ``field_errors``. +fragment will be used instead of the default ``form_errors``. Customizing the "Form Row" ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -771,25 +767,25 @@ Customizing the "Form Row" When you can manage it, the easiest way to render a form field is via the ``form_row`` function, which renders the label, errors and HTML widget of a field. To customize the markup used for rendering *all* form field rows, -override the ``field_row`` fragment. For example, suppose you want to add a +override the ``form_row`` fragment. For example, suppose you want to add a class to the ``div`` element around each row: .. configuration-block:: .. code-block:: html+jinja - {# field_row.html.twig #} - {% block field_row %} + {# form_row.html.twig #} + {% block form_row %}
          {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }}
          - {% endblock field_row %} + {% endblock form_row %} .. code-block:: html+php - +
          label($form) ?> errors($form) ?> @@ -804,17 +800,17 @@ Adding a "Required" Asterisk to Field Labels ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want to denote all of your required fields with a required asterisk (``*``), -you can do this by customizing the ``field_label`` fragment. +you can do this by customizing the ``form_label`` fragment. In Twig, if you're making the form customization inside the same template as your form, modify the ``use`` tag and add the following: .. code-block:: html+jinja - {% use 'form_div_layout.html.twig' with field_label as base_field_label %} + {% use 'form_div_layout.html.twig' with form_label as base_form_label %} - {% block field_label %} - {{ block('base_field_label') }} + {% block form_label %} + {{ block('base_form_label') }} {% if required %} * @@ -828,7 +824,7 @@ the following: {% extends 'form_div_layout.html.twig' %} - {% block field_label %} + {% block form_label %} {{ parent() }} {% if required %} @@ -841,10 +837,13 @@ original template: .. code-block:: html+php - + - + + + humanize($name); } ?> + @@ -865,10 +864,10 @@ form, modify the ``use`` tag and add the following: .. code-block:: html+jinja - {% use 'form_div_layout.html.twig' with field_widget as base_field_widget %} + {% use 'form_div_layout.html.twig' with form_widget_simple as base_form_widget_simple %} - {% block field_widget %} - {{ block('base_field_widget') }} + {% block form_widget_simple %} + {{ block('base_form_widget_simple') }} {% if help is defined %} {{ help }} @@ -882,7 +881,7 @@ the following: {% extends 'form_div_layout.html.twig' %} - {% block field_widget %} + {% block form_widget_simple %} {{ parent() }} {% if help is defined %} @@ -895,13 +894,13 @@ original template: .. code-block:: html+php - + " - value="escape($value) ?>" - renderBlock('attributes') ?> + type="escape($type) : 'text' ?>" + value="escape($value) ?>" + block($form, 'widget_attributes') ?> /> @@ -951,4 +950,4 @@ customizations directly. Look at the following example: The array passed as the second argument contains form "variables". For more details about this concept in Twig, see :ref:`twig-reference-form-variables`. -.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.0/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.2/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig diff --git a/cookbook/form/index.rst b/cookbook/form/index.rst index 337f411dd09..114e26a8d96 100644 --- a/cookbook/form/index.rst +++ b/cookbook/form/index.rst @@ -11,3 +11,4 @@ Form create_custom_field_type create_form_type_extension use_virtuals_forms + unit_testing diff --git a/cookbook/form/unit_testing.rst b/cookbook/form/unit_testing.rst new file mode 100644 index 00000000000..b3cca724fc6 --- /dev/null +++ b/cookbook/form/unit_testing.rst @@ -0,0 +1,246 @@ +.. index:: + single: Form; Form testing + +How to Unit Test your Forms +=========================== + +The Form Component consists of 3 core objects: a form type (implementing +:class:`Symfony\\Component\\Form\\FormTypeInterface`), the +:class:`Symfony\\Component\\Form\\Form` and the +:class:`Symfony\\Component\\Form\\FormView`. + +The only class that is usually manipulated by programmers is the form type class +which serves as a form blueprint. It is used to generate the ``Form`` and the +``FormView``. You could test it directly by mocking its interactions with the +factory but it would be complex. It is better to pass it to FormFactory like it +is done in a real application. It is simple to bootstrap and you can trust +the Symfony components enough to use them as a testing base. + +There is already a class that you can benefit from for simple FormTypes +testing: :class:`Symfony\\Component\\Form\\Tests\\Extension\\Core\\Type\\TypeTestCase`. +It is used to test the core types and you can use it to test your types too. + +.. note:: + + Depending on the way you installed your Symfony or Symfony Form Component + the tests may not be downloaded. Use the --prefer-source option with + composer if this is the case. + +The Basics +---------- + +The simplest ``TypeTestCase`` implementation looks like the following:: + + // src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php + namespace Acme\TestBundle\Tests\Form\Type; + + use Acme\TestBundle\Form\Type\TestedType; + use Acme\TestBundle\Model\TestObject; + use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase; + + class TestedTypeTest extends TypeTestCase + { + public function testBindValidData() + { + $formData = array( + 'test' => 'test', + 'test2' => 'test2', + ); + + $type = new TestedType(); + $form = $this->factory->create($type); + + $object = new TestObject(); + $object->fromArray($formData); + + $form->bind($formData); + + $this->assertTrue($form->isSynchronized()); + $this->assertEquals($object, $form->getData()); + + $view = $form->createView(); + $children = $view->children; + + foreach (array_keys($formData) as $key) { + $this->assertArrayHasKey($key, $children); + } + } + } + +So, what does it test? Let's explain it line by line. + +First you verify if the ``FormType`` compiles. This includes basic class +inheritance, the ``buildForm`` function and options resolution. This should +be the first test you write:: + + $type = new TestedType(); + $form = $this->factory->create($type); + +This test checks that none of your data transformers used by the form +failed. The :method:`Symfony\\Component\\Form\\FormInterface::isSynchronized`` +method is only set to ``false`` if a data transformer throws an exception:: + + $form->bind($formData); + $this->assertTrue($form->isSynchronized()); + +.. note:: + + Don't test the validation: it is applied by a listener that is not + active in the test case and it relies on validation configuration. + Instead, unit test your custom constraints directly. + +Next, verify the binding and mapping of the form. The test below +checks if all the fields are correctly specified:: + + $this->assertEquals($object, $form->getData()); + +Finally, check the creation of the ``FormView``. You should check if all +widgets you want to display are available in the children property:: + + $view = $form->createView(); + $children = $view->children; + + foreach (array_keys($formData) as $key) { + $this->assertArrayHasKey($key, $children); + } + +Adding a Type your Form depends on +---------------------------------- + +Your form may depend on other types that are defined as services. It +might look like this:: + + // src/Acme/TestBundle/Form/Type/TestedType.php + + // ... the buildForm method + $builder->add('acme_test_child_type'); + +To create your form correctly, you need to make the type available to the +form factory in your test. The easiest way is to register it manually +before creating the parent form:: + + // src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php + namespace Acme\TestBundle\Tests\Form\Type; + + use Acme\TestBundle\Form\Type\TestedType; + use Acme\TestBundle\Model\TestObject; + use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase; + + class TestedTypeTest extends TypeTestCase + { + public function testBindValidData() + { + $this->factory->addType(new TestChildType()); + + $type = new TestedType(); + $form = $this->factory->create($type); + + // ... your test + } + } + +.. caution:: + + Make sure the child type you add is well tested. Otherwise you may + be getting errors that are not related to the form you are currently + testing but to its children. + +Adding custom Extensions +------------------------ + +It often happens that you use some options that are added by +:doc:`form extensions`. One of the +cases may be the ``ValidatorExtension`` with its ``invalid_message`` option. +The ``TypeTestCase`` loads only the core form extension so an "Invalid option" +exception will be raised if you try to use it for testing a class that depends +on other extensions. You need add those extensions to the factory object:: + + // src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php + namespace Acme\TestBundle\Tests\Form\Type; + + use Acme\TestBundle\Form\Type\TestedType; + use Acme\TestBundle\Model\TestObject; + use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase; + + class TestedTypeTest extends TypeTestCase + { + protected function setUp() + { + parent::setUp(); + + $this->factory = Forms::createFormFactoryBuilder() + ->addTypeExtension( + new FormTypeValidatorExtension( + $this->getMock('Symfony\Component\Validator\ValidatorInterface') + ) + ) + ->addTypeGuesser( + $this->getMockBuilder( + 'Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser' + ) + ->disableOriginalConstructor() + ->getMock() + ) + ->getFormFactory(); + + $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory); + } + + // ... your tests + } + +Testing against different Sets of Data +-------------------------------------- + +If you are not familiar yet with PHPUnit's `data providers`_, this might be +a good opportunity to use them:: + + // src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php + namespace Acme\TestBundle\Tests\Form\Type; + + use Acme\TestBundle\Form\Type\TestedType; + use Acme\TestBundle\Model\TestObject; + use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase; + + class TestedTypeTest extends TypeTestCase + { + + /** + * @dataProvider getValidTestData + */ + public function testForm($data) + { + // ... your test + } + + public function getValidTestData() + { + return array( + array( + 'data' => array( + 'test' => 'test', + 'test2' => 'test2', + ), + ), + array( + 'data' => array(), + ), + array( + 'data' => array( + 'test' => null, + 'test2' => null, + ), + ), + ); + } + } + +The code above will run your test three times with 3 different sets of +data. This allows for decoupling the test fixtures from the tests and +easily testing against multiple sets of data. + +You can also pass another argument, such as a boolean if the form has to +be synchronized with the given set of data or not etc. + +.. _`data providers`: http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers diff --git a/cookbook/form/use_virtuals_forms.rst b/cookbook/form/use_virtuals_forms.rst index 7a1c1ad181a..1c1d1b96bb5 100644 --- a/cookbook/form/use_virtuals_forms.rst +++ b/cookbook/form/use_virtuals_forms.rst @@ -53,9 +53,12 @@ Start by creating a very simple ``CompanyType`` and ``CustomerType``:: use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + class CompanyType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name', 'text') @@ -68,12 +71,12 @@ Start by creating a very simple ``CompanyType`` and ``CustomerType``:: // src/Acme/HelloBundle/Form/Type/CustomerType.php namespace Acme\HelloBundle\Form\Type; + use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; class CustomerType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('firstName', 'text') @@ -88,11 +91,12 @@ location form type:: namespace Acme\HelloBundle\Form\Type; use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; class LocationType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('address', 'textarea') @@ -101,11 +105,11 @@ location form type:: ->add('country', 'text'); } - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( - 'virtual' => true, - ); + $resolver->setDefaults(array( + 'virtual' => true + )); } public function getName() @@ -120,23 +124,27 @@ But you absolutely want to have a dedicated form type to deal with location (rem The ``virtual`` form field option is the solution. -You can set the option ``'virtual' => true`` in the ``getDefaultOptions`` method +You can set the option ``'virtual' => true`` in the ``setDefaultOptions()`` method of ``LocationType`` and directly start using it in the two original form types. Look at the result:: // CompanyType - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->add('foo', new LocationType()); + $builder->add('foo', new LocationType(), array( + 'data_class' => 'Acme\HelloBundle\Entity\Company' + )); } .. code-block:: php // CustomerType - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->add('bar', new LocationType()); + $builder->add('bar', new LocationType(), array( + 'data_class' => 'Acme\HelloBundle\Entity\Customer' + )); } With the virtual option set to false (default behavior), the Form Component diff --git a/cookbook/index.rst b/cookbook/index.rst index b2e6f87de0f..8131c58898a 100644 --- a/cookbook/index.rst +++ b/cookbook/index.rst @@ -12,6 +12,7 @@ The Cookbook form/index validation/index configuration/index + serializer service_container/index bundles/index email/index diff --git a/cookbook/logging/channels_handlers.rst b/cookbook/logging/channels_handlers.rst new file mode 100644 index 00000000000..08161091004 --- /dev/null +++ b/cookbook/logging/channels_handlers.rst @@ -0,0 +1,98 @@ +.. index:: + single: Logging + +How to log Messages to different Files +====================================== + +.. versionadded:: 2.1 + The ability to specify channels for a specific handler was added to + the MonologBundle for Symfony 2.1. + +The Symfony Standard Edition contains a bunch of channels for logging: ``doctrine``, +``event``, ``security`` and ``request``. Each channel corresponds to a logger +service (``monolog.logger.XXX``) in the container and is injected to the +concerned service. The purpose of channels is to be able to organize different +types of log messages. + +By default, Symfony2 logs every messages into a single file (regardless of +the channel). + +Switching a Channel to a different Handler +------------------------------------------ + +Now, suppose you want to log the ``doctrine`` channel to a different file. + +To do so, just create a new handler and configure it like this: + +.. configuration-block:: + + .. code-block:: yaml + + monolog: + handlers: + main: + type: stream + path: /var/log/symfony.log + channels: !doctrine + doctrine: + type: stream + path: /var/log/doctrine.log + channels: doctrine + + .. code-block:: xml + + + + + + exclusive + doctrine + + + + + + inclusive + doctrine + + + + + +Yaml specification +------------------ + +You can specify the configuration by many forms: + +.. code-block:: yaml + + channels: ~ # Include all the channels + + channels: foo # Include only channel "foo" + channels: !foo # Include all channels, except "foo" + + channels: [foo, bar] # Include only channels "foo" and "bar" + channels: [!foo, !bar] # Include all channels, except "foo" and "bar" + + channels: + type: inclusive # Include only those listed below + elements: [ foo, bar ] + channels: + type: exclusive # Include all, except those listed below + elements: [ foo, bar ] + +Creating your own Channel +------------------------- + +You can change the channel monolog logs to one service at a time. This is done +by tagging your service with ``monolog.logger`` and specifying which channel +the service should log to. By doing this, the logger that is injected into +that service is preconfigured to use the channel you've specified. + +For more information - including a full example - read ":ref:`dic_tags-monolog`" +in the Dependency Injection Tags reference section. + +Learn more from the Cookbook +---------------------------- + +* :doc:`/cookbook/logging/monolog` diff --git a/cookbook/logging/index.rst b/cookbook/logging/index.rst index 3f0439d9f27..c10cfa54716 100644 --- a/cookbook/logging/index.rst +++ b/cookbook/logging/index.rst @@ -6,3 +6,4 @@ Logging monolog monolog_email + channels_handlers \ No newline at end of file diff --git a/cookbook/logging/monolog.rst b/cookbook/logging/monolog.rst index 9c837db0dde..9ddef6f33e7 100644 --- a/cookbook/logging/monolog.rst +++ b/cookbook/logging/monolog.rst @@ -239,7 +239,7 @@ using a processor. namespace Acme\MyBundle; - use Symfony\Component\HttpFoundation\Session; + use Symfony\Component\HttpFoundation\Session\Session; class SessionRequestProcessor { @@ -267,10 +267,12 @@ using a processor. } } + .. configuration-block:: .. code-block:: yaml + # app/config/config.yml services: monolog.formatter.session_request: class: Monolog\Formatter\LineFormatter diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index bf372309ff9..569d3615900 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -12,6 +12,7 @@ * :doc:`/cookbook/bundles/override` * :doc:`/cookbook/bundles/remove` * :doc:`/cookbook/bundles/extension` + * :doc:`/cookbook/bundles/prepend_extension` * :doc:`/cookbook/cache/index` @@ -54,6 +55,7 @@ * :doc:`/cookbook/doctrine/reverse_engineering` * :doc:`/cookbook/doctrine/multiple_entity_managers` * :doc:`/cookbook/doctrine/custom_dql_functions` + * :doc:`/cookbook/doctrine/resolve_target_entity` * :doc:`/cookbook/doctrine/registration_form` * :doc:`/cookbook/email/index` @@ -80,6 +82,7 @@ * :doc:`/cookbook/form/create_custom_field_type` * :doc:`/cookbook/form/create_form_type_extension` * :doc:`/cookbook/form/use_virtuals_forms` + * :doc:`/cookbook/form/unit_testing` * (validation) :doc:`/cookbook/validation/custom_constraint` * (doctrine) :doc:`/cookbook/doctrine/file_uploads` @@ -87,6 +90,7 @@ * :doc:`/cookbook/logging/monolog` * :doc:`/cookbook/logging/monolog_email` + * :doc:`/cookbook/logging/channels_handlers` * :doc:`/cookbook/profiler/index` @@ -102,6 +106,7 @@ * :doc:`/cookbook/routing/slash_in_parameter` * :doc:`/cookbook/routing/redirect_in_config` * :doc:`/cookbook/routing/method_parameters` + * :doc:`/cookbook/routing/service_container_parameters` * :doc:`/cookbook/security/index` @@ -115,6 +120,11 @@ * :doc:`/cookbook/security/securing_services` * :doc:`/cookbook/security/custom_provider` * :doc:`/cookbook/security/custom_authentication_provider` + * :doc:`/cookbook/security/target_path` + +* **Serializer** + + * :doc:`/cookbook/serializer` * :doc:`/cookbook/service_container/index` @@ -129,6 +139,7 @@ * :doc:`/cookbook/templating/index` * :doc:`/cookbook/templating/global_variables` + * :doc:`/cookbook/templating/namespaced_paths` * :doc:`/cookbook/templating/PHP` * :doc:`/cookbook/templating/twig_extension` * :doc:`/cookbook/templating/render_without_controller` @@ -154,3 +165,4 @@ * :doc:`/cookbook/workflow/new_project_git` * :doc:`/cookbook/workflow/new_project_svn` + diff --git a/cookbook/routing/index.rst b/cookbook/routing/index.rst index 7c96c4f40a3..e1d0e7e4063 100644 --- a/cookbook/routing/index.rst +++ b/cookbook/routing/index.rst @@ -8,3 +8,4 @@ Routing slash_in_parameter redirect_in_config method_parameters + service_container_parameters diff --git a/cookbook/routing/method_parameters.rst b/cookbook/routing/method_parameters.rst index 26a614a121f..f0360e7e87f 100644 --- a/cookbook/routing/method_parameters.rst +++ b/cookbook/routing/method_parameters.rst @@ -1,37 +1,34 @@ .. index:: - single: Routing; _method + single: Routing; methods How to use HTTP Methods beyond GET and POST in Routes ===================================================== The HTTP method of a request is one of the requirements that can be checked when seeing if it matches a route. This is introduced in the routing chapter -of the book ":doc:`/book/routing`" with examples using GET and POST. You -can also use other HTTP verbs in this way. For example, if you have a blog -post entry then you could use the same URL pattern to show it, make changes -to it and delete it by matching on GET, PUT and DELETE. +of the book ":doc:`/book/routing`" with examples using GET and POST. You can +also use other HTTP verbs in this way. For example, if you have a blog post +entry then you could use the same URL path to show it, make changes to it and +delete it by matching on GET, PUT and DELETE. .. configuration-block:: .. code-block:: yaml blog_show: - pattern: /blog/{slug} + path: /blog/{slug} defaults: { _controller: AcmeDemoBundle:Blog:show } - requirements: - _method: GET + methods: [GET] blog_update: - pattern: /blog/{slug} + path: /blog/{slug} defaults: { _controller: AcmeDemoBundle:Blog:update } - requirements: - _method: PUT + methods: [PUT] blog_delete: - pattern: /blog/{slug} + path: /blog/{slug} defaults: { _controller: AcmeDemoBundle:Blog:delete } - requirements: - _method: DELETE + methods: [DELETE] .. code-block:: xml @@ -41,19 +38,16 @@ to it and delete it by matching on GET, PUT and DELETE. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeDemoBundle:Blog:show - GET - + AcmeDemoBundle:Blog:update - PUT - + AcmeDemoBundle:Blog:delete - DELETE @@ -65,24 +59,29 @@ to it and delete it by matching on GET, PUT and DELETE. $collection = new RouteCollection(); $collection->add('blog_show', new Route('/blog/{slug}', array( '_controller' => 'AcmeDemoBundle:Blog:show', - ), array( - '_method' => 'GET', - ))); + ), array(), array(), '', array(), array('GET'))); $collection->add('blog_update', new Route('/blog/{slug}', array( '_controller' => 'AcmeDemoBundle:Blog:update', - ), array( - '_method' => 'PUT', - ))); + ), array(), array(), '', array(), array('PUT'))); $collection->add('blog_delete', new Route('/blog/{slug}', array( '_controller' => 'AcmeDemoBundle:Blog:delete', - ), array( - '_method' => 'DELETE', - ))); + ), array(), array(), '', array('DELETE'))); return $collection; +Faking the Method with _method +------------------------------ + +.. note:: + + The ``_method`` functionality shown here is disabled by default in Symfony 2.2 + and enabled by default in Symfony 2.3. To control it in Symfony 2.2, you + must call :method:`Request::enableHttpMethodParameterOverride ` + before you handle the request (e.g. in your front controller). In Symfony + 2.3, use the :ref:`configuration-framework-http_method_override` option. + Unfortunately, life isn't quite this simple, since most browsers do not support sending PUT and DELETE requests. Fortunately Symfony2 provides you with a simple way of working around this limitation. By including a ``_method`` diff --git a/cookbook/routing/redirect_in_config.rst b/cookbook/routing/redirect_in_config.rst index 8b0945a0a06..4d6b7004e6e 100644 --- a/cookbook/routing/redirect_in_config.rst +++ b/cookbook/routing/redirect_in_config.rst @@ -20,7 +20,7 @@ Your configuration will look like this: prefix: /app root: - pattern: / + path: / defaults: _controller: FrameworkBundle:Redirect:urlRedirect path: /app @@ -37,4 +37,4 @@ for redirecting request: parameter with the *name* of the route you want to redirect to. The ``permanent`` switch tells both methods to issue a 301 HTTP status code -instead of the default ``302`` status code. \ No newline at end of file +instead of the default ``302`` status code. diff --git a/cookbook/routing/scheme.rst b/cookbook/routing/scheme.rst index ea4d3dfb90d..9786f7a0a27 100644 --- a/cookbook/routing/scheme.rst +++ b/cookbook/routing/scheme.rst @@ -6,17 +6,16 @@ How to force routes to always use HTTPS or HTTP Sometimes, you want to secure some routes and be sure that they are always accessed via the HTTPS protocol. The Routing component allows you to enforce -the URI scheme via the ``_scheme`` requirement: +the URI scheme via schemes: .. configuration-block:: .. code-block:: yaml secure: - pattern: /secure + path: /secure defaults: { _controller: AcmeDemoBundle:Main:secure } - requirements: - _scheme: https + schemes: [https] .. code-block:: xml @@ -26,9 +25,8 @@ the URI scheme via the ``_scheme`` requirement: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeDemoBundle:Main:secure - https @@ -40,9 +38,7 @@ the URI scheme via the ``_scheme`` requirement: $collection = new RouteCollection(); $collection->add('secure', new Route('/secure', array( '_controller' => 'AcmeDemoBundle:Main:secure', - ), array( - '_scheme' => 'https', - ))); + ), array(), array(), '', array('https'))); return $collection; @@ -65,8 +61,8 @@ The requirement is also enforced for incoming requests. If you try to access the ``/secure`` path with HTTP, you will automatically be redirected to the same URL, but with the HTTPS scheme. -The above example uses ``https`` for the ``_scheme``, but you can also force a -URL to always use ``http``. +The above example uses ``https`` for the scheme, but you can also force a URL +to always use ``http``. .. note:: diff --git a/cookbook/routing/service_container_parameters.rst b/cookbook/routing/service_container_parameters.rst new file mode 100644 index 00000000000..8b6d100e264 --- /dev/null +++ b/cookbook/routing/service_container_parameters.rst @@ -0,0 +1,121 @@ +.. index:: + single: Routing; Service Container Parameters + +How to use Service Container Parameters in your Routes +====================================================== + +.. versionadded:: 2.1 + The ability to use parameters in your routes was added in Symfony 2.1. + +Sometimes you may find it useful to make some parts of your routes +globally configurable. For instance, if you build an internationalized +site, you'll probably start with one or two locales. Surely you'll +add a requirement to your routes to prevent a user from matching a locale +other than the locales your support. + +You *could* hardcode your ``_locale`` requirement in all your routes. But +a better solution is to use a configurable service container parameter right +inside your routing configuration: + +.. configuration-block:: + + .. code-block:: yaml + + contact: + path: /{_locale}/contact + defaults: { _controller: AcmeDemoBundle:Main:contact } + requirements: + _locale: %acme_demo.locales% + + .. code-block:: xml + + + + + + + AcmeDemoBundle:Main:contact + %acme_demo.locales% + + + + .. code-block:: php + + use Symfony\Component\Routing\RouteCollection; + use Symfony\Component\Routing\Route; + + $collection = new RouteCollection(); + $collection->add('contact', new Route('/{_locale}/contact', array( + '_controller' => 'AcmeDemoBundle:Main:contact', + ), array( + '_locale' => '%acme_demo.locales%', + ))); + + return $collection; + +You can now control and set the ``acme_demo.locales`` parameter somewhere +in your container: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + parameters: + acme_demo.locales: en|es + + .. code-block:: xml + + + + en|es + + + .. code-block:: php + + # app/config/config.php + $container->setParameter('acme_demo.locales', 'en|es'); + +You can also use a parameter to define your route path (or part of your +path): + +.. configuration-block:: + + .. code-block:: yaml + + some_route: + path: /%acme_demo.route_prefix%/contact + defaults: { _controller: AcmeDemoBundle:Main:contact } + + .. code-block:: xml + + + + + + + AcmeDemoBundle:Main:contact + + + + .. code-block:: php + + use Symfony\Component\Routing\RouteCollection; + use Symfony\Component\Routing\Route; + + $collection = new RouteCollection(); + $collection->add('some_route', new Route('/%acme_demo.route_prefix%/contact', array( + '_controller' => 'AcmeDemoBundle:Main:contact', + ))); + + return $collection; + +.. note:: + + Just like in normal service container configuration files, if you actually + need a ``%`` in your route, you can escape the percent sign by doubling + it, e.g. ``/score-50%%``, which would resolve to ``/score-50%``. diff --git a/cookbook/routing/slash_in_parameter.rst b/cookbook/routing/slash_in_parameter.rst index 0c371f5e7ff..cc1532c16ee 100644 --- a/cookbook/routing/slash_in_parameter.rst +++ b/cookbook/routing/slash_in_parameter.rst @@ -16,18 +16,18 @@ Configure the Route ------------------- By default, the Symfony routing components requires that the parameters -match the following regex pattern: ``[^/]+``. This means that all characters +match the following regex path: ``[^/]+``. This means that all characters are allowed except ``/``. You must explicitly allow ``/`` to be part of your parameter by specifying -a more permissive regex pattern. +a more permissive regex path. .. configuration-block:: .. code-block:: yaml _hello: - pattern: /hello/{name} + path: /hello/{name} defaults: { _controller: AcmeDemoBundle:Demo:hello } requirements: name: ".+" @@ -40,7 +40,7 @@ a more permissive regex pattern. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + AcmeDemoBundle:Demo:hello .+ @@ -75,4 +75,4 @@ a more permissive regex pattern. } } -That's it! Now, the ``{name}`` parameter can contain the ``/`` character. \ No newline at end of file +That's it! Now, the ``{name}`` parameter can contain the ``/`` character. diff --git a/cookbook/security/acl.rst b/cookbook/security/acl.rst index d83ed37319a..88d21bfb0f4 100644 --- a/cookbook/security/acl.rst +++ b/cookbook/security/acl.rst @@ -105,7 +105,7 @@ Creating an ACL, and adding an ACE // ... setup $form, and bind data if ($form->isValid()) { - $entityManager = $this->getDoctrine()->getEntityManager(); + $entityManager = $this->getDoctrine()->getManager(); $entityManager->persist($comment); $entityManager->flush(); diff --git a/cookbook/security/custom_authentication_provider.rst b/cookbook/security/custom_authentication_provider.rst index d66054f6815..c6223a3d733 100644 --- a/cookbook/security/custom_authentication_provider.rst +++ b/cookbook/security/custom_authentication_provider.rst @@ -415,79 +415,30 @@ to service ids that do not exist yet: ``wsse.security.authentication.provider`` ); Now that your services are defined, tell your security context about your -factory. Factories must be included in an individual configuration file, -at the time of this writing. So, start first by creating the file with the -factory service, tagged as ``security.listener.factory``: +factory in your bundle class: -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/DemoBundle/Resources/config/security_factories.yml - services: - security.authentication.factory.wsse: - class: Acme\DemoBundle\DependencyInjection\Security\Factory\WsseFactory - tags: - - { name: security.listener.factory } - - .. code-block:: xml - - - +.. versionadded:: 2.1 + Before 2.1, the factory below was added via ``security.yml`` instead. - - - - - - - - .. code-block:: php - - // src/Acme/DemoBundle/Resources/config/security_factories.php - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - $definition = new Definition('Acme\DemoBundle\DependencyInjection\Security\Factory\WsseFactory', array( - '', - '%kernel.cache_dir%/security/nonces', - )); - $definition->addTag('security.listener.factory'); - - $container->setDefinition('security.authentication.factory.wsse', $definition); - -Now, import the factory configuration via the the ``factories`` key in your -security configuration: - -.. configuration-block:: - - .. code-block:: yaml +.. code-block:: php - # app/config/security.yml - security: - factories: - - "%kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factories.yml" + // src/Acme/DemoBundle/AcmeDemoBundle.php + namespace Acme\DemoBundle; - .. code-block:: xml - - - - - "%kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factories.xml - - + use Acme\DemoBundle\DependencyInjection\Security\Factory\WsseFactory; + use Symfony\Component\HttpKernel\Bundle\Bundle; + use Symfony\Component\DependencyInjection\ContainerBuilder; - .. code-block:: php + class AcmeDemoBundle extends Bundle + { + public function build(ContainerBuilder $container) + { + parent::build($container); - // app/config/security.php - $container->loadFromExtension('security', array( - 'factories' => array( - "%kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factories.php", - ), - )); + $extension = $container->getExtension('security'); + $extension->addSecurityListenerFactory(new WsseFactory()); + } + } You are finished! You can now define parts of your app as under WSSE protection. diff --git a/cookbook/security/custom_provider.rst b/cookbook/security/custom_provider.rst index 9a268fa060d..6d1dbfc51b2 100644 --- a/cookbook/security/custom_provider.rst +++ b/cookbook/security/custom_provider.rst @@ -22,8 +22,16 @@ create a ``User`` class that represents that data. The ``User`` can look however you want and contain any data. The only requirement is that the class implements :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. The methods in this interface should therefore be defined in the custom user -class: ``getRoles()``, ``getPassword()``, ``getSalt()``, ``getUsername()``, -``eraseCredentials()``, ``equals()``. +class: :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getRoles`, +:method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getPassword`, +:method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getSalt`, +:method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getUsername`, +:method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::eraseCredentials`. +It may also be useful to implement the +:class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface` interface, +which defines a method to check if the user is equal to the current user. This +interface requires an :method:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface::isEqualTo` +method. Let's see this in action:: @@ -31,8 +39,9 @@ Let's see this in action:: namespace Acme\WebserviceUserBundle\Security\User; use Symfony\Component\Security\Core\User\UserInterface; + use Symfony\Component\Security\Core\User\EquatableInterface; - class WebserviceUser implements UserInterface + class WebserviceUser implements UserInterface, EquatableInterface { private $username; private $password; @@ -71,7 +80,7 @@ Let's see this in action:: { } - public function equals(UserInterface $user) + public function isEqualTo(UserInterface $user) { if (!$user instanceof WebserviceUser) { return false; @@ -93,11 +102,13 @@ Let's see this in action:: } } +.. versionadded:: 2.1 + The ``EquatableInterface`` was added in Symfony 2.1. Use the ``equals()`` + method of the ``UserInterface`` in Symfony 2.0. + If you have more information about your users - like a "first name" - then you can add a ``firstName`` field to hold that data. -For more details on each of the methods, see :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. - Create a User Provider ---------------------- diff --git a/cookbook/security/entity_provider.rst b/cookbook/security/entity_provider.rst index ceafa763e52..54708d9b544 100644 --- a/cookbook/security/entity_provider.rst +++ b/cookbook/security/entity_provider.rst @@ -133,14 +133,6 @@ focus on the most important methods that come from the { } - /** - * @inheritDoc - */ - public function equals(UserInterface $user) - { - return $this->id === $user->getId(); - } - /** * @see \Serializable::serialize() */ @@ -165,21 +157,36 @@ focus on the most important methods that come from the In order to use an instance of the ``AcmeUserBundle:User`` class in the Symfony security layer, the entity class must implement the :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. This -interface forces the class to implement the six following methods: +interface forces the class to implement the five following methods: -* ``getUsername()`` -* ``getSalt()`` -* ``getPassword()`` -* ``getRoles()`` +* ``getRoles()``, +* ``getPassword()``, +* ``getSalt()``, +* ``getUsername()``, * ``eraseCredentials()`` -* ``equals()`` For more details on each of these, see :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. -To keep it simple, the ``equals()`` method just compares the ``id`` field -but it's also possible to do more checks depending on the complexity of your -data model. On the other hand, the ``eraseCredentials()`` method remains empty -for the purposes of this tutorial. +.. versionadded:: 2.1 + In Symfony 2.1, the ``equals`` method was removed from ``UserInterface``. + If you need to override the default implementation of comparison logic, + implement the new :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface` + interface and implement the ``isEqualTo`` method. + +.. code-block:: php + + // src/Acme/UserBundle/Entity/User.php + + namespace Acme\UserBundle\Entity; + + use Symfony\Component\Security\Core\User\EquatableInterface; + + // ... + + public function isEqualTo(UserInterface $user) + { + return $this->id === $user->getId(); + } .. note:: @@ -439,7 +446,7 @@ The code below shows the implementation of the 'Unable to find an active admin AcmeUserBundle:User object identified by "%s".', $username ); - throw new UsernameNotFoundException($message, null, 0, $e); + throw new UsernameNotFoundException($message, 0, $e); } return $user; diff --git a/cookbook/security/form_login.rst b/cookbook/security/form_login.rst index 1e8b31f96d0..37b9f38fce1 100644 --- a/cookbook/security/form_login.rst +++ b/cookbook/security/form_login.rst @@ -31,12 +31,20 @@ directly to the login page), then the user is redirected to the default page, which is ``/`` (i.e. the homepage) by default. You can change this behavior in several ways. +.. note:: + + As mentioned, by default the user is redirected back to the page he originally + requested. Sometimes, this can cause problems, like if a background AJAX + request "appears" to be the last visited URL, causing the user to be + redirected there. For information on controlling this behavior, see + :doc:`/cookbook/security/target_path`. + Changing the Default Page ~~~~~~~~~~~~~~~~~~~~~~~~~ First, the default page can be set (i.e. the page the user is redirected to -if no previous page was stored in the session). To set it to ``/admin`` use -the following config: +if no previous page was stored in the session). To set it to the +``default_security_target`` route use the following config: .. configuration-block:: @@ -48,7 +56,7 @@ the following config: main: form_login: # ... - default_target_path: /admin + default_target_path: default_security_target .. code-block:: xml @@ -56,7 +64,7 @@ the following config: @@ -71,13 +79,14 @@ the following config: 'form_login' => array( // ... - 'default_target_path' => '/admin', + 'default_target_path' => 'default_security_target', ), ), ), )); -Now, when no URL is set in the session, users will be sent to ``/admin``. +Now, when no URL is set in the session, users will be sent to the +``default_security_target`` route. Always Redirect to the Default Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -129,7 +138,7 @@ Using the Referring URL ~~~~~~~~~~~~~~~~~~~~~~~ In case no previous URL was stored in the session, you may wish to try using -the ``HTTP_REFERER`` instead, as this will often be the same. You can do +the ``HTTP_REFERER`` instead, as this will often be the same. You can do this by setting ``use_referer`` to true (it defaults to false): .. configuration-block:: @@ -171,6 +180,10 @@ this by setting ``use_referer`` to true (it defaults to false): ), )); +.. versionadded:: 2.1 + As of 2.1, if the referer is equal to the ``login_path`` option, the + user will be redirected to the ``default_target_path``. + Control the Redirect URL from inside the Form ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -264,8 +277,8 @@ Redirecting on Login Failure In addition to redirecting the user after a successful login, you can also set the URL that the user should be redirected to after a failed login (e.g. an invalid username or password was submitted). By default, the user is redirected -back to the login form itself. You can set this to a different URL with the -following config: +back to the login form itself. You can set this to a different route (e.g. +``login_failure``) with the following config: .. configuration-block:: @@ -277,7 +290,7 @@ following config: main: form_login: # ... - failure_path: /login_failure + failure_path: login_failure .. code-block:: xml @@ -300,7 +313,7 @@ following config: 'form_login' => array( // ... - 'failure_path' => login_failure, + 'failure_path' => 'login_failure', ), ), ), diff --git a/cookbook/security/index.rst b/cookbook/security/index.rst index 2e4d18c7e3f..a8edbdc4317 100644 --- a/cookbook/security/index.rst +++ b/cookbook/security/index.rst @@ -14,3 +14,4 @@ Security securing_services custom_provider custom_authentication_provider + target_path diff --git a/cookbook/security/target_path.rst b/cookbook/security/target_path.rst new file mode 100644 index 00000000000..4d3113f4639 --- /dev/null +++ b/cookbook/security/target_path.rst @@ -0,0 +1,69 @@ +.. index:: + single: Security; Target redirect path + +How to change the Default Target Path Behavior +============================================== + +By default, the security component retains the information of the last request +URI in a session variable named ``_security.main.target_path`` (with ``main`` being +the name of the firewall, defined in ``security.yml``). Upon a successful +login, the user is redirected to this path, as to help her continue from the +last known page she visited. + +On some occasions, this is unexpected. For example when the last request +URI was an HTTP POST against a route which is configured to allow only a POST +method, the user is redirected to this route only to get a 404 error. + +To get around this behavior, you would simply need to extend the ``ExceptionListener`` +class and override the default method named ``setTargetPath()``. + +First, override the ``security.exception_listener.class`` parameter in your +configuration file. This can be done from your main configuration file (in +`app/config`) or from a configuration file being imported from a bundle: + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/HelloBundle/Resources/config/services.yml + parameters: + # ... + security.exception_listener.class: Acme\HelloBundle\Security\Firewall\ExceptionListener + + .. code-block:: xml + + + + + Acme\HelloBundle\Security\Firewall\ExceptionListener + + + .. code-block:: php + + // src/Acme/HelloBundle/Resources/config/services.php + // ... + $container->setParameter('security.exception_listener.class', 'Acme\HelloBundle\Security\Firewall\ExceptionListener'); + +Next, create your own ``ExceptionListener``:: + + // src/Acme/HelloBundle/Security/Firewall/ExceptionListener.php + namespace Acme\HelloBundle\Security\Firewall; + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Security\Http\Firewall\ExceptionListener as BaseExceptionListener; + + class ExceptionListener extends BaseExceptionListener + { + protected function setTargetPath(Request $request) + { + // Do not save target path for XHR and non-GET requests + // You can add any more logic here you want + if ($request->isXmlHttpRequest() || 'GET' !== $request->getMethod()) { + return; + } + + $request->getSession()->set('_security.main.target_path', $request->getUri()); + } + } + +Add as much or few logic here as required for your scenario! \ No newline at end of file diff --git a/cookbook/serializer.rst b/cookbook/serializer.rst new file mode 100644 index 00000000000..d4e9baeab00 --- /dev/null +++ b/cookbook/serializer.rst @@ -0,0 +1,111 @@ +.. index:: + single: Serializer + +How to use the Serializer +========================= + +Serializing and deserializing to and from objects and different formats (e.g. +JSON or XML) is a very complex topic. Symfony comes with a +:doc:`Serializer Component`, which gives you some +tools that you can leverage for your solution. + +In fact, before you start, get familiar with the serializer, normalizers +and encoders by reading the :doc:`Serializer Component`. +You should also check out the `JMSSerializerBundle`_, which expands on the +functionality offered by Symfony's core serializer. + +Activating the Serializer +------------------------- + +.. versionadded:: 2.3 + The Serializer has always existed in Symfony, but prior to Symfony 2.3, + you needed to build the ``serializer`` service yourself. + +The ``serializer`` service is not available by default. To turn it on, activate +it in your configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + framework: + # ... + serializer: + enabled: true + + .. code-block:: xml + + + + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('framework', array( + // ... + 'serializer' => array( + 'enabled' => true + ), + )); + +Adding Normalizers and Encoders +------------------------------- + +Once enabled, the ``serializer`` service will be available in the container +and will be loaded with two :ref:`encoders` +(:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder` and +:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder`) +but no :ref:`normalizers`, meaning you'll +need to load your own. + +You can load normalizers and/or encoders by tagging them as +:ref:`serializer.normalizer` and +:ref:`serializer.encoder`. It's also +possible to set the priority of the tag in order to decide the matching order. + +Here an example on how to load the load +the :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + services: + get_set_method_normalizer: + class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer + tags: + - { name: serializer.normalizer } + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // app/config/config.php + use Symfony\Component\DependencyInjection\Definition; + + $definition = new Definition( + 'Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer' + )); + $definition->addTag('serializer.normalizer'); + $container->setDefinition('get_set_method_normalizer', $definition); + +.. note:: + + The :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` + is broken by design. As soon as you have a circular object graph, an + infinite loop is created when calling the getters. You're encouraged + to add your own normalizers that fit your use-case. + +.. _JMSSerializerBundle: http://jmsyst.com/bundles/JMSSerializerBundle \ No newline at end of file diff --git a/cookbook/service_container/scopes.rst b/cookbook/service_container/scopes.rst index 2eea3583f04..e779bc933b9 100644 --- a/cookbook/service_container/scopes.rst +++ b/cookbook/service_container/scopes.rst @@ -7,7 +7,7 @@ How to work with Scopes This entry is all about scopes, a somewhat advanced topic related to the :doc:`/book/service_container`. If you've ever gotten an error mentioning "scopes" when creating services, or need to create a service that depends -on the `request` service, then this entry is for you. +on the ``request`` service, then this entry is for you. Understanding Scopes -------------------- @@ -16,49 +16,51 @@ The scope of a service controls how long an instance of a service is used by the container. The Dependency Injection component provides two generic scopes: -- `container` (the default one): The same instance is used each time you +- ``container`` (the default one): The same instance is used each time you request it from this container. -- `prototype`: A new instance is created each time you request the service. +- ``prototype``: A new instance is created each time you request the service. -The FrameworkBundle also defines a third scope: `request`. This scope is -tied to the request, meaning a new instance is created for each subrequest -and is unavailable outside the request (for instance in the CLI). +The +:class:`Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel` +also defines a third scope: ``request``. This scope is tied to the request, +meaning a new instance is created for each subrequest and is unavailable +outside the request (for instance in the CLI). Scopes add a constraint on the dependencies of a service: a service cannot depend on services from a narrower scope. For example, if you create a generic -`my_foo` service, but try to inject the `request` component, you'll receive +``my_foo`` service, but try to inject the ``request`` service, you will receive a :class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException` when compiling the container. Read the sidebar below for more details. .. sidebar:: Scopes and Dependencies - Imagine you've configured a `my_mailer` service. You haven't configured - the scope of the service, so it defaults to `container`. In other words, - every time you ask the container for the `my_mailer` service, you get + Imagine you've configured a ``my_mailer`` service. You haven't configured + the scope of the service, so it defaults to ``container``. In other words, + every time you ask the container for the ``my_mailer`` service, you get the same object back. This is usually how you want your services to work. - Imagine, however, that you need the `request` service in your `my_mailer` + Imagine, however, that you need the ``request`` service in your ``my_mailer`` service, maybe because you're reading the URL of the current request. So, you add it as a constructor argument. Let's look at why this presents a problem: - * When requesting `my_mailer`, an instance of `my_mailer` (let's call - it *MailerA*) is created and the `request` service (let's call it + * When requesting ``my_mailer``, an instance of ``my_mailer`` (let's call + it *MailerA*) is created and the ``request`` service (let's call it *RequestA*) is passed to it. Life is good! * You've now made a subrequest in Symfony, which is a fancy way of saying - that you've called, for example, the `{% render ... %}` Twig function, - which executes another controller. Internally, the old `request` service + that you've called, for example, the ``{{ render(...) }}`` Twig function, + which executes another controller. Internally, the old ``request`` service (*RequestA*) is actually replaced by a new request instance (*RequestB*). This happens in the background, and it's totally normal. - * In your embedded controller, you once again ask for the `my_mailer` - service. Since your service is in the `container` scope, the same + * In your embedded controller, you once again ask for the ``my_mailer`` + service. Since your service is in the ``container`` scope, the same instance (*MailerA*) is just re-used. But here's the problem: the *MailerA* instance still contains the old *RequestA* object, which is now **not** the correct request object to have (*RequestB* is now - the current `request` service). This is subtle, but the mis-match could + the current ``request`` service). This is subtle, but the mis-match could cause major problems, which is why it's not allowed. So, that's the reason *why* scopes exist, and how they can cause @@ -69,10 +71,74 @@ when compiling the container. Read the sidebar below for more details. A service can of course depend on a service from a wider scope without any issue. -Setting the Scope in the Definition ------------------------------------ +Using a Service from a narrower Scope +------------------------------------- + +If your service has a dependency on a scoped service (like the ``request``), +you have three ways to deal with it: + +* Use setter injection if the dependency is "synchronized"; this is the + recommended way and the best solution for the ``request`` instance as it is + synchronized with the ``request`` scope (see + :ref:`using-synchronized-service`). + +* Put your service in the same scope as the dependency (or a narrower one). If + you depend on the ``request`` service, this means putting your new service + in the ``request`` scope (see :ref:`changing-service-scope`); + +* Pass the entire container to your service and retrieve your dependency from + the container each time you need it to be sure you have the right instance + -- your service can live in the default ``container`` scope (see + :ref:`passing-container`); + +Each scenario is detailed in the following sections. + +.. _using-synchronized-service: + +Using a synchronized Service +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.3 + Synchronized services are new in Symfony 2.3. + +Injecting the container or setting your service to a narrower scope have +drawbacks. For synchronized services (like the ``request``), using setter +injection is the best option as it has no drawbacks and everything works +without any special code in your service or in your definition:: + + // src/Acme/HelloBundle/Mail/Mailer.php + namespace Acme\HelloBundle\Mail; + + use Symfony\Component\HttpFoundation\Request; + + class Mailer + { + protected $request; -The scope of a service is set in the definition of the service: + public function setRequest(Request $request = null) + { + $this->request = $request; + } + + public function sendEmail() + { + if (null === $this->request) { + // throw an error? + } + + // ... do something using the request here + } + } + +Whenever the ``request`` scope is entered or left, the service container will +automatically call the ``setRequest()`` method with the current ``request`` +instance. + +You might have noticed that the ``setRequest()`` method accepts ``null`` as a +valid value for the ``request`` argument. That's because when leaving the +``request`` scope, the ``request`` instance can be ``null`` (for the master +request for instance). Of course, you should take care of this possibility in +your code. This should also be taken into account when declaring your service: .. configuration-block:: @@ -82,42 +148,117 @@ The scope of a service is set in the definition of the service: services: greeting_card_manager: class: Acme\HelloBundle\Mail\GreetingCardManager - scope: request + calls: + - [setRequest, ['@?request']] .. code-block:: xml - + + + + .. code-block:: php // src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; + use Symfony\Component\DependencyInjection\ContainerInterface; - $container->setDefinition( + $definition = $container->setDefinition( 'greeting_card_manager', new Definition('Acme\HelloBundle\Mail\GreetingCardManager') - )->setScope('request'); + ) + ->addMethodCall('setRequest', array( + new Reference('request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false) + )); -If you don't specify the scope, it defaults to `container`, which is what -you want most of the time. Unless your service depends on another service -that's scoped to a narrower scope (most commonly, the `request` service), -you probably don't need to set the scope. +.. tip:: -Using a Service from a narrower Scope -------------------------------------- + You can declare your own ``synchronized`` services very easily; here is + the declaration of the ``request`` service for reference: + + .. configuration-block:: + + .. code-block:: yaml -If your service depends on a scoped service, the best solution is to put -it in the same scope (or a narrower one). Usually, this means putting your -new service in the `request` scope. + services: + request: + scope: request + synthetic: true + synchronized: true -But this is not always possible (for instance, a twig extension must be in -the `container` scope as the Twig environment needs it as a dependency). -In these cases, you should pass the entire container into your service and -retrieve your dependency from the container each time you need it to be sure -you have the right instance:: + .. code-block:: xml + + + + + + .. code-block:: php + + use Symfony\Component\DependencyInjection\Definition; + use Symfony\Component\DependencyInjection\ContainerInterface; + + $definition = $container->setDefinition('request') + ->setScope('request') + ->setSynthetic(true) + ->setSynchronized(true); + +.. _changing-service-scope: + +Changing the Scope of your Service +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Changing the scope of a service should be done in its definition: + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/HelloBundle/Resources/config/services.yml + services: + greeting_card_manager: + class: Acme\HelloBundle\Mail\GreetingCardManager + scope: request + arguments: [@request] + + .. code-block:: xml + + + + + + + + .. code-block:: php + + // src/Acme/HelloBundle/Resources/config/services.php + use Symfony\Component\DependencyInjection\Definition; + + $definition = $container->setDefinition( + 'greeting_card_manager', + new Definition( + 'Acme\HelloBundle\Mail\GreetingCardManager', + array(new Reference('request'), + )) + )->setScope('request'); + +.. _passing-container: + +Passing the Container as a Dependency of your Service +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Setting the scope to a narrower one is not always possible (for instance, a +twig extension must be in the ``container`` scope as the Twig environment +needs it as a dependency). In these cases, you can pass the entire container +into your service:: // src/Acme/HelloBundle/Mail/Mailer.php namespace Acme\HelloBundle\Mail; @@ -160,8 +301,7 @@ The service config for this class would look something like this: services: my_mailer: class: "%my_mailer.class%" - arguments: - - "@service_container" + arguments: ["@service_container"] # scope: container can be omitted as it is the default .. code-block:: xml @@ -195,10 +335,11 @@ The service config for this class would look something like this: .. note:: Injecting the whole container into a service is generally not a good - idea (only inject what you need). In some rare cases, it's necessary - when you have a service in the ``container`` scope that needs a service - in the ``request`` scope. + idea (only inject what you need). + +.. tip:: -If you define a controller as a service then you can get the ``Request`` object -without injecting the container by having it passed in as an argument of your -action method. See :ref:`book-controller-request-argument` for details. + If you define a controller as a service then you can get the ``Request`` + object without injecting the container by having it passed in as an + argument of your action method. See + :ref:`book-controller-request-argument` for details. diff --git a/cookbook/symfony1.rst b/cookbook/symfony1.rst index bbb0cf04262..57e6cdae880 100644 --- a/cookbook/symfony1.rst +++ b/cookbook/symfony1.rst @@ -48,7 +48,7 @@ lives inside a bundle (roughly equivalent to a symfony1 plugin) and, by default, each bundle lives inside the ``src`` directory. In that way, the ``src`` directory is a bit like the ``plugins`` directory in symfony1, but much more flexible. Additionally, while *your* bundles will live in the ``src/`` directory, -third-party bundles may live in the ``vendor/bundles/`` directory. +third-party bundles will live somewhere in the ``vendor/`` directory. To get a better picture of the ``src/`` directory, let's first think of a symfony1 application. First, part of your code likely lives inside one or @@ -77,8 +77,8 @@ The ``vendor/`` directory is basically equivalent to the ``lib/vendor/`` directory in symfony1, which was the conventional directory for all vendor libraries and bundles. By default, you'll find the Symfony2 library files in this directory, along with several other dependent libraries such as Doctrine2, -Twig and Swiftmailer. 3rd party Symfony2 bundles usually live in the -``vendor/bundles/``. +Twig and Swiftmailer. 3rd party Symfony2 bundles live somewhere in the +``vendor/``. The ``web/`` Directory ~~~~~~~~~~~~~~~~~~~~~~ @@ -116,7 +116,7 @@ That array told symfony1 exactly which file contained each class. In the production environment, this caused you to need to clear the cache when classes were added or moved. -In Symfony2, a new class - ``UniversalClassLoader`` - handles this process. +In Symfony2, a tool named `Composer`_ handles this process. The idea behind the autoloader is simple: the name of your class (including the namespace) must match up with the path to the file containing that class. Take the ``FrameworkExtraBundle`` from the Symfony2 Standard Edition as an @@ -133,24 +133,12 @@ example:: } The file itself lives at -``vendor/bundles/Sensio/Bundle/FrameworkExtraBundle/SensioFrameworkExtraBundle.php``. +``vendor/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle/SensioFrameworkExtraBundle.php``. As you can see, the location of the file follows the namespace of the class. Specifically, the namespace, ``Sensio\Bundle\FrameworkExtraBundle``, spells out the directory that the file should live in -(``vendor/bundles/Sensio/Bundle/FrameworkExtraBundle``). This is because, in the -``app/autoload.php`` file, you'll configure Symfony to look for the ``Sensio`` -namespace in the ``vendor/bundles`` directory: - -.. code-block:: php - - // app/autoload.php - - // ... - $loader->registerNamespaces(array( - // ... - - 'Sensio' => __DIR__.'/../vendor/bundles', - )); +(``vendor/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle/``). +Composer can then look for the file at this specific place and load it very fast. If the file did *not* live at this exact location, you'd receive a ``Class "Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle" does not exist.`` @@ -162,25 +150,25 @@ contains a different class). In order for a class to be autoloaded, you As mentioned before, for the autoloader to work, it needs to know that the ``Sensio`` namespace lives in the ``vendor/bundles`` directory and that, for -example, the ``Doctrine`` namespace lives in the ``vendor/doctrine/lib/`` -directory. This mapping is entirely controlled by you via the -``app/autoload.php`` file. +example, the ``Doctrine`` namespace lives in the ``vendor/doctrine/orm/lib/`` +directory. This mapping is entirely controlled by Composer. Each +third-party library you load through composer has their settings defined +and Composer takes care of everything for you. + +For this to work, all third-party libraries used by your project must be +defined in the ``composer.json`` file. If you look at the ``HelloController`` from the Symfony2 Standard Edition you can see that it lives in the ``Acme\DemoBundle\Controller`` namespace. Yet, the -``Acme`` namespace is not defined in the ``app/autoload.php``. By default you -do not need to explicitly configure the location of bundles that live in the -``src/`` directory. The ``UniversalClassLoader`` is configured to fallback to -the ``src/`` directory using its ``registerNamespaceFallbacks`` method: - -.. code-block:: php +``AcmeDemoBundle`` is not defined in your ``composer.json`` file. Nonetheless are +the files autoloaded. This is because you can tell composer to autoload files +from specific directories without defining a dependency: - // app/autoload.php +.. code-block:: yaml - // ... - $loader->registerNamespaceFallbacks(array( - __DIR__.'/../src', - )); + "autoload": { + "psr-0": { "": "src/" } + } Using the Console ----------------- @@ -367,4 +355,5 @@ In reality, the Symfony2 configuration is much more powerful and is used primarily to configure objects that you can use. For more information, see the chapter titled ":doc:`/book/service_container`". +.. _`Composer`: http://getcomposer.org .. _`Symfony2 Standard Edition`: https://github.com/symfony/symfony-standard diff --git a/cookbook/templating/PHP.rst b/cookbook/templating/PHP.rst index 61749ee0b09..40f03611fd0 100644 --- a/cookbook/templating/PHP.rst +++ b/cookbook/templating/PHP.rst @@ -289,7 +289,7 @@ pattern: # src/Acme/HelloBundle/Resources/config/routing.yml hello: # The route name - pattern: /hello/{name} + path: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } Using Assets: images, JavaScripts, and stylesheets diff --git a/cookbook/templating/global_variables.rst b/cookbook/templating/global_variables.rst index 2059079ecf9..4067cfb40fb 100644 --- a/cookbook/templating/global_variables.rst +++ b/cookbook/templating/global_variables.rst @@ -44,10 +44,10 @@ Now, the variable ``ga_tracking`` is available in all Twig templates: It's that easy! You can also take advantage of the built-in :ref:`book-service-container-parameters` system, which lets you isolate or reuse the value: -.. code-block:: ini +.. code-block:: yaml - ; app/config/parameters.ini - [parameters] + # app/config/parameters.yml + parameters: ga_tracking: UA-xxxxx-x .. configuration-block:: diff --git a/cookbook/templating/index.rst b/cookbook/templating/index.rst index 21ae6cda73c..46d6fe37012 100644 --- a/cookbook/templating/index.rst +++ b/cookbook/templating/index.rst @@ -5,6 +5,7 @@ Templating :maxdepth: 2 global_variables + namespaced_paths PHP twig_extension render_without_controller diff --git a/cookbook/templating/namespaced_paths.rst b/cookbook/templating/namespaced_paths.rst new file mode 100644 index 00000000000..8e33b38ef66 --- /dev/null +++ b/cookbook/templating/namespaced_paths.rst @@ -0,0 +1,83 @@ +.. index:: + single: Templating; Namespaced Twig Paths + +How to use and Register namespaced Twig Paths +============================================= + +.. versionadded:: 2.2 + Namespaced path support was added in 2.2. + +Usually, when you refer to a template, you'll use the ``MyBundle:Subdir:filename.html.twig`` +format (see :ref:`template-naming-locations`). + +Twig also natively offers a feature called "namespaced paths", and support +is built-in automatically for all of your bundles. + +Take the following paths as an example: + +.. code-block:: jinja + + {% extends "AcmeDemoBundle::layout.html.twig" %} + {% include "AcmeDemoBundle:Foo:bar.html.twig" %} + +With namespaced paths, the following works as well: + +.. code-block:: jinja + + {% extends "@AcmeDemo/layout.html.twig" %} + {% include "@AcmeDemo/Foo/bar.html.twig" %} + +Both paths are valid and functional by default in Symfony2. + +.. tip:: + + As an added bonus, the namespaced syntax is faster. + +Registering your own namespaces +------------------------------- + +You can also register your own custom namespaces. Suppose that you're using +some third-party library that includes Twig templates that live in +``vendor/acme/foo-project/templates``. First, register a namespace for this +directory: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + twig: + # ... + paths: + "%kernel.root_dir%/../vendor/acme/foo-bar/templates": foo_bar + + .. code-block:: xml + + + + + + + %kernel.root_dir%/../vendor/acme/foo-bar/templates + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('twig', array( + 'paths' => array( + '%kernel.root_dir%/../vendor/acme/foo-bar/templates' => 'foo_bar', + ); + )); + +The registered namespace is called ``foo_bar``, which refers to the +``vendor/acme/foo-project/templates`` directory. Assuming there's a file +called ``sidebar.twig`` in that directory, you can use it easily: + +.. code-block:: jinja + + {% include '@foo_bar/side.bar.twig` %} \ No newline at end of file diff --git a/cookbook/templating/render_without_controller.rst b/cookbook/templating/render_without_controller.rst index 5ab276b5d83..f2a44f7e4e1 100644 --- a/cookbook/templating/render_without_controller.rst +++ b/cookbook/templating/render_without_controller.rst @@ -19,7 +19,7 @@ can do this without creating a controller: .. code-block:: yaml acme_privacy: - pattern: /privacy + path: /privacy defaults: _controller: FrameworkBundle:Template:template template: 'AcmeBundle:Static:privacy.html.twig' @@ -32,7 +32,7 @@ can do this without creating a controller: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + FrameworkBundle:Template:template AcmeBundle:Static:privacy.html.twig @@ -57,17 +57,81 @@ template you've passed as the ``template`` default value. You can of course also use this trick when rendering embedded controllers from within a template. But since the purpose of rendering a controller from within a template is typically to prepare some data in a custom controller, -this probably isn't useful, except to easily cache static partials, a feature -which will become available in Symfony 2.2. +this is probably only useful if you'd like to cache this page partial (see +:ref:`cookbook-templating-no-controller-caching`). .. configuration-block:: .. code-block:: html+jinja - {% render url('acme_privacy') %} + {{ render(url('acme_privacy')) }} .. code-block:: html+php render( $view['router']->generate('acme_privacy', array(), true) ) ?> + +.. _cookbook-templating-no-controller-caching: + +Caching the static Template +--------------------------- + +.. versionadded:: 2.2 + The ability to cache templates rendered via ``FrameworkBundle:Template:template`` + is new in Symfony 2.2. + +Since templates that are rendered in this way are typically static, it might +make sense to cache them. Fortunately, this is easy! By configuring a few +other variables in your route, you can control exactly how your page is cached: + +.. configuration-block:: + + .. code-block:: yaml + + acme_privacy: + path: /privacy + defaults: + _controller: FrameworkBundle:Template:template + template: 'AcmeBundle:Static:privacy.html.twig' + maxAge: 86400 + sharedMaxAge: 86400 + + .. code-block:: xml + + + + + + + FrameworkBundle:Template:template + AcmeBundle:Static:privacy.html.twig + 86400 + 86400 + + + + .. code-block:: php + + use Symfony\Component\Routing\RouteCollection; + use Symfony\Component\Routing\Route; + + $collection = new RouteCollection(); + $collection->add('acme_privacy', new Route('/privacy', array( + '_controller' => 'FrameworkBundle:Template:template', + 'template' => 'AcmeBundle:Static:privacy.html.twig', + 'maxAge' => 86400, + 'sharedMaxAge' => 86400, + ))); + + return $collection; + +The ``maxAge`` and ``sharedMaxAge`` values are used to modify the Response +object created in the controller. For more information on caching, see +:doc:`/book/http_cache`. + +There is also a ``private`` variable (not shown here). By default, the Response +will be made public, as long as ``maxAge`` or ``sharedMaxAge`` are passed. +If set to ``true``, the Response will be marked as private. \ No newline at end of file diff --git a/cookbook/templating/twig_extension.rst b/cookbook/templating/twig_extension.rst index be2252aad13..37a2c67d723 100644 --- a/cookbook/templating/twig_extension.rst +++ b/cookbook/templating/twig_extension.rst @@ -118,6 +118,6 @@ Learning further For a more in-depth look into Twig Extensions, please take a look at the `Twig extensions documentation`_. .. _`Twig official extension repository`: https://github.com/fabpot/Twig-extensions -.. _`Twig extensions documentation`: http://twig.sensiolabs.org/doc/advanced_legacy.html#creating-an-extension +.. _`Twig extensions documentation`: http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension .. _`global variables`: http://twig.sensiolabs.org/doc/advanced.html#id1 .. _`functions`: http://twig.sensiolabs.org/doc/advanced.html#id2 diff --git a/cookbook/testing/doctrine.rst b/cookbook/testing/doctrine.rst index 911fe816cb5..ef9467861a3 100644 --- a/cookbook/testing/doctrine.rst +++ b/cookbook/testing/doctrine.rst @@ -41,7 +41,7 @@ which makes all of this quite easy:: static::$kernel->boot(); $this->em = static::$kernel->getContainer() ->get('doctrine') - ->getEntityManager() + ->getManager() ; } diff --git a/cookbook/testing/profiling.rst b/cookbook/testing/profiling.rst index 3e57fe19e74..d478c75ab47 100644 --- a/cookbook/testing/profiling.rst +++ b/cookbook/testing/profiling.rst @@ -11,15 +11,19 @@ various things and enforce some metrics. The Symfony2 :ref:`Profiler ` gathers a lot of data for each request. Use this data to check the number of database calls, the time -spent in the framework, ... But before writing assertions, always check that -the profiler is indeed available (it is enabled by default in the ``test`` -environment):: +spent in the framework, ... But before writing assertions, enable the profiler +and check that the profiler is indeed available (it is enabled by default in +the ``test`` environment):: class HelloControllerTest extends WebTestCase { public function testIndex() { $client = static::createClient(); + + // Enable the profiler for the next request (it does nothing if the profiler is not available) + $client->enableProfiler(); + $crawler = $client->request('GET', '/hello/Fabien'); // ... write some assertions about the Response @@ -34,8 +38,8 @@ environment):: // check the time spent in the framework $this->assertLessThan( - 0.5, - $profile->getCollector('timer')->getTime() + 500, + $profile->getCollector('time')->getTotalTime() ); } } diff --git a/cookbook/validation/custom_constraint.rst b/cookbook/validation/custom_constraint.rst index e8f169b4c50..71d97aa4983 100644 --- a/cookbook/validation/custom_constraint.rst +++ b/cookbook/validation/custom_constraint.rst @@ -52,7 +52,7 @@ In other words, if you create a custom ``Constraint`` (e.g. ``MyConstraint``), Symfony2 will automatically look for another class, ``MyConstraintValidator`` when actually performing the validation. -The validator class is also simple, and only has one required method: ``isValid``:: +The validator class is also simple, and only has one required method: ``validate``:: // src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumericValidator.php namespace Acme\DemoBundle\Validator\Constraints; @@ -62,22 +62,22 @@ The validator class is also simple, and only has one required method: ``isValid` class ContainsAlphanumericValidator extends ConstraintValidator { - public function isValid($value, Constraint $constraint) + public function validate($value, Constraint $constraint) { if (!preg_match('/^[a-zA-Za0-9]+$/', $value, $matches)) { - $this->setMessage($constraint->message, array('%string%' => $value)); - - return false; + $this->context->addViolation($constraint->message, array('%string%' => $value)); } - - return true; } } .. note:: - Don't forget to call ``setMessage`` to construct an error message when the - value is invalid. + The ``validate`` method does not return a value; instead, it adds violations + to the validator's ``context`` property with an ``addViolation`` method + call if there are validation failures. Therefore, a value could be considered + as being valid if it causes no violations to be added to the context. + The first parameter of the ``addViolation`` call is the error message to + use for that violation. Using the new Validator ----------------------- @@ -209,22 +209,15 @@ providing a target:: return self::CLASS_CONSTRAINT; } -With this, the validator ``isValid()`` method gets an object as its first argument:: +With this, the validator ``validate()`` method gets an object as its first argument:: class ProtocolClassValidator extends ConstraintValidator { - public function isValid($protocol, Constraint $constraint) + public function validate($protocol, Constraint $constraint) { if ($protocol->getFoo() != $protocol->getBar()) { - - $propertyPath = $this->context->getPropertyPath().'.foo'; - $this->context->setPropertyPath($propertyPath); - $this->context->addViolation($constraint->getMessage(), array(), null); - - return false; + $this->context->addViolationAt('foo', $constraint->message, array(), null); } - - return true; } } diff --git a/cookbook/workflow/_vendor_deps.rst.inc b/cookbook/workflow/_vendor_deps.rst.inc index c9f4db6e365..90b8dfa264a 100644 --- a/cookbook/workflow/_vendor_deps.rst.inc +++ b/cookbook/workflow/_vendor_deps.rst.inc @@ -1,5 +1,5 @@ -Managing Vendor Libraries with bin/vendors and deps ---------------------------------------------------- +Managing Vendor Libraries with composer.json +-------------------------------------------- How does it work? ~~~~~~~~~~~~~~~~~ @@ -9,82 +9,69 @@ way or another the goal is to download these files into your ``vendor/`` directory and, ideally, to give you some sane way to manage the exact version you need for each. -By default, these libraries are downloaded by running a ``php bin/vendors install`` -"downloader" script. This script reads from the ``deps`` file at the root -of your project. This is an ini-formatted script, which holds a list of each -of the external libraries you need, the directory each should be downloaded to, -and (optionally) the version to be downloaded. The ``bin/vendors`` script -uses ``git`` to downloaded these, solely because these external libraries -themselves tend to be stored via git. The ``bin/vendors`` script also reads -the ``deps.lock`` file, which allows you to pin each library to an exact -git commit hash. +By default, these libraries are downloaded by running a ``php composer.phar install`` +"downloader" binary. This ``composer.phar`` file is from a library called +`Composer`_ and you can read more about installing it in the :ref:`Installation` +chapter. -It's important to realize that these vendor libraries are *not* actually part -of *your* repository. Instead, they're simply un-tracked files that are downloaded -into the ``vendor/`` directory by the ``bin/vendors`` script. But since all -the information needed to download these files is saved in ``deps`` and ``deps.lock`` -(which *are* stored) in the repository), any other developer can use the -project, run ``php bin/vendors install``, and download the exact same set -of vendor libraries. This means that you're controlling exactly what each -vendor library looks like, without needing to actually commit them to *your* -repository. - -So, whenever a developer uses your project, he/she should run the ``php bin/vendors install`` -script to ensure that all of the needed vendor libraries are downloaded. - -.. sidebar:: Upgrading Symfony +The ``composer.phar`` file reads from the ``composer.json`` file at the root +of your project. This is an JSON-formatted file, which holds a list of each +of the external packages you need, the version to be downloaded and more. +The ``composer.phar`` file also reads from a ``composer.lock`` file, which +allows you to pin each library to an **exact** version. In fact, if a ``composer.lock`` +file exists, the versions inside will override those in ``composer.json``. +To upgrade your libraries to new versions, run ``php composer.phar update``. - Since Symfony is just a group of third-party libraries and third-party - libraries are entirely controlled through ``deps`` and ``deps.lock``, - upgrading Symfony means simply upgrading each of these files to match - their state in the latest Symfony Standard Edition. +.. tip:: - Of course, if you've added new entries to ``deps`` or ``deps.lock``, be sure - to replace only the original parts (i.e. be sure not to also delete any of - your custom entries). + If you want to add a new package to your application, modify the ``composer.json`` + file: -.. caution:: + .. code-block:: json - There is also a ``php bin/vendors update`` command, but this has nothing - to do with upgrading your project and you will normally not need to use - it. This command is used to freeze the versions of all of your vendor libraries - by updating them to the version specified in ``deps`` and recording it - into the ``deps.lock`` file. + { + "require": { + ... + "doctrine/doctrine-fixtures-bundle": "@dev" + } + } -Hacking vendor libraries -~~~~~~~~~~~~~~~~~~~~~~~~ + and then execute the ``update`` command for this specific package, i.e.: -Sometimes, you want a specific branch, tag, or commit of a library to be downloaded -or upgraded. You can set that directly to the ``deps`` file : + .. code-block:: bash -.. code-block:: text + $ php composer.phar update doctrine/doctrine-fixtures-bundle - [AcmeAwesomeBundle] - git=http://github.com/johndoe/Acme/AwesomeBundle.git - target=/bundles/Acme/AwesomeBundle - version=the-awesome-version + You can also combine both steps into a single command: -* The ``git`` option sets the URL of the library. It can use various protocols, - like ``http://`` as well as ``git://``. + .. code-block:: bash -* The ``target`` option specifies where the repository will live : plain Symfony - bundles should go under the ``vendor/bundles/Acme`` directory, other third-party - libraries usually go to ``vendor/my-awesome-library-name``. The target directory - defaults to this last option when not specified. + $ php composer.phar require doctrine/doctrine-fixtures-bundle:@dev -* The ``version`` option allows you to set a specific revision. You can use a tag - (``version=origin/0.42``) or a branch name (``refs/remotes/origin/awesome-branch``). - It defaults to ``origin/HEAD``. +To learn more about Composer, see `GetComposer.org`_: -Updating workflow -~~~~~~~~~~~~~~~~~ +It's important to realize that these vendor libraries are *not* actually part +of *your* repository. Instead, they're simply un-tracked files that are downloaded +into the ``vendor/``. But since all the information needed to download these +files is saved in ``composer.json`` and ``composer.lock`` (which *are* stored +in the repository), any other developer can use the project, run ``php composer.phar install``, +and download the exact same set of vendor libraries. This means that you're +controlling exactly what each vendor library looks like, without needing to +actually commit them to *your* repository. + +So, whenever a developer uses your project, he/she should run the ``php composer.phar install`` +script to ensure that all of the needed vendor libraries are downloaded. -When you execute the ``php bin/vendors install``, for every library, the script first -checks if the install directory exists. +.. sidebar:: Upgrading Symfony -If it does not (and ONLY if it does not), it runs a ``git clone``. + Since Symfony is just a group of third-party libraries and third-party + libraries are entirely controlled through ``composer.json`` and ``composer.lock``, + upgrading Symfony means simply upgrading each of these files to match + their state in the latest Symfony Standard Edition. -Then, it does a ``git fetch origin`` and a ``git reset --hard the-awesome-version``. + Of course, if you've added new entries to ``composer.json``, be sure + to replace only the original parts (i.e. be sure not to also delete any of + your custom entries). -This means that the repository will only be cloned once. If you want to perform any -change of the git remote, you MUST delete the entire target directory, not only its content. +.. _Composer: http://getcomposer.org/ +.. _GetComposer.org: http://getcomposer.org/ diff --git a/cookbook/workflow/new_project_git.rst b/cookbook/workflow/new_project_git.rst index c37c4f7a578..672a012f1c7 100644 --- a/cookbook/workflow/new_project_git.rst +++ b/cookbook/workflow/new_project_git.rst @@ -26,7 +26,7 @@ git repository: your new project structure, config files, etc. Rename it to whatever you like. 3. Create a new file called ``.gitignore`` at the root of your new project - (e.g. next to the ``deps`` file) and paste the following into it. Files + (e.g. next to the ``composer.json`` file) and paste the following into it. Files matching these patterns will be ignored by git: .. code-block:: text @@ -35,8 +35,8 @@ git repository: /app/bootstrap* /app/cache/* /app/logs/* - /vendor/ - /app/config/parameters.ini + /vendor/ + /app/config/parameters.yml .. tip:: @@ -44,11 +44,11 @@ git repository: in which case, you can find more information here: `Github .gitignore`_ This way you can exclude files/folders often used by your IDE for all of your projects. -4. Copy ``app/config/parameters.ini`` to ``app/config/parameters.ini.dist``. - The ``parameters.ini`` file is ignored by git (see above) so that machine-specific - settings like database passwords aren't committed. By creating the ``parameters.ini.dist`` +4. Copy ``app/config/parameters.yml`` to ``app/config/parameters.yml.dist``. + The ``parameters.yml`` file is ignored by git (see above) so that machine-specific + settings like database passwords aren't committed. By creating the ``parameters.yml.dist`` file, new developers can quickly clone the project, copy this file to - ``parameters.ini``, customize it, and start developing. + ``parameters.yml``, customize it, and start developing. 5. Initialize your git repository: @@ -68,48 +68,13 @@ git repository: $ git commit -m "Initial commit" -8. Finally, download all of the third-party vendor libraries: - - .. code-block:: bash - - $ php bin/vendors install +8. Finally, download all of the third-party vendor libraries by + executing composer. For details, see :ref:`installation-updating-vendors`. At this point, you have a fully-functional Symfony2 project that's correctly committed to git. You can immediately begin development, committing the new changes to your git repository. -.. tip:: - - After execution of the command: - - .. code-block:: bash - - $ php bin/vendors install - - your project will contain complete the git history of all the bundles - and libraries defined in the ``deps`` file. It can be as much as 100 MB! - If you save the current versions of all your dependencies with the command: - - .. code-block:: bash - - $ php bin/vendors lock - - then you can remove the git history directories with the following command: - - .. code-block:: bash - - $ find vendor -name .git -type d | xargs rm -rf - - The command removes all ``.git`` directories contained inside the - ``vendor`` directory. - - If you want to update bundles defined in ``deps`` file after this, you - will have to reinstall them: - - .. code-block:: bash - - $ php bin/vendors install --reinstall - You can continue to follow along with the :doc:`/book/page_creation` chapter to learn more about how to configure and develop inside your application. @@ -126,11 +91,12 @@ to learn more about how to configure and develop inside your application. Vendors and Submodules ~~~~~~~~~~~~~~~~~~~~~~ -Instead of using the ``deps``, ``bin/vendors`` system for managing your vendor +Instead of using the ``composer.json`` system for managing your vendor libraries, you may instead choose to use native `git submodules`_. There -is nothing wrong with this approach, though the ``deps`` system is the official -way to solve this problem and git submodules can be difficult to work with -at times. +is nothing wrong with this approach, though the ``composer.json`` system +is the official way to solve this problem and probably much easier to +deal with. Unlike git submodules, ``Composer`` is smart enough to calculate +which libraries depend on which other libraries. Storing your Project on a Remote Server --------------------------------------- diff --git a/cookbook/workflow/new_project_svn.rst b/cookbook/workflow/new_project_svn.rst index ef85e8620d3..9c94a714318 100644 --- a/cookbook/workflow/new_project_svn.rst +++ b/cookbook/workflow/new_project_svn.rst @@ -78,13 +78,13 @@ To get started, you'll need to download Symfony2 and get the basic Subversion se $ svn propset svn:ignore "vendor" . $ svn propset svn:ignore "bootstrap*" app/ - $ svn propset svn:ignore "parameters.ini" app/config/ + $ svn propset svn:ignore "parameters.yml" app/config/ $ svn propset svn:ignore "*" app/cache/ $ svn propset svn:ignore "*" app/logs/ $ svn propset svn:ignore "bundles" web - $ svn ci -m "commit basic Symfony ignore list (vendor, app/bootstrap*, app/config/parameters.ini, app/cache/*, app/logs/*, web/bundles)" + $ svn ci -m "commit basic Symfony ignore list (vendor, app/bootstrap*, app/config/parameters.yml, app/cache/*, app/logs/*, web/bundles)" 6. The rest of the files can now be added and committed to the project: @@ -93,24 +93,20 @@ To get started, you'll need to download Symfony2 and get the basic Subversion se $ svn add --force . $ svn ci -m "add basic Symfony Standard 2.X.Y" -7. Copy ``app/config/parameters.ini`` to ``app/config/parameters.ini.dist``. - The ``parameters.ini`` file is ignored by svn (see above) so that +7. Copy ``app/config/parameters.yml`` to ``app/config/parameters.yml.dist``. + The ``parameters.yml`` file is ignored by svn (see above) so that machine-specific settings like database passwords aren't committed. By - creating the ``parameters.ini.dist`` file, new developers can quickly clone - the project, copy this file to ``parameters.ini``, customize it, and start + creating the ``parameters.yml.dist`` file, new developers can quickly clone + the project, copy this file to ``parameters.yml``, customize it, and start developing. -8. Finally, download all of the third-party vendor libraries: - - .. code-block:: bash - - $ php bin/vendors install +8. Finally, download all of the third-party vendor libraries by + executing composer. For details, see :ref:`installation-updating-vendors`. .. tip:: - `git`_ has to be installed to run ``bin/vendors``, this is the protocol - used to fetch vendor libraries. This only means that ``git`` is used as - a tool to basically help download the libraries in the ``vendor/`` directory. + If you rely on any "dev" versions, then git may be used to install + those libraries, since there is no archive available for download. At this point, you have a fully-functional Symfony2 project stored in your Subversion repository. The development can start with commits in the Subversion diff --git a/images/components/console/progress.png b/images/components/console/progress.png new file mode 100644 index 00000000000..c126bff5252 Binary files /dev/null and b/images/components/console/progress.png differ diff --git a/images/cookbook/form/DataTransformersTypes.png b/images/cookbook/form/DataTransformersTypes.png new file mode 100644 index 00000000000..950acd39ea7 Binary files /dev/null and b/images/cookbook/form/DataTransformersTypes.png differ diff --git a/images/quick_tour/hello_fabien.png b/images/quick_tour/hello_fabien.png index 021e8631a83..e9b6ff58cdd 100644 Binary files a/images/quick_tour/hello_fabien.png and b/images/quick_tour/hello_fabien.png differ diff --git a/images/quick_tour/profiler.png b/images/quick_tour/profiler.png index 41c3b0cccee..1258b3a0b6a 100644 Binary files a/images/quick_tour/profiler.png and b/images/quick_tour/profiler.png differ diff --git a/images/quick_tour/web_debug_toolbar.png b/images/quick_tour/web_debug_toolbar.png index 7ed0df868c5..037f161a1f2 100644 Binary files a/images/quick_tour/web_debug_toolbar.png and b/images/quick_tour/web_debug_toolbar.png differ diff --git a/images/quick_tour/welcome.jpg b/images/quick_tour/welcome.jpg deleted file mode 100644 index 249a865996c..00000000000 Binary files a/images/quick_tour/welcome.jpg and /dev/null differ diff --git a/images/quick_tour/welcome.png b/images/quick_tour/welcome.png new file mode 100644 index 00000000000..b5e9e57eaf9 Binary files /dev/null and b/images/quick_tour/welcome.png differ diff --git a/index.rst b/index.rst index ea1cbfe29fb..f0c9137b94a 100644 --- a/index.rst +++ b/index.rst @@ -72,6 +72,17 @@ The Symfony Standard Edition comes with some bundles. Learn more about them: .. include:: /bundles/map.rst.inc +CMF +--- + +The Symfony CMFproject makes it easier for developers to add CMS functionality +to applications built with the Symfony2 PHP framework. + +.. toctree:: + :hidden: + + cmf/index + Contributing ------------ diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index 4a7668bab32..30cffa72fb3 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -58,46 +58,17 @@ This class must implement two methods: * ``registerContainerConfiguration()`` loads the application configuration (more on this later). -PHP autoloading can be configured via ``app/autoload.php``:: - - // app/autoload.php - use Symfony\Component\ClassLoader\UniversalClassLoader; - - $loader = new UniversalClassLoader(); - $loader->registerNamespaces(array( - 'Symfony' => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'), - 'Sensio' => __DIR__.'/../vendor/bundles', - 'JMS' => __DIR__.'/../vendor/bundles', - 'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib', - 'Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine-dbal/lib', - 'Doctrine' => __DIR__.'/../vendor/doctrine/lib', - 'Monolog' => __DIR__.'/../vendor/monolog/src', - 'Assetic' => __DIR__.'/../vendor/assetic/src', - 'Metadata' => __DIR__.'/../vendor/metadata/src', - )); - $loader->registerPrefixes(array( - 'Twig_Extensions_' => __DIR__.'/../vendor/twig-extensions/lib', - 'Twig_' => __DIR__.'/../vendor/twig/lib', - )); - - // ... - - $loader->registerNamespaceFallbacks(array( - __DIR__.'/../src', - )); - $loader->register(); - -The :class:`Symfony\\Component\\ClassLoader\\UniversalClassLoader` is used to -autoload files that respect either the technical interoperability `standards`_ -for PHP 5.3 namespaces or the PEAR naming `convention`_ for classes. As you -can see here, all dependencies are stored under the ``vendor/`` directory, but -this is just a convention. You can store them wherever you want, globally on -your server or locally in your projects. +Autoloading is handled automatically via `Composer`_, which means that you +can use any PHP classes without doing anything at all! If you need more flexibility, +you can extend the autoloader in the ``app/autoload.php`` file. All dependencies +are stored under the ``vendor/`` directory, but this is just a convention. +You can store them wherever you want, globally on your server or locally +in your projects. .. note:: - If you want to learn more about the flexibility of the Symfony2 - autoloader, read the ":doc:`/components/class_loader`" chapter. + If you want to learn more about Composer's autoloader, read `Composer-Autoloader`_. + Symfony also has an autoloading component - read ":doc:`/components/class_loader`". Understanding the Bundle System ------------------------------- @@ -162,20 +133,23 @@ PHP. Have a look at the default configuration: # app/config/config.yml imports: - - { resource: parameters.ini } + - { resource: parameters.yml } - { resource: security.yml } framework: + #esi: ~ + #translator: { fallback: "%locale%" } secret: "%secret%" - charset: UTF-8 - router: { resource: "%kernel.root_dir%/config/routing.yml" } + router: + resource: "%kernel.root_dir%/config/routing.yml" + strict_requirements: "%kernel.debug%" form: true csrf_protection: true validation: { enable_annotations: true } templating: { engines: ['twig'] } #assets_version: SomeVersionScheme - session: - default_locale: "%locale%" - auto_start: true + default_locale: "%locale%" + trusted_proxies: ~ + session: ~ # Twig Configuration twig: @@ -186,18 +160,21 @@ PHP. Have a look at the default configuration: assetic: debug: "%kernel.debug%" use_controller: false + bundles: [ ] + #java: /usr/bin/java filters: cssrewrite: ~ - # closure: - # jar: "%kernel.root_dir%/java/compiler.jar" - # yui_css: - # jar: "%kernel.root_dir%/java/yuicompressor-2.4.2.jar" + #closure: + # jar: "%kernel.root_dir%/Resources/java/compiler.jar" + #yui_css: + # jar: "%kernel.root_dir%/Resources/java/yuicompressor-2.4.7.jar" # Doctrine Configuration doctrine: dbal: driver: "%database_driver%" host: "%database_host%" + port: "%database_port%" dbname: "%database_name%" user: "%database_user%" password: "%database_password%" @@ -213,10 +190,7 @@ PHP. Have a look at the default configuration: host: "%mailer_host%" username: "%mailer_user%" password: "%mailer_password%" - - jms_security_extra: - secure_controllers: true - secure_all_services: false + spool: { type: memory } Each entry like ``framework`` defines the configuration for a specific bundle. For example, ``framework`` configures the ``FrameworkBundle`` while ``swiftmailer`` @@ -364,3 +338,5 @@ any topic you want. .. _standards: http://symfony.com/PSR0 .. _convention: http://pear.php.net/ +.. _Composer: http://getcomposer.org +.. _`Composer-Autoloader`: http://getcomposer.org/doc/01-basic-usage.md#autoloading diff --git a/quick_tour/the_big_picture.rst b/quick_tour/the_big_picture.rst index 363cb4473d6..7a13463e430 100644 --- a/quick_tour/the_big_picture.rst +++ b/quick_tour/the_big_picture.rst @@ -17,7 +17,7 @@ Downloading Symfony2 -------------------- First, check that you have installed and configured a Web server (such as -Apache) with PHP 5.3.2 or higher. +Apache) with PHP 5.3.3 or higher. .. tip:: @@ -71,12 +71,35 @@ have a ``Symfony/`` directory that looks like this: .. note:: - If you downloaded the Standard Edition *without vendors*, simply run the - following command to download all of the vendor libraries: + If you are familiar with Composer, you can run the following command + instead of downloading the archive: .. code-block:: bash - $ php bin/vendors install + $ composer.phar create-project symfony/framework-standard-edition path/to/install 2.2.0 + + # remove the Git history + $ rm -rf .git + + For an exact version, replace `2.2.0` with the latest Symfony version + (e.g. 2.1.1). For details, see the `Symfony Installation Page`_ + +.. tip:: + + If you have PHP 5.4, you can use the built-in web server: + + .. code-block:: bash + + # check your PHP CLI configuration + $ php ./app/check.php + + # run the built-in web server + $ php ./app/console server:run + + Then the URL to your application will be "http://localhost:8000/app_dev.php" + + The built-in server should be used only for development purpose, but it + can help you to start your project quickly and easily. Checking the Configuration -------------------------- @@ -87,7 +110,18 @@ URL to see the diagnostics for your machine: .. code-block:: text - http://localhost/Symfony/web/config.php + http://localhost/config.php + +.. note:: + + All of the example URLs assume that you've downloaded and unzipped Symfony + directly into the web server web root. If you've followed the directions + above and unzipped the `Symfony` directory into your web root, then add + `/Symfony/web` after `localhost` for all the URLs you see: + + .. code-block:: text + + http://localhost/Symfony/web/config.php .. note:: @@ -113,11 +147,11 @@ your first "real" Symfony2 webpage: .. code-block:: text - http://localhost/Symfony/web/app_dev.php/ + http://localhost/app_dev.php/ Symfony2 should welcome and congratulate you for your hard work so far! -.. image:: /images/quick_tour/welcome.jpg +.. image:: /images/quick_tour/welcome.png :align: center Understanding the Fundamentals @@ -141,7 +175,7 @@ Symfony2 (replace *Fabien* with your first name): .. code-block:: text - http://localhost/Symfony/web/app_dev.php/demo/hello/Fabien + http://localhost/app_dev.php/demo/hello/Fabien .. image:: /images/quick_tour/hello_fabien.png :align: center @@ -162,7 +196,7 @@ Routing ~~~~~~~ Symfony2 routes the request to the code that handles it by trying to match the -requested URL against some configured patterns. By default, these patterns +requested URL against some configured paths. By default, these paths (called routes) are defined in the ``app/config/routing.yml`` configuration file. When you're in the ``dev`` :ref:`environment` - indicated by the app_**dev**.php front controller - the ``app/config/routing_dev.yml`` @@ -173,7 +207,7 @@ these "demo" pages are placed in that file: # app/config/routing_dev.yml _welcome: - pattern: / + path: / defaults: { _controller: AcmeDemoBundle:Welcome:index } _demo: @@ -314,7 +348,7 @@ file, routes are defined as annotations on action methods:: // ... } -The ``@Route()`` annotation defines a new route with a pattern of +The ``@Route()`` annotation defines a new route with a path of ``/hello/{name}`` that executes the ``helloAction`` method when matched. A string enclosed in curly brackets like ``{name}`` is called a placeholder. As you can see, its value can be retrieved through the ``$name`` method argument. @@ -392,20 +426,25 @@ the profiler. .. image:: /images/quick_tour/profiler.png :align: center +.. note:: + + You can get more information quickly by hovering over the items on the + Web Debug Toolbar. + Of course, you won't want to show these tools when you deploy your application to production. That's why you will find another front controller in the ``web/`` directory (``app.php``), which is optimized for the production environment: .. code-block:: text - http://localhost/Symfony/web/app.php/demo/hello/Fabien + http://localhost/app.php/demo/hello/Fabien And if you use Apache with ``mod_rewrite`` enabled, you can even omit the ``app.php`` part of the URL: .. code-block:: text - http://localhost/Symfony/web/demo/hello/Fabien + http://localhost/demo/hello/Fabien Last but not least, on the production servers, you should point your web root directory to the ``web/`` directory to secure your installation and have an @@ -464,3 +503,4 @@ are eager to learn more about Symfony2, dive into the next section: .. _YAML: http://www.yaml.org/ .. _annotations in controllers: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html#annotations-for-controllers .. _Twig: http://twig.sensiolabs.org/ +.. _`Symfony Installation Page`: http://symfony.com/download diff --git a/quick_tour/the_controller.rst b/quick_tour/the_controller.rst old mode 100644 new mode 100755 index 51c0a77fb86..51a160204be --- a/quick_tour/the_controller.rst +++ b/quick_tour/the_controller.rst @@ -41,7 +41,7 @@ automatically selects the right template, here ``hello.xml.twig``: That's all there is to it. For standard formats, Symfony2 will also automatically choose the best ``Content-Type`` header for the response. If you want to support different formats for a single action, use the ``{_format}`` -placeholder in the route pattern instead:: +placeholder in the route path instead:: // src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; @@ -130,20 +130,25 @@ from any controller:: // in another controller for another request $foo = $session->get('foo'); - // set the user locale - $session->setLocale('fr'); + // use a default value if the key doesn't exist + $filters = $session->set('filters', array()); You can also store small messages that will only be available for the very next request:: // store a message for the very next request (in a controller) - $session->setFlash('notice', 'Congratulations, your action succeeded!'); + $session->getFlashBag()->add('notice', 'Congratulations, your action succeeded!'); - // display the message back in the next request (in a template) - {{ app.session.flash('notice') }} + // display any messages back in the next request (in a template) + + {% for flashMessage in app.session.flashbag.get('notice') %} +
          {{ flashMessage }}
          + {% endfor %} This is useful when you need to set a success message before redirecting -the user to another page (which will then show the message). +the user to another page (which will then show the message). Please note that +when you use has() instead of get(), the flash message will not be cleared and +thus remains available during the following requests. Securing Resources ------------------ @@ -164,9 +169,10 @@ fits most common needs: providers: in_memory: - users: - user: { password: userpass, roles: [ 'ROLE_USER' ] } - admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } + memory: + users: + user: { password: userpass, roles: [ 'ROLE_USER' ] } + admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } firewalls: dev: @@ -197,7 +203,7 @@ Moreover, the ``admin`` user has a ``ROLE_ADMIN`` role, which includes the configuration, but you can use any hashing algorithm by tweaking the ``encoders`` section. -Going to the ``http://localhost/Symfony/web/app_dev.php/demo/secured/hello`` +Going to the ``http://localhost/app_dev.php/demo/secured/hello`` URL will automatically redirect you to the login form because this resource is protected by a ``firewall``. diff --git a/quick_tour/the_view.rst b/quick_tour/the_view.rst index 36cabefe769..60788277a0d 100644 --- a/quick_tour/the_view.rst +++ b/quick_tour/the_view.rst @@ -170,7 +170,7 @@ And change the ``index.html.twig`` template to include it: {# override the body block from embedded.html.twig #} {% block content %} - {% include "AcmeDemoBundle:Demo:embedded.html.twig" %} + {{ include("AcmeDemoBundle:Demo:embedded.html.twig") }} {% endblock %} Embedding other Controllers @@ -180,57 +180,19 @@ And what if you want to embed the result of another controller in a template? That's very useful when working with Ajax, or when the embedded template needs some variable not available in the main template. -Suppose you've created a ``fancyAction`` controller method, and you want to "render" -it inside the ``index`` template. First, create a route to your new controller -in one of your application's routing configuration files. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - fancy: - pattern: /included/fancy/{name}/{color} - defaults: { _controller: AcmeDemoBundle:Demo:fancy } - - .. code-block:: xml - - - - - - - AcmeDemoBundle:Demo:fancy - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('fancy', new Route('/included/fancy/{name}/{color}', array( - '_controller' => 'AcmeDemoBundle:Demo:fancy', - ))); - - return $collection; - -To include the result (e.g. ``HTML``) of the controller, use the ``render`` tag: +Suppose you've created a ``fancyAction`` controller method, and you want to +"render" it inside the ``index`` template, which means including the result +(e.g. ``HTML``) of the controller. To do this, use the ``render`` function: .. code-block:: jinja {# src/Acme/DemoBundle/Resources/views/Demo/index.html.twig #} - {% render url('fancy', { 'name': name, 'color': 'green'}) %} - -.. include:: /book/_security-2012-6431.rst.inc + {{ render(controller("AcmeDemoBundle:Demo:fancy", {'name': name, 'color': 'green'})) }} -The ``render`` tag will execute the ``AcmeDemoBundle:Demo:fancy`` controller -and include its result. For example, your new ``fancyAction`` might look -like this:: +Here, the ``AcmeDemoBundle:Demo:fancy`` string refers to the ``fancy`` action +of the ``Demo`` controller. The arguments (``name`` and ``color``) act like +simulated request variables (as if the ``fancyAction`` were handling a whole +new request) and are made available to the controller:: // src/Acme/DemoBundle/Controller/DemoController.php @@ -243,7 +205,7 @@ like this:: return $this->render('AcmeDemoBundle:Demo:fancy.html.twig', array( 'name' => $name, - 'object' => $object + 'object' => $object, )); } diff --git a/reference/configuration/assetic.rst b/reference/configuration/assetic.rst index d7d9a814175..e58e5a7686b 100644 --- a/reference/configuration/assetic.rst +++ b/reference/configuration/assetic.rst @@ -12,13 +12,19 @@ Full Default Configuration .. code-block:: yaml assetic: - debug: true - use_controller: true + debug: "%kernel.debug%" + use_controller: + enabled: "%kernel.debug%" + profiler: false read_from: "%kernel.root_dir%/../web" write_to: "%assetic.read_from%" java: /usr/bin/java node: /usr/bin/node + ruby: /usr/bin/ruby sass: /usr/bin/sass + # An key-value pair of any number of named elements + variables: + some_name: [] bundles: # Defaults (all currently registered bundles): @@ -30,26 +36,22 @@ Full Default Configuration - DoctrineBundle - AsseticBundle - ... - assets: - - # Prototype - name: + # An array of named assets (e.g. some_asset, some_other_asset) + some_asset: inputs: [] filters: [] options: - - # Prototype - name: [] + # A key-value array of options and values + some_option_name: [] filters: - # Prototype - name: [] + # An array of named filters (e.g. some_filter, some_other_filter) + some_filter: [] twig: functions: - - # Prototype - name: [] + # An array of named functions (e.g. some_function, some_other_function) + some_function: [] .. code-block:: xml diff --git a/reference/configuration/doctrine.rst b/reference/configuration/doctrine.rst index 1d990051b17..c2115238e27 100644 --- a/reference/configuration/doctrine.rst +++ b/reference/configuration/doctrine.rst @@ -12,62 +12,161 @@ Doctrine Configuration Reference doctrine: dbal: default_connection: default + types: + # A collection of custom types + # Example + some_custom_type: + class: Acme\HelloBundle\MyCustomType + commented: true + connections: default: dbname: database + + # A collection of different named connections (e.g. default, conn2, etc) + default: + dbname: ~ host: localhost - port: 1234 - user: user - password: secret + port: ~ + user: root + password: ~ + charset: ~ + path: ~ + memory: ~ + + # The unix socket to use for MySQL + unix_socket: ~ + + # True to use as persistent connection for the ibm_db2 driver + persistent: ~ + + # The protocol to use for the ibm_db2 driver (default to TCPIP if omitted) + protocol: ~ + + # True to use dbname as service name instead of SID for Oracle + service: ~ + + # The session mode to use for the oci8 driver + sessionMode: ~ + + # True to use a pooled server with the oci8 driver + pooled: ~ + + # Configuring MultipleActiveResultSets for the pdo_sqlsrv driver + MultipleActiveResultSets: ~ driver: pdo_mysql - driver_class: MyNamespace\MyDriverImpl + platform_service: ~ + logging: %kernel.debug% + profiling: %kernel.debug% + driver_class: ~ + wrapper_class: ~ options: - foo: bar - path: "%kernel.data_dir%/data.sqlite" - memory: true - unix_socket: /tmp/mysql.sock - wrapper_class: MyDoctrineDbalConnectionWrapper - charset: UTF8 - logging: "%kernel.debug%" - platform_service: MyOwnDatabasePlatformService + # an array of options + key: [] mapping_types: - enum: string - conn1: - # ... - types: - custom: Acme\HelloBundle\MyCustomType + # an array of mapping types + name: [] + slaves: + + # a collection of named slave connections (e.g. slave1, slave2) + slave1: + dbname: ~ + host: localhost + port: ~ + user: root + password: ~ + charset: ~ + path: ~ + memory: ~ + + # The unix socket to use for MySQL + unix_socket: ~ + + # True to use as persistent connection for the ibm_db2 driver + persistent: ~ + + # The protocol to use for the ibm_db2 driver (default to TCPIP if omitted) + protocol: ~ + + # True to use dbname as service name instead of SID for Oracle + service: ~ + + # The session mode to use for the oci8 driver + sessionMode: ~ + + # True to use a pooled server with the oci8 driver + pooled: ~ + + # Configuring MultipleActiveResultSets for the pdo_sqlsrv driver + MultipleActiveResultSets: ~ + orm: - auto_generate_proxy_classes: false - proxy_namespace: Proxies - proxy_dir: "%kernel.cache_dir%/doctrine/orm/Proxies" - default_entity_manager: default # The first defined is used if not set + default_entity_manager: ~ + auto_generate_proxy_classes: false + proxy_dir: %kernel.cache_dir%/doctrine/orm/Proxies + proxy_namespace: Proxies + # search for the "ResolveTargetEntityListener" class for a cookbook about this + resolve_target_entities: [] entity_managers: - default: - # The name of a DBAL connection (the one marked as default is used if not set) - connection: conn1 - mappings: # Required - AcmeHelloBundle: ~ - class_metadata_factory_name: Doctrine\ORM\Mapping\ClassMetadataFactory - # All cache drivers have to be array, apc, xcache or memcache - metadata_cache_driver: array - query_cache_driver: array + # A collection of different named entity managers (e.g. some_em, another_em) + some_em: + query_cache_driver: + type: array # Required + host: ~ + port: ~ + instance_class: ~ + class: ~ + metadata_cache_driver: + type: array # Required + host: ~ + port: ~ + instance_class: ~ + class: ~ result_cache_driver: - type: memcache - host: localhost - port: 11211 - instance_class: Memcache - class: Doctrine\Common\Cache\MemcacheCache + type: array # Required + host: ~ + port: ~ + instance_class: ~ + class: ~ + connection: ~ + class_metadata_factory_name: Doctrine\ORM\Mapping\ClassMetadataFactory + default_repository_class: Doctrine\ORM\EntityRepository + auto_mapping: false + hydrators: + + # An array of hydrator names + hydrator_name: [] + mappings: + # An array of mappings, which may be a bundle name or something else + mapping_name: + mapping: true + type: ~ + dir: ~ + alias: ~ + prefix: ~ + is_bundle: ~ dql: + # a collection of string functions string_functions: - test_string: Acme\HelloBundle\DQL\StringFunction + # example + # test_string: Acme\HelloBundle\DQL\StringFunction + + # a collection of numeric functions numeric_functions: - test_numeric: Acme\HelloBundle\DQL\NumericFunction + # example + # test_numeric: Acme\HelloBundle\DQL\NumericFunction + + # a collection of datetime functions datetime_functions: - test_datetime: Acme\HelloBundle\DQL\DatetimeFunction - hydrators: - custom: Acme\HelloBundle\Hydrators\CustomHydrator - em2: - # ... + # example + # test_datetime: Acme\HelloBundle\DQL\DatetimeFunction + + # Register SQL Filters in the entity manager + filters: + # An array of filters + some_filter: + class: ~ # Required + enabled: false .. code-block:: xml @@ -117,7 +216,7 @@ Doctrine Configuration Reference @@ -152,8 +251,8 @@ certain classes, but those are for very advanced use-cases only. Caching Drivers ~~~~~~~~~~~~~~~ -For the caching drivers you can specify the values "array", "apc", "memcache" -or "xcache". +For the caching drivers you can specify the values "array", "apc", "memcache", "memcached", +"xcache" or "service". The following example shows an overview of the caching configurations: @@ -163,7 +262,9 @@ The following example shows an overview of the caching configurations: orm: auto_mapping: true metadata_cache_driver: apc - query_cache_driver: xcache + query_cache_driver: + type: service + id: my_doctrine_common_cache_service result_cache_driver: type: memcache host: localhost @@ -246,6 +347,8 @@ The following block shows all possible configuration keys: enum: string types: custom: Acme\HelloBundle\MyCustomType + # the DBAL keepSlave option + keep_slave: false .. code-block:: xml diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index a64cc4f292c..79ced25beba 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -15,11 +15,10 @@ routing and more. Configuration ------------- -* `charset`_ * `secret`_ +* `http_method_override`_ * `ide`_ * `test`_ -* `trust_proxy_headers`_ * `trusted_proxies`_ * `form`_ * enabled @@ -27,20 +26,22 @@ Configuration * enabled * field_name * `session`_ - * `lifetime`_ + * `cookie_lifetime`_ + * `cookie_path`_ + * `cookie_domain`_ + * `cookie_secure`_ + * `cookie_httponly`_ + * `gc_divisor`_ + * `gc_probability`_ + * `gc_maxlifetime`_ + * `save_path`_ +* `serializer`_ + * `enabled`_ * `templating`_ * `assets_base_urls`_ * `assets_version`_ * `assets_version_format`_ -charset -~~~~~~~ - -**type**: ``string`` **default**: ``UTF-8`` - -The character set that's used throughout the framework. It becomes the service -container parameter named ``kernel.charset``. - secret ~~~~~~ @@ -51,6 +52,23 @@ it's used for generating the CSRF tokens, but it could be used in any other context where having a unique string is useful. It becomes the service container parameter named ``kernel.secret``. +.. _configuration-framework-http_method_override: + +http_method_override +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.3 + The ``http_method_override`` option is new in Symfony 2.3. + +**type**: ``Boolean`` **default**: ``true`` + +This determines whether the ``_method`` request parameter is used as the intended +HTTP method on POST requests. If enabled, the +:method:`Request::enableHttpMethodParameterOverride ` +gets called automatically. It becomes the service container parameter named +``kernel.http_method_override``. For more information, see +:doc:`/cookbook/routing/method_parameters`. + ide ~~~ @@ -119,23 +137,6 @@ see :doc:`/components/http_foundation/trusting_proxies`. 'trusted_proxies' => array('192.0.0.1'), )); -trust_proxy_headers -~~~~~~~~~~~~~~~~~~~ - -.. caution:: - - The ``trust_proxy_headers`` option is deprecated and will be removed in - Symfony 2.3. See `trusted_proxies`_ and :doc:`/components/http_foundation/trusting_proxies` - for details on how to properly trust proxy data. - -**type**: ``Boolean`` - -Configures if HTTP headers (like ``HTTP_X_FORWARDED_FOR``, ``X_FORWARDED_PROTO``, and -``X_FORWARDED_HOST``) are trusted as an indication for an SSL connection. By default, it is -set to ``false`` and only SSL_HTTPS connections are indicated as secure. - -You should enable this setting if your application is behind a reverse proxy. - .. _reference-framework-form: form @@ -147,14 +148,104 @@ csrf_protection session ~~~~~~~ -lifetime -........ +cookie_lifetime +............... **type**: ``integer`` **default**: ``0`` This determines the lifetime of the session - in seconds. By default it will use ``0``, which means the cookie is valid for the length of the browser session. +cookie_path +........... + +**type**: ``string`` **default**: ``/`` + +This determines the path to set in the session cookie. By default it will use ``/``. + +cookie_domain +............. + +**type**: ``string`` **default**: ``''`` + +This determines the domain to set in the session cookie. By default it's blank, +meaning the host name of the server which generated the cookie according +to the cookie specification. + +cookie_secure +............. + +**type**: ``Boolean`` **default**: ``false`` + +This determines whether cookies should only be sent over secure connections. + +cookie_httponly +............... + +**type**: ``Boolean`` **default**: ``false`` + +This determines whether cookies should only accessible through the HTTP protocol. +This means that the cookie won't be accessible by scripting languages, such +as JavaScript. This setting can effectively help to reduce identity theft +through XSS attacks. + +gc_probability +.............. + +.. versionadded:: 2.1 + The ``gc_probability`` option is new in version 2.1 + +**type**: ``integer`` **default**: ``1`` + +This defines the probability that the garbage collector (GC) process is started +on every session initialization. The probability is calculated by using +``gc_probability`` / ``gc_divisor``, e.g. 1/100 means there is a 1% chance +that the GC process will start on each request. + +gc_divisor +.......... + +.. versionadded:: 2.1 + The ``gc_divisor`` option is new in version 2.1 + +**type**: ``integer`` **default**: ``100`` + +See `gc_probability`_. + +gc_maxlifetime +.............. + +.. versionadded:: 2.1 + The ``gc_maxlifetime`` option is new in version 2.1 + +**type**: ``integer`` **default**: ``14400`` + +This determines the number of seconds after which data will be seen as "garbage" +and potentially cleaned up. Garbage collection may occur during session start +and depends on `gc_divisor`_ and `gc_probability`_. + +save_path +......... + +**type**: ``string`` **default**: ``%kernel.cache.dir%/sessions`` + +This determines the argument to be passed to the save handler. If you choose +the default file handler, this is the path where the files are created. + +.. _configuration-framework-serializer: + +serializer +~~~~~~~~~~ + +enabled +....... + +**type**: ``boolean`` **default**: ``false`` + +Whether to enable the ``serializer`` service or not in the service container. + +For more details, see :doc:`/cookbook/serializer`. + templating ~~~~~~~~~~ @@ -175,6 +266,15 @@ is `protocol-relative`_ (i.e. starts with `//`) it will be added to both collections. URLs starting with ``http://`` will only be added to the ``http`` collection. +.. versionadded:: 2.1 + Unlike most configuration blocks, successive values for ``assets_base_urls`` + will overwrite each other instead of being merged. This behavior was chosen + because developers will typically define base URL's for each environment. + Given that most projects tend to inherit configurations + (e.g. ``config_test.yml`` imports ``config_dev.yml``) and/or share a common + base configuration (i.e. ``config.yml``), merging could yield a set of base + URL's for multiple environments. + .. _ref-framework-assets-version: assets_version @@ -279,36 +379,43 @@ Full Default Configuration .. code-block:: yaml framework: - - # general configuration - charset: ~ - secret: ~ # Required + secret: ~ + http_method_override: true + trusted_proxies: [] ide: ~ test: ~ - trust_proxy_headers: false + default_locale: en # form configuration form: - enabled: true + enabled: false csrf_protection: - enabled: true + enabled: false field_name: _token # esi configuration esi: - enabled: true + enabled: false + + # fragments configuration + fragments: + enabled: false + path: /_fragment # profiler configuration profiler: + enabled: false only_exceptions: false - only_master_requests: false - dsn: "sqlite:%kernel.cache_dir%/profiler.db" + only_master_requests: false + dsn: file:%kernel.cache_dir%/profiler username: password: lifetime: 86400 matcher: ip: ~ - path: ~ + + # use the urldecoded format + path: ~ # Example: ^/path to resource/ service: ~ # router configuration @@ -318,29 +425,46 @@ Full Default Configuration http_port: 80 https_port: 443 + # set to true to throw an exception when a parameter does not match the requirements + # set to false to disable exceptions when a parameter does not match the requirements (and return null instead) + # set to null to disable parameter checks against requirements + # 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production + strict_requirements: true + # session configuration session: - auto_start: ~ - default_locale: en storage_id: session.storage.native + handler_id: session.handler.native_file name: ~ - lifetime: 0 - path: ~ - domain: ~ - secure: ~ - httponly: ~ + cookie_lifetime: ~ + cookie_path: ~ + cookie_domain: ~ + cookie_secure: ~ + cookie_httponly: ~ + gc_divisor: ~ + gc_probability: ~ + gc_maxlifetime: ~ + save_path: %kernel.cache_dir%/sessions + + # serializer configuration + serializer: + enabled: false # templating configuration templating: assets_version: ~ - assets_version_format: "%%s?%%s" + assets_version_format: %%s?%%s + hinclude_default_template: ~ + form: + resources: + + # Default: + - FrameworkBundle:Form assets_base_urls: http: [] ssl: [] cache: ~ engines: # Required - form: - resources: [FrameworkBundle:Form] # Example: - twig @@ -350,26 +474,27 @@ Full Default Configuration # Prototype name: version: ~ - version_format: ~ + version_format: %%s?%%s base_urls: http: [] ssl: [] # translator configuration translator: - enabled: true + enabled: false fallback: en # validation configuration validation: - enabled: true + enabled: false cache: ~ enable_annotations: false + translation_domain: validators # annotation configuration annotations: cache: file - file_cache_dir: "%kernel.cache_dir%/annotations" - debug: true + file_cache_dir: %kernel.cache_dir%/annotations + debug: %kernel.debug% .. _`protocol-relative`: http://tools.ietf.org/html/rfc3986#section-4.2 diff --git a/reference/configuration/kernel.rst b/reference/configuration/kernel.rst index 026f475518b..24566753ebb 100644 --- a/reference/configuration/kernel.rst +++ b/reference/configuration/kernel.rst @@ -11,11 +11,36 @@ the parent :class:`Symfony\\Component\\HttpKernel\\Kernel` class. Configuration ------------- +* `Charset`_ * `Kernel Name`_ * `Root Directory`_ * `Cache Directory`_ * `Log Directory`_ +.. versionadded:: 2.1 + The :method:`Symfony\\Component\\HttpKernel\\Kernel::getCharset` method is new + in Symfony 2.1 + +Charset +~~~~~~~ + +**type**: ``string`` **default**: ``UTF-8`` + +This returns the charset that is used in the application. To change it, override the +:method:`Symfony\\Component\\HttpKernel\\Kernel::getCharset` method and return another +charset, for instance:: + + // app/AppKernel.php + + // ... + class AppKernel extends Kernel + { + public function getCharset() + { + return 'ISO-8859-1'; + } + } + Kernel Name ~~~~~~~~~~~ diff --git a/reference/configuration/monolog.rst b/reference/configuration/monolog.rst index 66ec4645567..8acf1f5fbb1 100644 --- a/reference/configuration/monolog.rst +++ b/reference/configuration/monolog.rst @@ -18,6 +18,8 @@ Monolog Configuration Reference level: ERROR bubble: false formatter: my_formatter + processors: + - some_callable main: type: fingers_crossed action_level: WARNING @@ -27,8 +29,8 @@ Monolog Configuration Reference type: service id: my_handler - # Prototype - name: + # Default options and values for some "my_custom_handler" + my_custom_handler: type: ~ # Required id: ~ priority: 0 @@ -39,16 +41,23 @@ Monolog Configuration Reference facility: user max_files: 0 action_level: WARNING + activation_strategy: ~ stop_buffering: true buffer_size: 0 handler: ~ members: [] + channels: + type: ~ + elements: ~ from_email: ~ to_email: ~ subject: ~ email_prototype: - id: ~ # Required (when the email_prototype is used) - method: ~ + id: ~ # Required (when the email_prototype is used) + factory-method: ~ + channels: + type: ~ + elements: [] formatter: ~ .. code-block:: xml diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 33f4484b6e2..9d208d605e2 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -19,74 +19,99 @@ Each part will be explained in the next section. # app/config/security.yml security: - access_denied_url: /foo/error403 - - always_authenticate_before_granting: false + access_denied_url: ~ # Example: /foo/error403 # strategy can be: none, migrate, invalidate - session_fixation_strategy: migrate - + session_fixation_strategy: migrate + hide_user_not_found: true + always_authenticate_before_granting: false + erase_credentials: true access_decision_manager: - strategy: affirmative - allow_if_all_abstain: false - allow_if_equal_granted_denied: true - + strategy: affirmative + allow_if_all_abstain: false + allow_if_equal_granted_denied: true acl: - connection: default # any name configured in doctrine.dbal section - tables: - class: acl_classes - entry: acl_entries - object_identity: acl_object_identities - object_identity_ancestors: acl_object_identity_ancestors - security_identity: acl_security_identities + + # any name configured in doctrine.dbal section + connection: ~ cache: - id: service_id - prefix: sf2_acl_ + id: ~ + prefix: sf2_acl_ + provider: ~ + tables: + class: acl_classes + entry: acl_entries + object_identity: acl_object_identities + object_identity_ancestors: acl_object_identity_ancestors + security_identity: acl_security_identities voter: - allow_if_object_identity_unavailable: true + allow_if_object_identity_unavailable: true encoders: - somename: - class: Acme\DemoBundle\Entity\User - Acme\DemoBundle\Entity\User: sha512 - Acme\DemoBundle\Entity\User: plaintext - Acme\DemoBundle\Entity\User: - algorithm: sha512 - encode_as_base64: true - iterations: 5000 - Acme\DemoBundle\Entity\User: - id: my.custom.encoder.service.id - - providers: - memory: - name: memory - users: - foo: { password: foo, roles: ROLE_USER } - bar: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] } - entity: - entity: { class: SecurityBundle:User, property: username } - - factories: - MyFactory: "%kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factories.xml" - - firewalls: + # Examples: + Acme\DemoBundle\Entity\User1: sha512 + Acme\DemoBundle\Entity\User2: + algorithm: sha512 + encode_as_base64: true + iterations: 5000 + + # PBKDF2 encoder + # see the note about PBKDF2 below for details on security and speed + Acme\Your\Class\Name: + algorithm: pbkdf2 + hash_algorithm: sha512 + encode_as_base64: true + iterations: 1000 + + # Example options/values for what a custom encoder might look like + Acme\DemoBundle\Entity\User3: + id: my.encoder.id + + providers: # Required + # Examples: + my_in_memory_provider: + memory: + users: + foo: + password: foo + roles: ROLE_USER + bar: + password: bar + roles: [ROLE_USER, ROLE_ADMIN] + + my_entity_provider: + entity: + class: SecurityBundle:User + property: username + + # Example custom provider + my_some_custom_provider: + id: ~ + + # Chain some providers + my_chain_provider: + chain: + providers: [ my_in_memory_provider, my_entity_provider ] + + firewalls: # Required + # Examples: somename: pattern: .* request_matcher: some.service.id access_denied_url: /foo/error403 access_denied_handler: some.service.id entry_point: some.service.id - provider: name + provider: some_key_from_above # manages where each firewall stores session information # See "Firewall Context" below for more details context: context_key stateless: false x509: - provider: name + provider: some_key_from_above http_basic: - provider: name + provider: some_key_from_above http_digest: - provider: name + provider: some_key_from_above form_login: # submit the login form here check_path: /login_check @@ -106,6 +131,7 @@ Each part will be explained in the next section. # login failure redirecting options (read further below) failure_path: /foo failure_forward: false + failure_path_parameter: _failure_path failure_handler: some.service.id success_handler: some.service.id @@ -143,20 +169,51 @@ Each part will be explained in the next section. success_handler: some.service.id anonymous: ~ - access_control: - - - path: ^/foo - host: mydomain.foo - ip: 192.0.0.0/8 - roles: [ROLE_A, ROLE_B] - requires_channel: https + # Default values and options for any firewall + some_firewall_listener: + pattern: ~ + security: true + request_matcher: ~ + access_denied_url: ~ + access_denied_handler: ~ + entry_point: ~ + provider: ~ + stateless: false + context: ~ + logout: + csrf_parameter: _csrf_token + csrf_provider: ~ + intention: logout + path: /logout + target: / + success_handler: ~ + invalidate_session: true + delete_cookies: + + # Prototype + name: + path: ~ + domain: ~ + handlers: [] + anonymous: + key: 4f954a0667e01 + switch_user: + provider: ~ + parameter: _switch_user + role: ROLE_ALLOWED_TO_SWITCH + access_control: + requires_channel: ~ + + # use the urldecoded format + path: ~ # Example: ^/path to resource/ + host: ~ + ip: ~ + methods: [] + roles: [] role_hierarchy: - ROLE_SUPERADMIN: ROLE_ADMIN - ROLE_SUPERADMIN: 'ROLE_ADMIN, ROLE_USER' - ROLE_SUPERADMIN: [ROLE_ADMIN, ROLE_USER] - anything: { id: ROLE_SUPERADMIN, value: 'ROLE_USER, ROLE_ADMIN' } - anything: { id: ROLE_SUPERADMIN, value: [ROLE_USER, ROLE_ADMIN] } + ROLE_ADMIN: [ROLE_ORGANIZER, ROLE_USER] + ROLE_SUPERADMIN: [ROLE_ADMIN] .. _reference-security-firewall-form-login: @@ -172,19 +229,19 @@ The Login Form and Process ~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``login_path`` (type: ``string``, default: ``/login``) - This is the URL that the user will be redirected to (unless ``use_forward`` - is set to ``true``) when he/she tries to access a protected resource - but isn't fully authenticated. + This is the route or path that the user will be redirected to (unless + ``use_forward`` is set to ``true``) when he/she tries to access a + protected resource but isn't fully authenticated. - This URL **must** be accessible by a normal, un-authenticated user, else + This path **must** be accessible by a normal, un-authenticated user, else you may create a redirect loop. For details, see ":ref:`Avoid Common Pitfalls`". * ``check_path`` (type: ``string``, default: ``/login_check``) - This is the URL that your login form must submit to. The firewall will - intercept any requests (``POST`` requests only, by default) to this URL - and process the submitted login credentials. - + This is the route or path that your login form must submit to. The + firewall will intercept any requests (``POST`` requests only, by default) + to this URL and process the submitted login credentials. + Be sure that this URL is covered by your main firewall (i.e. don't create a separate firewall just for ``check_path`` URL). @@ -215,7 +272,95 @@ Redirecting after Login * ``target_path_parameter`` (type: ``string``, default: ``_target_path``) * ``use_referer`` (type: ``Boolean``, default: ``false``) -.. _reference-security-firewall-context: +.. _reference-security-pbkdf2: + +Using the PBKDF2 encoder: Security and Speed +-------------------------------------------- + +.. versionadded:: 2.2 + The PBKDF2 password encoder was added in Symfony 2.2. + +The `PBKDF2`_ encoder provides a high level of Cryptographic security, as +recommended by the National Institute of Standards and Technology (NIST). + +You can see an example of the ``pbkdf2`` encoder in the YAML block on this page. + +But using PBKDF2 also warrants a warning: using it (with a high number +of iterations) slows down the process. Thus, PBKDF2 should be used with +caution and care. + +A good configuration lies around at least 1000 iterations and sha512 +for the hash algorithm. + +.. _reference-security-bcrypt: + +Using the BCrypt Password Encoder +--------------------------------- + +.. versionadded:: 2.2 + The BCrypt password encoder was added in Symfony 2.2. + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + # ... + + encoders: + Symfony\Component\Security\Core\User\User: + algorithm: bcrypt + cost: 15 + + .. code-block:: xml + + + + + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + // ... + 'encoders' => array( + 'Symfony\Component\Security\Core\User\User' => array( + 'algorithm' => 'bcrypt', + 'cost' => 15, + ), + ), + )); + +The ``cost`` can be in the range of ``4-31`` and determines how long a password +will be encoded. Each increment of ``cost`` *doubles* the time it takes to +encode a password. + +If you don't provide the ``cost`` option, the default cost of ``13`` is used. + +.. note:: + + You can change the cost at any time — even if you already have some + passwords encoded using a different cost. New passwords will be encoded + using the new cost, while the already encoded ones will be validated + using a cost that was used back when they were encoded. + +A salt for each new password is generated automatically and need not be +persisted. Since an encoded password contains the salt used to encode it, +persisting the encoded password alone is enough. + +.. note:: + + All the encoded passwords are ``60`` characters long, so make sure to + allocate enough space for them to be persisted. + + .. _reference-security-firewall-context: Firewall Context ---------------- @@ -315,3 +460,4 @@ To use HTTP-Digest authentication you need to provide a realm and a key: ), )); +.. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2 diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index 7af917cce9f..5977ce092fb 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -9,11 +9,12 @@ TwigBundle Configuration Reference .. code-block:: yaml twig: + exception_controller: twig.controller.exception:showAction form: resources: # Default: - - div_layout.html.twig + - form_div_layout.html.twig # Example: - MyBundle::form.html.twig @@ -23,9 +24,11 @@ TwigBundle Configuration Reference foo: "@bar" pi: 3.14 - # Prototype - key: + # Example options, but the easiest use is as seen above + some_variable_name: + # a service id that should be the value id: ~ + # set to service or leave blank type: ~ value: ~ autoescape: ~ @@ -35,7 +38,7 @@ TwigBundle Configuration Reference debug: "%kernel.debug%" strict_variables: ~ auto_reload: ~ - exception_controller: Symfony\Bundle\TwigBundle\Controller\ExceptionController::showAction + optimizations: ~ .. code-block:: xml @@ -83,7 +86,7 @@ Configuration exception_controller .................... -**type**: ``string`` **default**: ``Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAction`` +**type**: ``string`` **default**: ``twig.controller.exception:showAction`` This is the controller that is activated after an exception is thrown anywhere in your application. The default controller diff --git a/reference/configuration/web_profiler.rst b/reference/configuration/web_profiler.rst index 6809b3a6b36..5253692a6be 100644 --- a/reference/configuration/web_profiler.rst +++ b/reference/configuration/web_profiler.rst @@ -12,12 +12,13 @@ Full Default Configuration .. code-block:: yaml web_profiler: - - # display secondary information, disable to make the toolbar shorter - verbose: true + + # DEPRECATED, it is not useful anymore and can be removed safely from your configuration + verbose: true # display the web debug toolbar at the bottom of pages with a summary of profiler info - toolbar: false + toolbar: false + position: bottom # gives you the opportunity to look at the collected data before following the redirect intercept_redirects: false diff --git a/reference/constraints.rst b/reference/constraints.rst index 54d67eaf8a1..1ed9b35649b 100644 --- a/reference/constraints.rst +++ b/reference/constraints.rst @@ -14,14 +14,12 @@ Validation Constraints Reference constraints/Type constraints/Email - constraints/MinLength - constraints/MaxLength + constraints/Length constraints/Url constraints/Regex constraints/Ip - constraints/Max - constraints/Min + constraints/Range constraints/Date constraints/DateTime @@ -29,6 +27,7 @@ Validation Constraints Reference constraints/Choice constraints/Collection + constraints/Count constraints/UniqueEntity constraints/Language constraints/Locale @@ -37,13 +36,17 @@ Validation Constraints Reference constraints/File constraints/Image + constraints/CardScheme + constraints/Luhn + constraints/Callback - constraints/Valid constraints/All + constraints/UserPassword + constraints/Valid The Validator is designed to validate objects against *constraints*. In real life, a constraint could be: "The cake must not be burned". In -Symfony2, constraints are similar: They are assertions that a condition is +Symfony2, constraints are similar: They are assertions that a condition is true. Supported Constraints diff --git a/reference/constraints/All.rst b/reference/constraints/All.rst index 83a279a09a8..17f2e2dadcd 100644 --- a/reference/constraints/All.rst +++ b/reference/constraints/All.rst @@ -30,7 +30,8 @@ entry in that array: favoriteColors: - All: - NotBlank: ~ - - MinLength: 5 + - Length: + min: 5 .. code-block:: php-annotations @@ -44,7 +45,7 @@ entry in that array: /** * @Assert\All({ * @Assert\NotBlank - * @Assert\MinLength(5), + * @Assert\Length(min = "5"), * }) */ protected $favoriteColors = array(); @@ -58,8 +59,8 @@ entry in that array: @@ -81,7 +82,7 @@ entry in that array: $metadata->addPropertyConstraint('favoriteColors', new Assert\All(array( 'constraints' => array( new Assert\NotBlank(), - new Assert\MinLength(array('limit' => 5)), + new Assert\Length(array('limit' => 5)), ), ))); } diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst index cc545296750..93539cb7eb2 100644 --- a/reference/constraints/Callback.rst +++ b/reference/constraints/Callback.rst @@ -86,28 +86,26 @@ Setup The Callback Method ------------------- -The callback method is passed a special ``ExecutionContext`` object. You +The callback method is passed a special ``ExecutionContextInterface`` object. You can set "violations" directly on this object and determine to which field those errors should be attributed:: // ... - use Symfony\Component\Validator\ExecutionContext; + use Symfony\Component\Validator\ExecutionContextInterface; class Author { // ... private $firstName; - public function isAuthorValid(ExecutionContext $context) + public function isAuthorValid(ExecutionContextInterface $context) { // somehow you have an array of "fake names" $fakeNames = array(); // check if the name is actually a fake name if (in_array($this->getFirstName(), $fakeNames)) { - $propertyPath = $context->getPropertyPath() . '.firstName'; - $context->setPropertyPath($propertyPath); - $context->addViolation('This name sounds totally fake!', array(), null); + $context->addViolationAt('firstname', 'This name sounds totally fake!', array(), null); } } } @@ -127,7 +125,7 @@ process. Each method can be one of the following formats: If the name of a method is a simple string (e.g. ``isAuthorValid``), that method will be called on the same object that's being validated and the - ``ExecutionContext`` will be the only argument (see the above example). + ``ExecutionContextInterface`` will be the only argument (see the above example). 2) **Static array callback** @@ -191,16 +189,16 @@ process. Each method can be one of the following formats: In this case, the static method ``isAuthorValid`` will be called on the ``Acme\BlogBundle\MyStaticValidatorClass`` class. It's passed both the original - object being validated (e.g. ``Author``) as well as the ``ExecutionContext``:: + object being validated (e.g. ``Author``) as well as the ``ExecutionContextInterface``:: namespace Acme\BlogBundle; - use Symfony\Component\Validator\ExecutionContext; + use Symfony\Component\Validator\ExecutionContextInterface; use Acme\BlogBundle\Entity\Author; class MyStaticValidatorClass { - public static function isAuthorValid(Author $author, ExecutionContext $context) + public static function isAuthorValid(Author $author, ExecutionContextInterface $context) { // ... } diff --git a/reference/constraints/CardScheme.rst b/reference/constraints/CardScheme.rst new file mode 100644 index 00000000000..35b63aacb02 --- /dev/null +++ b/reference/constraints/CardScheme.rst @@ -0,0 +1,120 @@ +CardScheme +========== + +.. versionadded:: 2.2 + The CardScheme validation is new in Symfony 2.2. + +This constraint ensures that a credit card number is valid for a given credit card +company. It can be used to validate the number before trying to initiate a payment +through a payment gateway. + ++----------------+--------------------------------------------------------------------------+ +| Applies to | :ref:`property or method` | ++----------------+--------------------------------------------------------------------------+ +| Options | - `schemes`_ | +| | - `message`_ | ++----------------+--------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\CardScheme` | ++----------------+--------------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\CardSchemeValidator` | ++----------------+--------------------------------------------------------------------------+ + +Basic Usage +----------- + +To use the ``CardScheme`` validator, simply apply it to a property or method +on an object that will contain a credit card number. + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/SubscriptionBundle/Resources/config/validation.yml + Acme\SubscriptionBundle\Entity\Transaction: + properties: + cardNumber: + - CardScheme: + schemes: [VISA] + message: You credit card number is invalid. + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php-annotations + + // src/Acme/SubscriptionBundle/Entity/Transaction.php + use Symfony\Component\Validator\Constraints as Assert; + + class Transaction + { + /** + * @Assert\CardScheme(schemes = {"VISA"}, message = "You credit card number is invalid.") + */ + protected $cardNumber; + } + + .. code-block:: php + + // src/Acme/SubscriptionBundle/Entity/Transaction.php + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints\CardScheme; + + class Transaction + { + protected $cardNumber; + + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('cardSchema', new CardScheme(array( + 'schemes' => array( + 'VISA' + ), + 'message' => 'You credit card number is invalid.', + ))); + } + } + +Available Options +----------------- + +schemes +------- + +**type**: ``mixed`` [:ref:`default option`] + +This option is required and represents the name of the number scheme used to +validate the credit card number, it can either be a string or an array. Valid +values are: + +* ``AMEX`` +* ``CHINA_UNIONPAY`` +* ``DINERS`` +* ``DISCOVER`` +* ``INSTAPAYMENT`` +* ``JCB`` +* ``LASER`` +* ``MAESTRO`` +* ``MASTERCARD`` +* ``VISA`` + +For more information about the used schemes, see `Wikipedia: Issuer identification number (IIN)`_. + +message +~~~~~~~ + +**type**: ``string`` **default**: ``Unsupported card type or invalid card number`` + +The message shown when the value does not pass the ``CardScheme`` check. + +.. _`Wikipedia: Issuer identification number (IIN)`: http://en.wikipedia.org/wiki/Bank_card_number#Issuer_identification_number_.28IIN.29 \ No newline at end of file diff --git a/reference/constraints/Collection.rst b/reference/constraints/Collection.rst index ba63fb2f485..f8ec600f7e2 100644 --- a/reference/constraints/Collection.rst +++ b/reference/constraints/Collection.rst @@ -5,7 +5,7 @@ This constraint is used when the underlying data is a collection (i.e. an array or an object that implements ``Traversable`` and ``ArrayAccess``), but you'd like to validate different keys of that collection in different ways. For example, you might validate the ``email`` key using the ``Email`` -constraint and the ``inventory`` key of the collection with the ``Min`` constraint. +constraint and the ``inventory`` key of the collection with the ``Range`` constraint. This constraint can also make sure that certain collection keys are present and that extra keys are not present. @@ -63,9 +63,9 @@ blank but is no longer than 100 characters in length, you would do the following personal_email: Email short_bio: - NotBlank - - MaxLength: - limit: 100 - message: Your short bio is too long! + - Length: + max: 100 + maxMessage: Your short bio is too long! allowMissingFields: true .. code-block:: php-annotations @@ -83,9 +83,9 @@ blank but is no longer than 100 characters in length, you would do the following * "personal_email" = @Assert\Email, * "short_bio" = { * @Assert\NotBlank(), - * @Assert\MaxLength( - * limit = 100, - * message = "Your bio is too long!" + * @Assert\Length( + * max = 100, + * maxMessage = "Your bio is too long!" * ) * } * }, @@ -110,9 +110,9 @@ blank but is no longer than 100 characters in length, you would do the following - - - + + + @@ -138,7 +138,10 @@ blank but is no longer than 100 characters in length, you would do the following $metadata->addPropertyConstraint('profileData', new Assert\Collection(array( 'fields' => array( 'personal_email' => new Assert\Email(), - 'lastName' => array(new Assert\NotBlank(), new Assert\MaxLength(100)), + 'lastName' => array( + new Assert\NotBlank(), + new Assert\Length(array("max" => 100)), + ), ), 'allowMissingFields' => true, ))); diff --git a/reference/constraints/Count.rst b/reference/constraints/Count.rst new file mode 100644 index 00000000000..9c808e9576d --- /dev/null +++ b/reference/constraints/Count.rst @@ -0,0 +1,138 @@ +Count +===== + +Validates that a given collection's (i.e. an array or an object that implements Countable) +element count is *between* some minimum and maximum value. + +.. versionadded:: 2.1 + The Count constraint was added in Symfony 2.1. + ++----------------+---------------------------------------------------------------------+ +| Applies to | :ref:`property or method` | ++----------------+---------------------------------------------------------------------+ +| Options | - `min`_ | +| | - `max`_ | +| | - `minMessage`_ | +| | - `maxMessage`_ | +| | - `exactMessage`_ | ++----------------+---------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Count` | ++----------------+---------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\CountValidator` | ++----------------+---------------------------------------------------------------------+ + +Basic Usage +----------- + +To verify that the ``emails`` array field contains between 1 and 5 elements +you might add the following: + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/EventBundle/Resources/config/validation.yml + Acme\EventBundle\Entity\Participant: + properties: + emails: + - Count: + min: 1 + max: 5 + minMessage: "You must specify at least one email" + maxMessage: "You cannot specify more than {{ limit }} emails" + + .. code-block:: php-annotations + + // src/Acme/EventBundle/Entity/Participant.php + namespace Acme\EventBundle\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + /** + * @Assert\Count( + * min = "1", + * max = "5", + * minMessage = "You must specify at least one email", + * maxMessage = "You cannot specify more than {{ limit }} emails" + * ) + */ + protected $emails = array(); + } + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // src/Acme/EventBundle/Entity/Participant.php + namespace Acme\EventBundle\Entity; + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + public static function loadValidatorMetadata(ClassMetadata $data) + { + $metadata->addPropertyConstraint('emails', new Assert\Count(array( + 'min' => 1, + 'max' => 5, + 'minMessage' => 'You must specify at least one email', + 'maxMessage' => 'You cannot specify more than {{ limit }} emails', + ))); + } + } + +Options +------- + +min +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "min" count value. Validation will fail if the given +collection elements count is **less** than this min value. + +max +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "max" count value. Validation will fail if the given +collection elements count is **greater** than this max value. + +minMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This collection should contain {{ limit }} elements or more.``. + +The message that will be shown if the underlying collection elements count is less than the `min`_ option. + +maxMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This collection should contain {{ limit }} elements or less.``. + +The message that will be shown if the underlying collection elements count is more than the `max`_ option. + +exactMessage +~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This collection should contain exactly {{ limit }} elements.``. + +The message that will be shown if min and max values are equal and the underlying collection elements +count is not exactly this value. diff --git a/reference/constraints/Email.rst b/reference/constraints/Email.rst index d7c010ec3ef..f0ea3098655 100644 --- a/reference/constraints/Email.rst +++ b/reference/constraints/Email.rst @@ -9,6 +9,7 @@ cast to a string before being validated. +----------------+---------------------------------------------------------------------+ | Options | - `message`_ | | | - `checkMX`_ | +| | - `checkHost`_ | +----------------+---------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Validator\\Constraints\\Email` | +----------------+---------------------------------------------------------------------+ @@ -29,17 +30,17 @@ Basic Usage - Email: message: The email "{{ value }}" is not a valid email. checkMX: true - + .. code-block:: php-annotations // src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity; - + use Symfony\Component\Validator\Constraints as Assert; class Author { - /** + /** * @Assert\Email( * message = "The email '{{ value }}' is not a valid email.", * checkMX = true @@ -100,7 +101,17 @@ checkMX **type**: ``Boolean`` **default**: ``false`` -If true, then the `checkdnsrr`_ PHP function will be used to check the validity -of the MX record of the host of the given email. +If true, then the :phpfunction:`checkdnsrr` PHP function will be used to +check the validity of the MX record of the host of the given email. + +checkHost +~~~~~~~~~ + +.. versionadded:: 2.1 + The ``checkHost`` option was added in Symfony 2.1 + +**type**: ``Boolean`` **default**: ``false`` -.. _`checkdnsrr`: http://www.php.net/manual/en/function.checkdnsrr.php +If true, then the :phpfunction:`checkdnsrr` PHP function will be used to +check the validity of the MX *or* the A *or* the AAAA record of the host +of the given email. diff --git a/reference/constraints/Image.rst b/reference/constraints/Image.rst index d7ee756e88d..ca90c6476c7 100644 --- a/reference/constraints/Image.rst +++ b/reference/constraints/Image.rst @@ -5,19 +5,149 @@ The Image constraint works exactly like the :doc:`File` constraint for the bulk of the documentation on this constraint. ++----------------+----------------------------------------------------------------------+ +| Applies to | :ref:`property or method` | ++----------------+----------------------------------------------------------------------+ +| Options | - `mimeTypes`_ | +| | - `minWidth`_ | +| | - `maxWidth`_ | +| | - `maxHeight`_ | +| | - `minHeight`_ | +| | - `mimeTypesMessage`_ | +| | - `sizeNotDetectedMessage`_ | +| | - `maxWidthMessage`_ | +| | - `minWidthMessage`_ | +| | - `maxHeightMessage`_ | +| | - `minHeightMessage`_ | +| | - See :doc:`File` for inherited options | ++----------------+----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\File` | ++----------------+----------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\FileValidator` | ++----------------+----------------------------------------------------------------------+ + +Basic Usage +----------- + +This constraint is most commonly used on a property that will be rendered +in a form as a :doc:`file` form type. For example, +suppose you're creating an author form where you can upload a "headshot" +image for the author. In your form, the ``headshot`` property would be a +``file`` type. The ``Author`` class might look as follows:: + + // src/Acme/BlogBundle/Entity/Author.php + namespace Acme\BlogBundle\Entity; + + use Symfony\Component\HttpFoundation\File\File; + + class Author + { + protected $headshot; + + public function setHeadshot(File $file = null) + { + $this->headshot = $file; + } + + public function getHeadshot() + { + return $this->headshot; + } + } + +To guarantee that the ``headshot`` ``File`` object is a valid image and that +it is between a certain size, add the following: + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/BlogBundle/Resources/config/validation.yml + Acme\BlogBundle\Entity\Author + properties: + headshot: + - Image: + minWidth: 200 + maxWidth: 400 + minHeight: 200 + maxHeight: 400 + + + .. code-block:: php-annotations + + // src/Acme/BlogBundle/Entity/Author.php + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + /** + * @Assert\Image( + * minWidth = 200, + * maxWidth = 400, + * minHeight = 200, + * maxHeight = 400 + * ) + */ + protected $headshot; + } + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // src/Acme/BlogBundle/Entity/Author.php + // ... + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints\Image; + + class Author + { + // ... + + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('headshot', new Image(array( + 'minWidth' => 200, + 'maxWidth' => 400, + 'minHeight' => 200, + 'maxHeight' => 400, + ))); + } + } + +The ``headshot`` property is validated to guarantee that it is a real image +and that it is between a certain width and height. + Options ------- This constraint shares all of its options with the :doc:`File` -constraint. It does, however, modify two of the default option values: +constraint. It does, however, modify two of the default option values and +add several other options. mimeTypes ~~~~~~~~~ -**type**: ``array`` or ``string`` **default**: an array of jpg, gif and png image mime types +**type**: ``array`` or ``string`` **default**: ``image/*`` You can find a list of existing image mime types on the `IANA website`_ @@ -26,5 +156,76 @@ mimeTypesMessage **type**: ``string`` **default**: ``This file is not a valid image`` +.. versionadded:: 2.1 + All of the min/max width/height options are new to Symfony 2.1. + +minWidth +~~~~~~~~ + +**type**: ``integer`` + +If set, the width of the image file must be greater than or equal to this +value in pixels. + +maxWidth +~~~~~~~~ + +**type**: ``integer`` + +If set, the width of the image file must be less than or equal to this +value in pixels. + +minHeight +~~~~~~~~~ + +**type**: ``integer`` + +If set, the height of the image file must be greater than or equal to this +value in pixels. + +maxHeight +~~~~~~~~~ + +**type**: ``integer`` + +If set, the height of the image file must be less than or equal to this +value in pixels. + +sizeNotDetectedMessage +~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The size of the image could not be detected`` + +If the system is unable to determine the size of the image, this error will +be displayed. This will only occur when at least one of the four size constraint +options has been set. + +maxWidthMessage +~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px`` + +The error message if the width of the image exceeds `maxWidth`_. + +minWidthMessage +~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px`` + +The error message if the width of the image is less than `minWidth`_. + +maxHeightMessage +~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px`` + +The error message if the height of the image exceeds `maxHeight`_. + +minHeightMessage +~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px`` + +The error message if the height of the image is less than `minHeight`_. -.. _`IANA website`: http://www.iana.org/assignments/media-types/image/index.html \ No newline at end of file +.. _`IANA website`: http://www.iana.org/assignments/media-types/image/index.html diff --git a/reference/constraints/Length.rst b/reference/constraints/Length.rst new file mode 100644 index 00000000000..a78cf42c62e --- /dev/null +++ b/reference/constraints/Length.rst @@ -0,0 +1,148 @@ +Length +====== + +Validates that a given string length is *between* some minimum and maximum value. + +.. versionadded:: 2.1 + The Length constraint was added in Symfony 2.1. + ++----------------+----------------------------------------------------------------------+ +| Applies to | :ref:`property or method` | ++----------------+----------------------------------------------------------------------+ +| Options | - `min`_ | +| | - `max`_ | +| | - `charset`_ | +| | - `minMessage`_ | +| | - `maxMessage`_ | +| | - `exactMessage`_ | ++----------------+----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Length` | ++----------------+----------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\LengthValidator` | ++----------------+----------------------------------------------------------------------+ + +Basic Usage +----------- + +To verify that the ``firstName`` field length of a class is between "2" and +"50", you might add the following: + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/EventBundle/Resources/config/validation.yml + Acme\EventBundle\Entity\Participant: + properties: + firstName: + - Length: + min: 2 + max: 50 + minMessage: "Your first name must be at least {{ limit }} characters length" + maxMessage: "Your first name cannot be longer than than {{ limit }} characters length" + + .. code-block:: php-annotations + + // src/Acme/EventBundle/Entity/Participant.php + namespace Acme\EventBundle\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + /** + * @Assert\Length( + * min = "2", + * max = "50", + * minMessage = "Your first name must be at least {{ limit }} characters length", + * maxMessage = "Your first name cannot be longer than than {{ limit }} characters length" + * ) + */ + protected $firstName; + } + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // src/Acme/EventBundle/Entity/Participant.php + namespace Acme\EventBundle\Entity; + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('firstName', new Assert\Length(array( + 'min' => 2, + 'max' => 50, + 'minMessage' => 'Your first name must be at least {{ limit }} characters length', + 'maxMessage' => 'Your first name cannot be longer than than {{ limit }} characters length', + ))); + } + } + +Options +------- + +min +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "min" length value. Validation will fail if the given +value's length is **less** than this min value. + +max +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "max" length value. Validation will fail if the given +value's length is **greater** than this max value. + +charset +~~~~~~~ + +**type**: ``string`` **default**: ``UTF-8`` + +The charset to be used when computing value's length. The :phpfunction:`grapheme_strlen` PHP +function is used if available. If not, the the :phpfunction:`mb_strlen` PHP function +is used if available. If neither are available, the :phpfunction:`strlen` PHP function +is used. + +minMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value is too short. It should have {{ limit }} characters or more.``. + +The message that will be shown if the underlying value's length is less than the `min`_ option. + +maxMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value is too long. It should have {{ limit }} characters or less.``. + +The message that will be shown if the underlying value's length is more than the `max`_ option. + +exactMessage +~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should have exactly {{ limit }} characters.``. + +The message that will be shown if min and max values are equal and the underlying +value's length is not exactly this value. diff --git a/reference/constraints/Luhn.rst b/reference/constraints/Luhn.rst new file mode 100644 index 00000000000..f2103cc2a62 --- /dev/null +++ b/reference/constraints/Luhn.rst @@ -0,0 +1,90 @@ +Luhn +====== + +.. versionadded:: 2.2 + The Luhn validation is new in Symfony 2.2. + +This constraint is used to ensure that a credit card number passes the `Luhn algorithm`_. +It is useful as a first step to validating a credit card: before communicating with a +payment gateway. + ++----------------+-----------------------------------------------------------------------+ +| Applies to | :ref:`property or method` | ++----------------+-----------------------------------------------------------------------+ +| Options | - `message`_ | ++----------------+-----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Luhn` | ++----------------+-----------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\LuhnValidator` | ++----------------+-----------------------------------------------------------------------+ + +Basic Usage +----------- + +To use the Luhn validator, simply apply it to a property on an object that +will contain a credit card number. + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/SubscriptionBundle/Resources/config/validation.yml + Acme\SubscriptionBundle\Entity\Transaction: + properties: + cardNumber: + - Luhn: + message: Please check your credit card number. + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php-annotations + + // src/Acme/SubscriptionBundle/Entity/Transaction.php + use Symfony\Component\Validator\Constraints as Assert; + + class Transaction + { + /** + * @Assert\Luhn(message = "Please check your credit card number.") + */ + protected $cardNumber; + } + + .. code-block:: php + + // src/Acme/SubscriptionBundle/Entity/Transaction.php + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints\Luhn; + + class Transaction + { + protected $cardNumber; + + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('luhn', new Luhn(array( + 'message' => 'Please check your credit card number', + ))); + } + } + +Available Options +----------------- + +message +~~~~~~~ + +**type**: ``string`` **default**: ``Invalid card number`` + +The default message supplied when the value does not pass the Luhn check. + +.. _`Luhn algorithm`: http://en.wikipedia.org/wiki/Luhn_algorithm \ No newline at end of file diff --git a/reference/constraints/Max.rst b/reference/constraints/Max.rst deleted file mode 100644 index 4bb358117f8..00000000000 --- a/reference/constraints/Max.rst +++ /dev/null @@ -1,105 +0,0 @@ -Max -=== - -Validates that a given number is *less* than some maximum number. - -+----------------+--------------------------------------------------------------------+ -| Applies to | :ref:`property or method` | -+----------------+--------------------------------------------------------------------+ -| Options | - `limit`_ | -| | - `message`_ | -| | - `invalidMessage`_ | -+----------------+--------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Max` | -+----------------+--------------------------------------------------------------------+ -| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\MaxValidator` | -+----------------+--------------------------------------------------------------------+ - -Basic Usage ------------ - -To verify that the "age" field of a class is not greater than "50", you might -add the following: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/EventBundle/Resources/config/validation.yml - Acme\EventBundle\Entity\Participant: - properties: - age: - - Max: { limit: 50, message: You must be 50 or under to enter. } - - .. code-block:: php-annotations - - // src/Acme/EventBundle/Entity/Participant.php - namespace Acme\EventBundle\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - - class Participant - { - /** - * @Assert\Max(limit = 50, message = "You must be 50 or under to enter.") - */ - protected $age; - } - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // src/Acme/EventBundle/Entity/Participant.php - namespace Acme\EventBundle\Entity; - - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints as Assert; - - class Participant - { - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('age', new Assert\Max(array( - 'limit' => 50, - 'message' => 'You must be 50 or under to enter.', - ))); - } - } - -Options -------- - -limit -~~~~~ - -**type**: ``integer`` [:ref:`default option`] - -This required option is the "max" value. Validation will fail if the given -value is **greater** than this max value. - -message -~~~~~~~ - -**type**: ``string`` **default**: ``This value should be {{ limit }} or less`` - -The message that will be shown if the underlying value is greater than the -`limit`_ option. - -invalidMessage -~~~~~~~~~~~~~~ - -**type**: ``string`` **default**: ``This value should be a valid number`` - -The message that will be shown if the underlying value is not a number (per -the :phpfunction:`is_numeric` PHP function). diff --git a/reference/constraints/MaxLength.rst b/reference/constraints/MaxLength.rst deleted file mode 100644 index b1bcc7b5fd2..00000000000 --- a/reference/constraints/MaxLength.rst +++ /dev/null @@ -1,101 +0,0 @@ -MaxLength -========= - -Validates that the length of a string is not larger than the given limit. - -+----------------+-------------------------------------------------------------------------+ -| Applies to | :ref:`property or method` | -+----------------+-------------------------------------------------------------------------+ -| Options | - `limit`_ | -| | - `message`_ | -| | - `charset`_ | -+----------------+-------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Validator\\Constraints\\MaxLength` | -+----------------+-------------------------------------------------------------------------+ -| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\MaxLengthValidator` | -+----------------+-------------------------------------------------------------------------+ - -Basic Usage ------------ - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Blog: - properties: - summary: - - MaxLength: 100 - - .. code-block:: php-annotations - - // src/Acme/BlogBundle/Entity/Blog.php - namespace Acme\BlogBundle\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - - class Blog - { - /** - * @Assert\MaxLength(100) - */ - protected $summary; - } - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Blog.php - namespace Acme\BlogBundle\Entity; - - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints as Assert; - - class Blog - { - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('summary', new Assert\MaxLength(array( - 'limit' => 100, - ))); - } - } - -Options -------- - -limit -~~~~~ - -**type**: ``integer`` [:ref:`default option`] - -This required option is the "max" value. Validation will fail if the length -of the give string is **greater** than this number. - -message -~~~~~~~ - -**type**: ``string`` **default**: ``This value is too long. It should have {{ limit }} characters or less`` - -The message that will be shown if the underlying string has a length that -is longer than the `limit`_ option. - -charset -~~~~~~~ - -**type**: ``charset`` **default**: ``UTF-8`` - -If the PHP extension "mbstring" is installed, then the PHP function :phpfunction:`mb_strlen` -will be used to calculate the length of the string. The value of the ``charset`` -option is passed as the second argument to that function. diff --git a/reference/constraints/Min.rst b/reference/constraints/Min.rst deleted file mode 100644 index 241cce09010..00000000000 --- a/reference/constraints/Min.rst +++ /dev/null @@ -1,105 +0,0 @@ -Min -=== - -Validates that a given number is *greater* than some minimum number. - -+----------------+--------------------------------------------------------------------+ -| Applies to | :ref:`property or method` | -+----------------+--------------------------------------------------------------------+ -| Options | - `limit`_ | -| | - `message`_ | -| | - `invalidMessage`_ | -+----------------+--------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Min` | -+----------------+--------------------------------------------------------------------+ -| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\MinValidator` | -+----------------+--------------------------------------------------------------------+ - -Basic Usage ------------ - -To verify that the "age" field of a class is "18" or greater, you might add -the following: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/EventBundle/Resources/config/validation.yml - Acme\EventBundle\Entity\Participant: - properties: - age: - - Min: { limit: 18, message: You must be 18 or older to enter. } - - .. code-block:: php-annotations - - // src/Acme/EventBundle/Entity/Participant.php - namespace Acme\EventBundle\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - - class Participant - { - /** - * @Assert\Min(limit = "18", message = "You must be 18 or older to enter.") - */ - protected $age; - } - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // src/Acme/EventBundle/Entity/Participant.php - namespace Acme\EventBundle\Entity\Participant; - - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints as Assert; - - class Participant - { - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('age', new Assert\Min(array( - 'limit' => '18', - 'message' => 'You must be 18 or older to enter.', - ))); - } - } - -Options -------- - -limit -~~~~~ - -**type**: ``integer`` [:ref:`default option`] - -This required option is the "min" value. Validation will fail if the given -value is **less** than this min value. - -message -~~~~~~~ - -**type**: ``string`` **default**: ``This value should be {{ limit }} or more`` - -The message that will be shown if the underlying value is less than the `limit`_ -option. - -invalidMessage -~~~~~~~~~~~~~~ - -**type**: ``string`` **default**: ``This value should be a valid number`` - -The message that will be shown if the underlying value is not a number (per -the :phpfunction:`is_numeric` PHP function). diff --git a/reference/constraints/MinLength.rst b/reference/constraints/MinLength.rst deleted file mode 100644 index 50b3350b18e..00000000000 --- a/reference/constraints/MinLength.rst +++ /dev/null @@ -1,106 +0,0 @@ -MinLength -========= - -Validates that the length of a string is at least as long as the given limit. - -+----------------+-------------------------------------------------------------------------+ -| Applies to | :ref:`property or method` | -+----------------+-------------------------------------------------------------------------+ -| Options | - `limit`_ | -| | - `message`_ | -| | - `charset`_ | -+----------------+-------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Validator\\Constraints\\MinLength` | -+----------------+-------------------------------------------------------------------------+ -| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\MinLengthValidator` | -+----------------+-------------------------------------------------------------------------+ - -Basic Usage ------------ - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Blog: - properties: - firstName: - - MinLength: { limit: 3, message: "Your name must have at least {{ limit }} characters." } - - .. code-block:: php-annotations - - // src/Acme/BlogBundle/Entity/Blog.php - namespace Acme\BlogBundle\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - - class Blog - { - /** - * @Assert\MinLength( - * limit=3, - * message="Your name must have at least {{ limit }} characters." - * ) - */ - protected $summary; - } - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Blog.php - namespace Acme\BlogBundle\Entity; - - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints as Assert; - - class Blog - { - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('summary', new Assert\MinLength(array( - 'limit' => 3, - 'message' => 'Your name must have at least {{ limit }} characters.', - ))); - } - } - -Options -------- - -limit -~~~~~ - -**type**: ``integer`` [:ref:`default option`] - -This required option is the "min" value. Validation will fail if the length -of the give string is **less** than this number. - -message -~~~~~~~ - -**type**: ``string`` **default**: ``This value is too short. It should have {{ limit }} characters or more`` - -The message that will be shown if the underlying string has a length that -is shorter than the `limit`_ option. - -charset -~~~~~~~ - -**type**: ``charset`` **default**: ``UTF-8`` - -If the PHP extension "mbstring" is installed, then the PHP function :phpfunction:`mb_strlen` -will be used to calculate the length of the string. The value of the ``charset`` -option is passed as the second argument to that function. diff --git a/reference/constraints/Range.rst b/reference/constraints/Range.rst new file mode 100644 index 00000000000..41c820b61e3 --- /dev/null +++ b/reference/constraints/Range.rst @@ -0,0 +1,141 @@ +Range +===== + +Validates that a given number is *between* some minimum and maximum number. + +.. versionadded:: 2.1 + The Range constraint was added in Symfony 2.1. + ++----------------+---------------------------------------------------------------------+ +| Applies to | :ref:`property or method` | ++----------------+---------------------------------------------------------------------+ +| Options | - `min`_ | +| | - `max`_ | +| | - `minMessage`_ | +| | - `maxMessage`_ | +| | - `invalidMessage`_ | ++----------------+---------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Range` | ++----------------+---------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\RangeValidator` | ++----------------+---------------------------------------------------------------------+ + +Basic Usage +----------- + +To verify that the "height" field of a class is between "120" and "180", you might add +the following: + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/EventBundle/Resources/config/validation.yml + Acme\EventBundle\Entity\Participant: + properties: + height: + - Range: + min: 120 + max: 180 + minMessage: You must be at least 120cm tall to enter + maxMessage: You cannot be taller than 180cm to enter + + .. code-block:: php-annotations + + // src/Acme/EventBundle/Entity/Participant.php + namespace Acme\EventBundle\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + /** + * @Assert\Range( + * min = "120", + * max = "180", + * minMessage = "You must be at least 120cm tall to enter", + * maxMessage = "You cannot be taller than 180cm to enter" + * ) + */ + protected $height; + } + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // src/Acme/EventBundle/Entity/Participant.php + namespace Acme\EventBundle\Entity; + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('height', new Assert\Range(array( + 'min' => 120, + 'max' => 180, + 'minMessage' => 'You must be at least 120cm tall to enter', + 'maxMessage' => 'You cannot be taller than 180cm to enter', + ))); + } + } + +Options +------- + +min +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "min" value. Validation will fail if the given +value is **less** than this min value. + +max +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "max" value. Validation will fail if the given +value is **greater** than this max value. + +minMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should be {{ limit }} or more.`` + +The message that will be shown if the underlying value is less than the `min`_ +option. + +maxMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should be {{ limit }} or less.`` + +The message that will be shown if the underlying value is more than the `max`_ +option. + +invalidMessage +~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should be a valid number.`` + +The message that will be shown if the underlying value is not a number (per +the `is_numeric`_ PHP function). + +.. _`is_numeric`: http://www.php.net/manual/en/function.is-numeric.php diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index e06fe15fc6d..5ec43a1c225 100644 --- a/reference/constraints/UniqueEntity.rst +++ b/reference/constraints/UniqueEntity.rst @@ -11,6 +11,7 @@ using an email address that already exists in the system. | Options | - `fields`_ | | | - `message`_ | | | - `em`_ | +| | - `repositoryMethod`_ | +----------------+-------------------------------------------------------------------------------------+ | Class | :class:`Symfony\\Bridge\\Doctrine\\Validator\\Constraints\\UniqueEntity` | +----------------+-------------------------------------------------------------------------------------+ @@ -132,4 +133,19 @@ em **type**: ``string`` The name of the entity manager to use for making the query to determine the -uniqueness. If it's left blank, the default entity manager will be used. +uniqueness. If it's left blank, the correct entity manager will determined for +this class. For that reason, this option should probably not need to be +used. + +repositoryMethod +~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``findBy`` + +.. versionadded:: 2.1 + The ``repositoryMethod`` option was added in Symfony 2.1. Before, it + always used the ``findBy`` method. + +The name of the repository method to use for making the query to determine the +uniqueness. If it's left blank, the ``findBy`` method will be used. This +method should return a countable result. diff --git a/reference/constraints/UserPassword.rst b/reference/constraints/UserPassword.rst new file mode 100644 index 00000000000..dc0dfcab277 --- /dev/null +++ b/reference/constraints/UserPassword.rst @@ -0,0 +1,106 @@ +UserPassword +============ + +.. versionadded:: 2.1 + This constraint is new in version 2.1. + +.. note:: + + Since Symfony 2.2, the `UserPassword*` classes in the + `Symfony\Component\Security\Core\Validator\Constraint` namespace are + deprecated and will be removed in Symfony 2.3. Please use the + `UserPassword*` classes in the + `Symfony\Component\Security\Core\Validator\Constraints` namespace instead. + +This validates that an input value is equal to the current authenticated +user's password. This is useful in a form where a user can change his password, +but needs to enter his old password for security. + +.. note:: + + This should **not** be used to validate a login form, since this is done + automatically by the security system. + ++----------------+--------------------------------------------------------------------------------------------+ +| Applies to | :ref:`property or method` | ++----------------+--------------------------------------------------------------------------------------------+ +| Options | - `message`_ | ++----------------+--------------------------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Security\\Core\\Validator\\Constraints\\UserPassword` | ++----------------+--------------------------------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Security\\Core\\Validator\\Constraints\\UserPasswordValidator` | ++----------------+--------------------------------------------------------------------------------------------+ + +Basic Usage +----------- + +Suppose you have a `PasswordChange` class, that's used in a form where the +user can change his password by entering his old password and a new password. +This constraint will validate that the old password matches the user's current +password: + +.. configuration-block:: + + .. code-block:: yaml + + # src/UserBundle/Resources/config/validation.yml + Acme\UserBundle\Form\Model\ChangePassword: + properties: + oldPassword: + - Symfony\Component\Security\Core\Validator\Constraints\UserPassword: + message: "Wrong value for your current password" + + .. code-block:: php-annotations + + // src/Acme/UserBundle/Form/Model/ChangePassword.php + namespace Acme\UserBundle\Form\Model; + + use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert; + + class ChangePassword + { + /** + * @SecurityAssert\UserPassword( + * message = "Wrong value for your current password" + * ) + */ + protected $oldPassword; + } + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // src/Acme/UserBundle/Form/Model/ChangePassword.php + namespace Acme\UserBundle\Form\Model; + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert; + + class ChangePassword + { + public static function loadValidatorData(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('oldPassword', new SecurityAssert\UserPassword(array( + 'message' => 'Wrong value for your current password', + ))); + } + } + +Options +------- + +message +~~~~~~~ + +**type**: ``message`` **default**: ``This value should be the user current password`` + +This is the message that's displayed when the underlying string does *not* +match the current user's password. diff --git a/reference/constraints/Valid.rst b/reference/constraints/Valid.rst index 2a847f8b64e..f19942c0e46 100644 --- a/reference/constraints/Valid.rst +++ b/reference/constraints/Valid.rst @@ -54,13 +54,15 @@ an ``Address`` instance in the ``$address`` property. - NotBlank: ~ zipCode: - NotBlank: ~ - - MaxLength: 5 + - Length: + max: 5 Acme\HelloBundle\Entity\Author: properties: firstName: - NotBlank: ~ - - MinLength: 4 + - Length: + min: 4 lastName: - NotBlank: ~ @@ -80,7 +82,7 @@ an ``Address`` instance in the ``$address`` property. /** * @Assert\NotBlank - * @Assert\MaxLength(5) + * @Assert\Length(max = "5") */ protected $zipCode; } @@ -92,7 +94,7 @@ an ``Address`` instance in the ``$address`` property. { /** * @Assert\NotBlank - * @Assert\MinLength(4) + * @Assert\Length(min = "4") */ protected $firstName; @@ -113,14 +115,18 @@ an ``Address`` instance in the ``$address`` property. - 5 + + + - 4 + + + @@ -144,7 +150,9 @@ an ``Address`` instance in the ``$address`` property. { $metadata->addPropertyConstraint('street', new Assert\NotBlank()); $metadata->addPropertyConstraint('zipCode', new Assert\NotBlank()); - $metadata->addPropertyConstraint('zipCode', new Assert\MaxLength(5)); + $metadata->addPropertyConstraint( + 'zipCode', + new Assert\Length(array("max" => 5))); } } @@ -163,7 +171,7 @@ an ``Address`` instance in the ``$address`` property. public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('firstName', new Assert\NotBlank()); - $metadata->addPropertyConstraint('firstName', new Assert\MinLength(4)); + $metadata->addPropertyConstraint('firstName', new Assert\Length(array("min" => 4))); $metadata->addPropertyConstraint('lastName', new Assert\NotBlank()); } } diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc index 08d26f02c63..3d10d637ccf 100644 --- a/reference/constraints/map.rst.inc +++ b/reference/constraints/map.rst.inc @@ -16,8 +16,7 @@ String Constraints ~~~~~~~~~~~~~~~~~~ * :doc:`Email ` -* :doc:`MinLength ` -* :doc:`MaxLength ` +* :doc:`Length ` * :doc:`Url ` * :doc:`Regex ` * :doc:`Ip ` @@ -25,8 +24,7 @@ String Constraints Number Constraints ~~~~~~~~~~~~~~~~~~ -* :doc:`Max ` -* :doc:`Min ` +* :doc:`Range ` Date Constraints ~~~~~~~~~~~~~~~~ @@ -40,6 +38,7 @@ Collection Constraints * :doc:`Choice ` * :doc:`Collection ` +* :doc:`Count ` * :doc:`UniqueEntity ` * :doc:`Language ` * :doc:`Locale ` @@ -51,9 +50,16 @@ File Constraints * :doc:`File ` * :doc:`Image ` +Financial Constraints +~~~~~~~~~~~~~~~~~~~~~ + +* :doc:`CardScheme ` +* :doc:`Luhn ` + Other Constraints ~~~~~~~~~~~~~~~~~ * :doc:`Callback ` * :doc:`All ` -* :doc:`Valid ` \ No newline at end of file +* :doc:`UserPassword ` +* :doc:`Valid ` diff --git a/reference/dic_tags.rst b/reference/dic_tags.rst index 5188362b9c6..79d44d66d8f 100644 --- a/reference/dic_tags.rst +++ b/reference/dic_tags.rst @@ -28,6 +28,8 @@ the AsseticBundle has several tags that aren't listed here. +-----------------------------------+---------------------------------------------------------------------------+ | `kernel.event_listener`_ | Listen to different events/hooks in Symfony | +-----------------------------------+---------------------------------------------------------------------------+ +| `kernel.event_subscriber`_ | To subscribe to a set of different events/hooks in Symfony | ++-----------------------------------+---------------------------------------------------------------------------+ | `monolog.logger`_ | Logging with a custom logging channel | +-----------------------------------+---------------------------------------------------------------------------+ | `monolog.processor`_ | Add a custom processor for logging | @@ -40,6 +42,10 @@ the AsseticBundle has several tags that aren't listed here. +-----------------------------------+---------------------------------------------------------------------------+ | `security.listener.factory`_ | Necessary when creating a custom authentication system | +-----------------------------------+---------------------------------------------------------------------------+ +| `serializer.encoder`_ | Register a new encoder in the ``serializer`` service | ++-----------------------------------+---------------------------------------------------------------------------+ +| `serializer.normalizer`_ | Register a new normalizer in the ``serializer`` service | ++-----------------------------------+---------------------------------------------------------------------------+ | `swiftmailer.plugin`_ | Register a custom SwiftMailer Plugin | +-----------------------------------+---------------------------------------------------------------------------+ | `templating.helper`_ | Make your service available in PHP templates | @@ -48,6 +54,8 @@ the AsseticBundle has several tags that aren't listed here. +-----------------------------------+---------------------------------------------------------------------------+ | `twig.extension`_ | Register a custom Twig Extension | +-----------------------------------+---------------------------------------------------------------------------+ +| `twig.loader`_ | Register a custom service that loads Twig templates | ++-----------------------------------+---------------------------------------------------------------------------+ | `validator.constraint_validator`_ | Create your own custom validation constraint | +-----------------------------------+---------------------------------------------------------------------------+ | `validator.initializer`_ | Register a service that initializes objects before validation | @@ -93,7 +101,7 @@ the interface directly:: class MyFormTypeExtension extends AbstractTypeExtension { // ... fill in whatever methods you want to override - // like buildForm(), buildView(), buildViewBottomUp(), getDefaultOptions() or getAllowedOptionValues() + // like buildForm(), buildView(), finishView(), setDefaultOptions() } In order for Symfony to know about your form extension and use it, give it @@ -243,13 +251,15 @@ kernel.request +-------------------------------------------------------------------------------------------+-----------+ | :class:`Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener` | 1024 | +-------------------------------------------------------------------------------------------+-----------+ -| :class:`Symfony\\Bundle\\FrameworkBundle\\EventListener\\RouterListener` | 0 and 255 | -+-------------------------------------------------------------------------------------------+-----------+ | :class:`Symfony\\Bundle\\FrameworkBundle\\EventListener\\TestSessionListener` | 192 | +-------------------------------------------------------------------------------------------+-----------+ | :class:`Symfony\\Bundle\\FrameworkBundle\\EventListener\\SessionListener` | 128 | +-------------------------------------------------------------------------------------------+-----------+ -| :class:`Symfony\\Component\\Security\\Http\\Firewall` | 64 | +| :class:`Symfony\\Component\\HttpKernel\\EventListener\\RouterListener` | 32 | ++-------------------------------------------------------------------------------------------+-----------+ +| :class:`Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener` | 16 | ++-------------------------------------------------------------------------------------------+-----------+ +| :class:`Symfony\\Component\\Security\\Http\\Firewall` | 8 | +-------------------------------------------------------------------------------------------+-----------+ kernel.controller @@ -279,6 +289,8 @@ kernel.response +-------------------------------------------------------------------------------------------+----------+ | :class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener` | -128 | +-------------------------------------------------------------------------------------------+----------+ +| :class:`Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener` | -1024 | ++-------------------------------------------------------------------------------------------+----------+ kernel.exception ................ @@ -291,6 +303,61 @@ kernel.exception | :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener` | -128 | +-------------------------------------------------------------------------------------------+----------+ +kernel.terminate +................ + ++-------------------------------------------------------------------------------------------+----------+ +| Listener Class Name | Priority | ++-------------------------------------------------------------------------------------------+----------+ +| :class:`Symfony\\Bundle\\SwiftmailerBundle\\EventListener\\EmailSenderListener` | 0 | ++-------------------------------------------------------------------------------------------+----------+ + +.. _dic-tags-kernel-event-subscriber: + +kernel.event_subscriber +----------------------- + +**Purpose**: To subscribe to a set of different events/hooks in Symfony + +.. versionadded:: 2.1 + The ability to add kernel event subscribers is new to 2.1. + +To enable a custom subscriber, add it as a regular service in one of your +configuration, and tag it with ``kernel.event_subscriber``: + +.. configuration-block:: + + .. code-block:: yaml + + services: + kernel.subscriber.your_subscriber_name: + class: Fully\Qualified\Subscriber\Class\Name + tags: + - { name: kernel.event_subscriber } + + .. code-block:: xml + + + + + + .. code-block:: php + + $container + ->register('kernel.subscriber.your_subscriber_name', 'Fully\Qualified\Subscriber\Class\Name') + ->addTag('kernel.event_subscriber') + ; + +.. note:: + + Your service must implement the :class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface` + interface. + +.. note:: + + If your service is created by a factory, you **MUST** correctly set the ``class`` + parameter for this tag to work correctly. + .. _dic_tags-monolog: monolog.logger @@ -497,6 +564,30 @@ is used behind the scenes to determine if the user should have access. The For more information, read the cookbook article: :doc:`/cookbook/security/voters`. +.. _reference-dic-tags-serializer-encoder: + +serializer.encoder +------------------ + +**Purpose**: Register a new encoder in the ``serializer`` service + +The class that's tagged should implement the :class:`Symfony\\Component\\Serializer\\Encoder\\EncoderInterface` +and :class:`Symfony\\Component\\Serializer\\Encoder\\DecoderInterface`. + +For more details, see :doc:`/cookbook/serializer`. + +.. _reference-dic-tags-serializer-normalizer: + +serializer.normalizer +--------------------- + +**Purpose**: Register a new normalizer in the Serializer service + +The class that's tagged should implement the :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface` +and :class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface`. + +For more details, see :doc:`/cookbook/serializer`. + swiftmailer.plugin ------------------ @@ -684,6 +775,39 @@ also have to be added as regular services: ->addTag('twig.extension') ; +twig.loader +----------- + +**Purpose**: Register a custom service that loads Twig templates + +By default, Symfony uses only one `Twig Loader`_ - +:class:`Symfony\\Bundle\\TwigBundle\\Loader\\FilesystemLoader`. If you need +to load Twig templates from another resource, you can create a service for +the new loader and tag it with ``twig.loader``: + +.. configuration-block:: + + .. code-block:: yaml + + services: + acme.demo_bundle.loader.some_twig_loader: + class: Acme\DemoBundle\Loader\SomeTwigLoader + tags: + - { name: twig.loader } + + .. code-block:: xml + + + + + + .. code-block:: php + + $container + ->register('acme.demo_bundle.loader.some_twig_loader', 'Acme\DemoBundle\Loader\SomeTwigLoader') + ->addTag('twig.loader') + ; + validator.constraint_validator ------------------------------ @@ -712,5 +836,6 @@ For an example, see the ``EntityInitializer`` class inside the Doctrine Bridge. .. _`Twig's documentation`: http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension .. _`Twig official extension repository`: https://github.com/fabpot/Twig-extensions -.. _`KernelEvents`: https://github.com/symfony/symfony/blob/2.0/src/Symfony/Component/HttpKernel/KernelEvents.php +.. _`KernelEvents`: https://github.com/symfony/symfony/blob/2.2/src/Symfony/Component/HttpKernel/KernelEvents.php .. _`SwiftMailer's Plugin Documentation`: http://swiftmailer.org/docs/plugins.html +.. _`Twig Loader`: http://twig.sensiolabs.org/doc/api.html#loaders diff --git a/reference/forms/twig_reference.rst b/reference/forms/twig_reference.rst index ba8d8fa88ef..845df8ee5be 100644 --- a/reference/forms/twig_reference.rst +++ b/reference/forms/twig_reference.rst @@ -37,8 +37,8 @@ label you want to display as the second argument. {{ form_label(form.name) }} {# The two following syntaxes are equivalent #} - {{ form_label(form.name, 'Your Name', { 'attr': {'class': 'foo'} }) }} - {{ form_label(form.name, null, { 'label': 'Your name', 'attr': {'class': 'foo'} }) }} + {{ form_label(form.name, 'Your Name', {'label_attr': {'class': 'foo'}}) }} + {{ form_label(form.name, null, {'label': 'Your name', 'label_attr': {'class': 'foo'}}) }} See ":ref:`twig-reference-form-variables`" to learn about the ``variables`` argument. @@ -68,7 +68,7 @@ or collection of fields, each underlying form row will be rendered. .. code-block:: jinja {# render a widget, but add a "foo" class to it #} - {{ form_widget(form.name, { 'attr': {'class': 'foo'} }) }} + {{ form_widget(form.name, {'attr': {'class': 'foo'}}) }} The second argument to ``form_widget`` is an array of variables. The most common variable is ``attr``, which is an array of HTML attributes to apply @@ -91,7 +91,7 @@ label, errors and widget. .. code-block:: jinja {# render a field row, but display a label with text "foo" #} - {{ form_row(form.name, { 'label': 'foo' }) }} + {{ form_row(form.name, {'label': 'foo'}) }} The second argument to ``form_row`` is an array of variables. The templates provided in Symfony only allow to override the label as shown in the example @@ -151,18 +151,25 @@ not be immediately clear, but they're incredibly powerful. Whenever you render any part of a form, the block that renders it makes use of a number of variables. By default, these blocks live inside `form_div_layout.html.twig`_. -Look at the ``generic_label`` as an example: +Look at the ``form_label`` as an example: .. code-block:: jinja - {% block generic_label %} + {% block form_label %} + {% if not compound %} + {% set label_attr = label_attr|merge({'for': id}) %} + {% endif %} {% if required %} - {% set attr = attr|merge({'class': attr.class|default('') ~ ' required'}) %} + {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} {% endif %} - {{ label|trans }} - {% endblock %} + {% if label is empty %} + {% set label = name|humanize %} + {% endif %} + {{ label|trans({}, translation_domain) }} + {% endblock form_label %} -This block makes use of 3 variables: ``required``, ``attr`` and ``label``. +This block makes use of several variables: ``compound``, ``label_attr``, ``required``, +``label``, ``name`` and ``translation_domain``. These variables are made available by the form rendering system. But more importantly, these are the variables that you can override when calling ``form_label`` (since in this example, you're rendering the label). @@ -223,6 +230,10 @@ object: get('name')->vars['label'] ?> +.. versionadded:: 2.1 + The ``valid``, ``label_attr``, ``compound``, and ``disabled`` variables + are new in Symfony 2.1. + +-----------------+-----------------------------------------------------------------------------------------+ | Variable | Usage | +=================+=========================================================================================+ @@ -236,10 +247,15 @@ object: | ``errors`` | An array of any errors attached to *this* specific field (e.g. ``form.title.errors``). | | | Note that you can't use ``form.errors`` to determine if a form is valid, | | | since this only returns "global" errors: some individual fields may have errors | +| | Instead, use the ``valid`` option | ++-----------------+-----------------------------------------------------------------------------------------+ +| ``valid`` | Returns ``true`` or ``false`` depending on whether the whole form is valid | +-----------------+-----------------------------------------------------------------------------------------+ | ``value`` | The value that will be used when rendering (commonly the ``value`` HTML attribute) | +-----------------+-----------------------------------------------------------------------------------------+ -| ``read_only`` | If ``true``, ``disabled="disabled"`` is added to the field | +| ``read_only`` | If ``true``, ``readonly="readonly"`` is added to the field | ++-----------------+-----------------------------------------------------------------------------------------+ +| ``disabled`` | If ``true``, ``disabled="disabled"`` is added to the field | +-----------------+-----------------------------------------------------------------------------------------+ | ``required`` | If ``true``, a ``required`` attribute is added to the field to activate HTML5 | | | validation. Additionally, a ``required`` class is added to the label. | @@ -255,5 +271,10 @@ object: +-----------------+-----------------------------------------------------------------------------------------+ | ``attr`` | A key-value array that will be rendered as HTML attributes on the field | +-----------------+-----------------------------------------------------------------------------------------+ +| ``label_attr`` | A key-value array that will be rendered as HTML attributes on the label | ++-----------------+-----------------------------------------------------------------------------------------+ +| ``compound`` | Whether or not a field is actually a holder for a group of children fields | +| | (for example, a ``choice`` field, which is actually a group of checkboxes | ++-----------------+-----------------------------------------------------------------------------------------+ -.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.0/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig diff --git a/reference/forms/types/birthday.rst b/reference/forms/types/birthday.rst index 2f9a9a8eb26..784d9bf3a43 100644 --- a/reference/forms/types/birthday.rst +++ b/reference/forms/types/birthday.rst @@ -31,6 +31,8 @@ option defaults to 120 years ago to the current year. | | - `user_timezone`_ | | | - `invalid_message`_ | | | - `invalid_message_parameters`_ | +| | - `read_only`_ | +| | - `disabled`_ | +----------------------+------------------------------------------------------------------------------------------------------------------------+ | Parent type | :doc:`date` | +----------------------+------------------------------------------------------------------------------------------------------------------------+ @@ -75,3 +77,6 @@ These options inherit from the :doc:`date` type: .. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc +.. include:: /reference/forms/types/options/read_only.rst.inc + +.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/reference/forms/types/checkbox.rst b/reference/forms/types/checkbox.rst index abd7bd43a13..dec426115e1 100644 --- a/reference/forms/types/checkbox.rst +++ b/reference/forms/types/checkbox.rst @@ -16,6 +16,7 @@ if the box is unchecked, the value will be set to false. | Inherited | - `required`_ | | options | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+------------------------------------------------------------------------+ | Parent type | :doc:`field` | @@ -55,4 +56,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/choice.rst b/reference/forms/types/choice.rst index c0c72982c70..3608b6efdac 100644 --- a/reference/forms/types/choice.rst +++ b/reference/forms/types/choice.rst @@ -25,6 +25,7 @@ option. | Inherited | - `required`_ | | options | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+-----------------------------------------------------------------------------+ | Parent type | :doc:`form` (if expanded), ``field`` otherwise | @@ -118,4 +119,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/collection.rst b/reference/forms/types/collection.rst index cc429f1d679..6c9acc98a69 100644 --- a/reference/forms/types/collection.rst +++ b/reference/forms/types/collection.rst @@ -18,6 +18,7 @@ forms, which is useful when creating forms that expose one-to-many relationships | | - `allow_add`_ | | | - `allow_delete`_ | | | - `prototype`_ | +| | - `prototype_name`_ | +-------------+-----------------------------------------------------------------------------+ | Inherited | - `label`_ | | options | - `error_bubbling`_ | @@ -128,9 +129,9 @@ will look like this: .. code-block:: html - + -By replacing ``$$name$$`` with some unique value (e.g. ``2``), +By replacing ``__name__`` with some unique value (e.g. ``2``), you can build and insert new HTML fields into your form. Using jQuery, a simple example might look like this. If you're rendering @@ -171,10 +172,10 @@ you need is the JavaScript: // grab the prototype template var newWidget = emailList.attr('data-prototype'); - // replace the "$$name$$" used in the id and name of the prototype + // replace the "__name__" used in the id and name of the prototype // with a number that's unique to your emails // end name attribute looks like name="contact[emails][2]" - newWidget = newWidget.replace(/\$\$name\$\$/g, emailCount); + newWidget = newWidget.replace(/__name__/g, emailCount); emailCount++; // create a new list element and add it to the list @@ -284,8 +285,8 @@ This option is useful when using the `allow_add`_ option. If ``true`` (and if `allow_add`_ is also ``true``), a special "prototype" attribute will be available so that you can render a "template" example on your page of what a new element should look like. The ``name`` attribute given to this element -is ``$$name$$``. This allows you to add a "add another" button via JavaScript -which reads the prototype, replaces ``$$name$$`` with some unique name or +is ``__name__``. This allows you to add a "add another" button via JavaScript +which reads the prototype, replaces ``__name__`` with some unique name or number, and render it inside your form. When submitted, it will be added to your underlying array due to the `allow_add`_ option. @@ -300,7 +301,7 @@ collection field: .. code-block:: php - row($form['emails']->get('prototype')) ?> + row($form['emails']->vars['prototype']) ?> Note that all you really need is the "widget", but depending on how you're rendering your form, having the entire "form row" may be easier for you. @@ -314,6 +315,18 @@ rendering your form, having the entire "form row" may be easier for you. For details on how to actually use this option, see the above example as well as :ref:`cookbook-form-collections-new-prototype`. +prototype_name +~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ``prototype_name`` option was added in Symfony 2.1 + +**type**: ``String`` **default**: ``__name__`` + +If you have several collections in your form, or worse, nested collections +you may want to change the placeholder so that unrelated placeholders are not +replaced with the same value. + Inherited options ----------------- diff --git a/reference/forms/types/country.rst b/reference/forms/types/country.rst index d83aad0e827..05fd40b4ed3 100644 --- a/reference/forms/types/country.rst +++ b/reference/forms/types/country.rst @@ -30,6 +30,7 @@ you should just use the ``choice`` type directly. | | - `required`_ | | | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | +-------------+-----------------------------------------------------------------------+ | Parent type | :doc:`choice` | +-------------+-----------------------------------------------------------------------+ @@ -58,3 +59,5 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/label.rst.inc .. include:: /reference/forms/types/options/read_only.rst.inc + +.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/reference/forms/types/date.rst b/reference/forms/types/date.rst index ea7f9dac43f..3f249ffbff1 100644 --- a/reference/forms/types/date.rst +++ b/reference/forms/types/date.rst @@ -32,6 +32,8 @@ day, and year) or three select boxes (see the `widget_` option). +----------------------+-----------------------------------------------------------------------------+ | Inherited | - `invalid_message`_ | | options | - `invalid_message_parameters`_ | +| | - `read_only`_ | +| | - `disabled`_ | +----------------------+-----------------------------------------------------------------------------+ | Parent type | ``field`` (if text), ``form`` otherwise | +----------------------+-----------------------------------------------------------------------------+ @@ -120,4 +122,8 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/invalid_message.rst.inc -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc \ No newline at end of file +.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc + +.. include:: /reference/forms/types/options/read_only.rst.inc + +.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst index bc4401c43fa..8ce93a153d2 100644 --- a/reference/forms/types/datetime.rst +++ b/reference/forms/types/datetime.rst @@ -31,6 +31,8 @@ data can be a ``DateTime`` object, a string, a timestamp or an array. +----------------------+-----------------------------------------------------------------------------+ | Inherited | - `invalid_message`_ | | options | - `invalid_message_parameters`_ | +| | - `read_only`_ | +| | - `disabled`_ | +----------------------+-----------------------------------------------------------------------------+ | Parent type | :doc:`form` | +----------------------+-----------------------------------------------------------------------------+ @@ -104,4 +106,8 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/invalid_message.rst.inc -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc \ No newline at end of file +.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc + +.. include:: /reference/forms/types/options/read_only.rst.inc + +.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/reference/forms/types/email.rst b/reference/forms/types/email.rst index c485c7ec5b5..1dcd4dcfe77 100644 --- a/reference/forms/types/email.rst +++ b/reference/forms/types/email.rst @@ -15,6 +15,7 @@ The ``email`` field is a text field that is rendered using the HTML5 | | - `label`_ | | | - `trim`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+---------------------------------------------------------------------+ | Parent type | :doc:`field` | @@ -37,4 +38,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc -.. include:: /reference/forms/types/options/error_bubbling.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + +.. include:: /reference/forms/types/options/error_bubbling.rst.inc \ No newline at end of file diff --git a/reference/forms/types/entity.rst b/reference/forms/types/entity.rst index f2f7a2cfc9b..9144c6364b6 100644 --- a/reference/forms/types/entity.rst +++ b/reference/forms/types/entity.rst @@ -14,6 +14,7 @@ objects from the database. +-------------+------------------------------------------------------------------+ | Options | - `class`_ | | | - `property`_ | +| | - `group_by`_ | | | - `query_builder`_ | | | - `em`_ | +-------------+------------------------------------------------------------------+ @@ -24,6 +25,7 @@ objects from the database. | | - `preferred_choices`_ | | | - `empty_value`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+------------------------------------------------------------------+ | Parent type | :doc:`choice` | @@ -89,6 +91,17 @@ This is the property that should be used for displaying the entities as text in the HTML element. If left blank, the entity object will be cast into a string and so must have a ``__toString()`` method. +group_by +~~~~~~~~ + +**type**: ``string`` + +This is a property path (e.g. ``author.name``) used to organize the +available choices in groups. It only works when rendered as a select tag +and does so by adding optgroup tags around options. Choices that do not +return a value for this property path are rendered directly under the +select tag, without a surrounding optgroup. + query_builder ~~~~~~~~~~~~~ @@ -129,4 +142,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/field.rst b/reference/forms/types/field.rst index 51b7dec035c..ee06911d6bb 100644 --- a/reference/forms/types/field.rst +++ b/reference/forms/types/field.rst @@ -4,19 +4,5 @@ The Abstract "field" Type ========================= -The ``field`` form type is not an actual field type you use, but rather -functions as the parent field type for many other fields. - -The ``field`` type predefines a couple of options: - -.. include:: /reference/forms/types/options/data.rst.inc - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/trim.rst.inc - -.. include:: /reference/forms/types/options/property_path.rst.inc - -.. include:: /reference/forms/types/options/attr.rst.inc +The ``field`` form type is deprecated as of Symfony 2.1. +Please use the :doc:`Form field type` instead. diff --git a/reference/forms/types/file.rst b/reference/forms/types/file.rst index 06d6c50bba7..1b101631069 100644 --- a/reference/forms/types/file.rst +++ b/reference/forms/types/file.rst @@ -12,6 +12,7 @@ The ``file`` type represents a file input in your form. | Inherited | - `required`_ | | options | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+---------------------------------------------------------------------+ | Parent type | :doc:`form` | @@ -89,4 +90,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc -.. include:: /reference/forms/types/options/error_bubbling.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + +.. include:: /reference/forms/types/options/error_bubbling.rst.inc \ No newline at end of file diff --git a/reference/forms/types/form.rst b/reference/forms/types/form.rst index 1121a0befb2..308ad8b93b5 100644 --- a/reference/forms/types/form.rst +++ b/reference/forms/types/form.rst @@ -4,4 +4,27 @@ form Field Type =============== -See :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType`. \ No newline at end of file +See :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType`. + +The ``form`` type predefines a couple of options that are then available +on all fields. + +.. include:: /reference/forms/types/options/data.rst.inc + +.. include:: /reference/forms/types/options/required.rst.inc + +.. include:: /reference/forms/types/options/constraints.rst.inc + +.. include:: /reference/forms/types/options/cascade_validation.rst.inc + +.. include:: /reference/forms/types/options/read_only.rst.inc + +.. include:: /reference/forms/types/options/trim.rst.inc + +.. include:: /reference/forms/types/options/mapped.rst.inc + +.. include:: /reference/forms/types/options/property_path.rst.inc + +.. include:: /reference/forms/types/options/attr.rst.inc + +.. include:: /reference/forms/types/options/translation_domain.rst.inc diff --git a/reference/forms/types/hidden.rst b/reference/forms/types/hidden.rst index f5c609060f2..8ee7c542da5 100644 --- a/reference/forms/types/hidden.rst +++ b/reference/forms/types/hidden.rst @@ -24,4 +24,4 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/data.rst.inc -.. include:: /reference/forms/types/options/property_path.rst.inc +.. include:: /reference/forms/types/options/property_path.rst.inc \ No newline at end of file diff --git a/reference/forms/types/integer.rst b/reference/forms/types/integer.rst index 2fe33fcb282..7310f2b8ec3 100644 --- a/reference/forms/types/integer.rst +++ b/reference/forms/types/integer.rst @@ -21,6 +21,7 @@ integers. By default, all non-integer values (e.g. 6.78) will round down (e.g. 6 | Inherited | - `required`_ | | options | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | | | - `invalid_message`_ | | | - `invalid_message_parameters`_ | @@ -67,6 +68,8 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc .. include:: /reference/forms/types/options/invalid_message.rst.inc diff --git a/reference/forms/types/language.rst b/reference/forms/types/language.rst index a0865e7ff6b..8996254266a 100644 --- a/reference/forms/types/language.rst +++ b/reference/forms/types/language.rst @@ -31,6 +31,7 @@ you should just use the ``choice`` type directly. | | - `required`_ | | | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | +-------------+------------------------------------------------------------------------+ | Parent type | :doc:`choice` | +-------------+------------------------------------------------------------------------+ @@ -59,3 +60,5 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/label.rst.inc .. include:: /reference/forms/types/options/read_only.rst.inc + +.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/reference/forms/types/locale.rst b/reference/forms/types/locale.rst index e8a970dca5d..475dde16d2f 100644 --- a/reference/forms/types/locale.rst +++ b/reference/forms/types/locale.rst @@ -32,6 +32,7 @@ you should just use the ``choice`` type directly. | | - `required`_ | | | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | +-------------+------------------------------------------------------------------------+ | Parent type | :doc:`choice` | +-------------+------------------------------------------------------------------------+ @@ -60,3 +61,5 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/label.rst.inc .. include:: /reference/forms/types/options/read_only.rst.inc + +.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/reference/forms/types/money.rst b/reference/forms/types/money.rst index 577a18e2deb..d5a9a785335 100644 --- a/reference/forms/types/money.rst +++ b/reference/forms/types/money.rst @@ -22,6 +22,7 @@ how the input and output of the data is handled. | Inherited | - `required`_ | | options | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | | | - `invalid_message`_ | | | - `invalid_message_parameters`_ | @@ -88,6 +89,8 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc .. include:: /reference/forms/types/options/invalid_message.rst.inc diff --git a/reference/forms/types/number.rst b/reference/forms/types/number.rst index fb2ddde3ed4..847d1564ec3 100644 --- a/reference/forms/types/number.rst +++ b/reference/forms/types/number.rst @@ -18,6 +18,7 @@ you want to use for your number. | Inherited | - `required`_ | | options | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | | | - `invalid_message`_ | | | - `invalid_message_parameters`_ | @@ -86,6 +87,8 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc .. include:: /reference/forms/types/options/invalid_message.rst.inc diff --git a/reference/forms/types/options/by_reference.rst.inc b/reference/forms/types/options/by_reference.rst.inc index 0157ac50417..4df684682fe 100644 --- a/reference/forms/types/options/by_reference.rst.inc +++ b/reference/forms/types/options/by_reference.rst.inc @@ -20,7 +20,7 @@ To explain this further, here's a simple example:: ) If ``by_reference`` is true, the following takes place behind the scenes -when you call ``bindRequest`` on the form:: +when you call ``bind`` on the form:: $article->setTitle('...'); $article->getAuthor()->setName('...'); diff --git a/reference/forms/types/options/cascade_validation.rst.inc b/reference/forms/types/options/cascade_validation.rst.inc new file mode 100644 index 00000000000..60d1bd50593 --- /dev/null +++ b/reference/forms/types/options/cascade_validation.rst.inc @@ -0,0 +1,16 @@ +cascade_validation +~~~~~~~~~~~~~~~~~~ + +**type**: Boolean **default**: false + +Set this option to ``true`` to force validation on embedded form types. +For example, if you have a ``ProductType`` with an embedded ``CategoryType``, +setting ``cascade_validation`` to ``true`` on ``ProductType`` will cause +the data from ``CategoryType`` to also be validated. + +Instead of using this option, you can also use the ``Valid`` constraint in +your model to force validation on a child object stored on a property. + + + + diff --git a/reference/forms/types/options/constraints.rst.inc b/reference/forms/types/options/constraints.rst.inc new file mode 100644 index 00000000000..0a09d94be4b --- /dev/null +++ b/reference/forms/types/options/constraints.rst.inc @@ -0,0 +1,9 @@ +constraints +~~~~~~~~~~~ + +**type**: array or :class:`Symfony\\Component\\Validator\\Constraint` **default**: ``null`` + +Allows you to attach one or more validation constraints to a specific field. +For more information, see :ref:`Adding Validation`. +This option is added in the :class:`Symfony\\Component\\Form\\Extension\\Validator\\Type\\FormTypeValidatorExtension` +form extension. diff --git a/reference/forms/types/options/date_widget.rst.inc b/reference/forms/types/options/date_widget.rst.inc index 8fc89e3d09e..f5722fd8090 100644 --- a/reference/forms/types/options/date_widget.rst.inc +++ b/reference/forms/types/options/date_widget.rst.inc @@ -10,5 +10,5 @@ The basic way in which this field should be rendered. Can be one of the followin * ``text``: renders a three field input of type text (month, day, year). -* ``single_text``: renders a single input of type text. User's input is validated - based on the `format`_ option. +* ``single_text``: renders a single input of type date (text in Symfony 2.0). User's + input is validated based on the `format`_ option. diff --git a/reference/forms/types/options/disabled.rst.inc b/reference/forms/types/options/disabled.rst.inc index 4d24ee0c5aa..df3b93a6357 100644 --- a/reference/forms/types/options/disabled.rst.inc +++ b/reference/forms/types/options/disabled.rst.inc @@ -1,21 +1,11 @@ disabled ~~~~~~~~ + +.. versionadded:: 2.1 + The ``disabled`` option is new in version 2.1 + **type**: ``boolean`` **default**: ``false`` If you don't want a user to modify the value of a field, you can set the disabled option to true. Any submitted value will be ignored. -.. code-block:: php - - use Symfony\Component\Form\TextField - - $field = new TextField('status', array( - 'data' => 'Old data', - 'disabled' => true, - )); - $field->submit('New data'); - - // prints "Old data" - echo $field->getData(); - - diff --git a/reference/forms/types/options/mapped.rst.inc b/reference/forms/types/options/mapped.rst.inc new file mode 100644 index 00000000000..a68b3113b00 --- /dev/null +++ b/reference/forms/types/options/mapped.rst.inc @@ -0,0 +1,7 @@ +mapped +~~~~~~ + +**type**: ``boolean`` + +If you wish the field to be ignored when reading or writing to the object, you +can set the ``mapped`` option to ``false`` diff --git a/reference/forms/types/options/property_path.rst.inc b/reference/forms/types/options/property_path.rst.inc index 58f1d4d4a96..7bdbe3156d8 100644 --- a/reference/forms/types/options/property_path.rst.inc +++ b/reference/forms/types/options/property_path.rst.inc @@ -11,4 +11,9 @@ you can set the ``property_path`` option. Its default value is the field's name. If you wish the field to be ignored when reading or writing to the object -you can set the ``property_path`` option to ``false`` +you can set the ``property_path`` option to ``false``, but using +``property_path`` for this purpose is deprecated, you should do it the way +described below: + +.. versionadded:: 2.1 + Since 2.1, the ``mapped`` option has been added for this use-case. diff --git a/reference/forms/types/options/read_only.rst.inc b/reference/forms/types/options/read_only.rst.inc index f0b91763eb9..73f35688e00 100644 --- a/reference/forms/types/options/read_only.rst.inc +++ b/reference/forms/types/options/read_only.rst.inc @@ -1,7 +1,12 @@ read_only ~~~~~~~~~ +.. versionadded:: 2.1 + The ``read_only`` option was changed in 2.1 to render as a ``readonly`` + HTML attribute. Previously, it rendered as a ``disabled`` attribute. + Use the `disabled`_ option if you need the old behavior. + **type**: ``Boolean`` **default**: ``false`` -If this option is true, the field will be rendered with the ``disabled`` +If this option is true, the field will be rendered with the ``readonly`` attribute so that the field is not editable. \ No newline at end of file diff --git a/reference/forms/types/options/translation_domain.rst.inc b/reference/forms/types/options/translation_domain.rst.inc new file mode 100644 index 00000000000..5317f2c372e --- /dev/null +++ b/reference/forms/types/options/translation_domain.rst.inc @@ -0,0 +1,7 @@ +translation_domain +~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``messages`` + +This is the translation domain that will be used for any labels or options +that are rendered for this field. diff --git a/reference/forms/types/options/with_seconds.rst.inc b/reference/forms/types/options/with_seconds.rst.inc index ad387c07a63..684a748fb3e 100644 --- a/reference/forms/types/options/with_seconds.rst.inc +++ b/reference/forms/types/options/with_seconds.rst.inc @@ -4,7 +4,4 @@ with_seconds **type**: ``Boolean`` **default**: ``false`` Whether or not to include seconds in the input. This will result in an additional -input to capture seconds. This may not work as expected in Symfony 2.0 due -to a `known bug`_. - -.. _`known bug`: https://github.com/symfony/symfony/pull/3860 \ No newline at end of file +input to capture seconds. \ No newline at end of file diff --git a/reference/forms/types/password.rst b/reference/forms/types/password.rst index cdea69c117f..67b1f1afd6c 100644 --- a/reference/forms/types/password.rst +++ b/reference/forms/types/password.rst @@ -16,6 +16,7 @@ The ``password`` field renders an input password text box. | | - `label`_ | | | - `trim`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+------------------------------------------------------------------------+ | Parent type | :doc:`text` | @@ -53,4 +54,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc \ No newline at end of file diff --git a/reference/forms/types/percent.rst b/reference/forms/types/percent.rst index a316fe5f11e..b247ca5ab18 100644 --- a/reference/forms/types/percent.rst +++ b/reference/forms/types/percent.rst @@ -21,6 +21,7 @@ This field adds a percentage sign "``%``" after the input box. | Inherited | - `required`_ | | options | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | | | - `invalid_message`_ | | | - `invalid_message_parameters`_ | @@ -72,6 +73,8 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc .. include:: /reference/forms/types/options/invalid_message.rst.inc diff --git a/reference/forms/types/radio.rst b/reference/forms/types/radio.rst index d6e29d41fd7..d524b85821b 100644 --- a/reference/forms/types/radio.rst +++ b/reference/forms/types/radio.rst @@ -20,6 +20,7 @@ If you want to have a Boolean field, use :doc:`checkbox` | @@ -49,4 +50,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/repeated.rst b/reference/forms/types/repeated.rst index 95aec9456bd..48c54389eea 100644 --- a/reference/forms/types/repeated.rst +++ b/reference/forms/types/repeated.rst @@ -14,6 +14,8 @@ accuracy. +-------------+------------------------------------------------------------------------+ | Options | - `type`_ | | | - `options`_ | +| | - `first_options`_ | +| | - `second_options`_ | | | - `first_name`_ | | | - `second_name`_ | +-------------+------------------------------------------------------------------------+ @@ -34,7 +36,10 @@ Example Usage $builder->add('password', 'repeated', array( 'type' => 'password', 'invalid_message' => 'The password fields must match.', - 'options' => array('label' => 'Password'), + 'options' => array('attr' => array('class' => 'password-field')), + 'required' => true, + 'first_options' => array('label' => 'Password'), + 'second_options' => array('label' => 'Repeat Password'), )); Upon a successful form submit, the value entered into both of the "password" @@ -70,13 +75,11 @@ To render each field individually, use something like this: .. code-block:: jinja - {{ form_errors(form.password) }} {{ form_row(form.password.first) }} {{ form_row(form.password.second) }} .. code-block:: php - errors($form['password']) ?> row($form['password']['first']) ?> row($form['password']['second']) ?> @@ -118,6 +121,35 @@ For example, if the ``type`` option is set to ``password``, this array might contain the options ``always_empty`` or ``required`` - both options that are supported by the ``password`` field type. +first_options +~~~~~~~~~~~~~ + +**type**: ``array`` **default**: ``array()`` + +.. versionadded:: 2.1 + The ``first_options`` option is new in Symfony 2.1. + +Additional options (will be merged into `options` above) that should be passed +*only* to the first field. This is especially useful for customizing the +label:: + + $builder->add('password', 'repeated', array( + 'first_options' => array('label' => 'Password'), + 'second_options' => array('label' => 'Repeat Password'), + )); + +second_options +~~~~~~~~~~~~~~ + +**type**: ``array`` **default**: ``array()`` + +.. versionadded:: 2.1 + The ``second_options`` option is new in Symfony 2.1. + +Additional options (will be merged into `options` above) that should be passed +*only* to the second field. This is especially useful for customizing the +label (see `first_options`_). + first_name ~~~~~~~~~~ diff --git a/reference/forms/types/search.rst b/reference/forms/types/search.rst index 1b0f59bbce7..8309d8a04a9 100644 --- a/reference/forms/types/search.rst +++ b/reference/forms/types/search.rst @@ -17,6 +17,7 @@ Read about the input search field at `DiveIntoHTML5.info`_ | | - `label`_ | | | - `trim`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+----------------------------------------------------------------------+ | Parent type | :doc:`text` | @@ -39,6 +40,8 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc .. _`DiveIntoHTML5.info`: http://diveintohtml5.info/forms.html#type-search diff --git a/reference/forms/types/text.rst b/reference/forms/types/text.rst index eaca7bb0598..0e0eb83c602 100644 --- a/reference/forms/types/text.rst +++ b/reference/forms/types/text.rst @@ -14,6 +14,7 @@ The text field represents the most basic input text field. | | - `label`_ | | | - `trim`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+--------------------------------------------------------------------+ | Parent type | :doc:`field` | @@ -37,4 +38,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/textarea.rst b/reference/forms/types/textarea.rst index 3b77ddf9b97..d2dbfd5eda0 100644 --- a/reference/forms/types/textarea.rst +++ b/reference/forms/types/textarea.rst @@ -14,6 +14,7 @@ Renders a ``textarea`` HTML element. | | - `label`_ | | | - `trim`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+------------------------------------------------------------------------+ | Parent type | :doc:`field` | @@ -36,5 +37,7 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/time.rst b/reference/forms/types/time.rst index 8568b8905a5..9c67d365184 100644 --- a/reference/forms/types/time.rst +++ b/reference/forms/types/time.rst @@ -26,6 +26,8 @@ as a ``DateTime`` object, a string, a timestamp or an array. +----------------------+-----------------------------------------------------------------------------+ | Inherited | - `invalid_message`_ | | options | - `invalid_message_parameters`_ | +| | - `read_only`_ | +| | - `disabled`_ | +----------------------+-----------------------------------------------------------------------------+ | Parent type | form | +----------------------+-----------------------------------------------------------------------------+ @@ -115,4 +117,8 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/invalid_message.rst.inc -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc \ No newline at end of file +.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc + +.. include:: /reference/forms/types/options/read_only.rst.inc + +.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/reference/forms/types/timezone.rst b/reference/forms/types/timezone.rst index ccb4a4ef859..08d81aa8052 100644 --- a/reference/forms/types/timezone.rst +++ b/reference/forms/types/timezone.rst @@ -22,10 +22,11 @@ you should just use the ``choice`` type directly. | options | - `expanded`_ | | | - `preferred_choices`_ | | | - `empty_value`_ | -| | - `error_bubbling`_ | | | - `required`_ | | | - `label`_ | | | - `read_only`_ | +| | - `disabled`_ | +| | - `error_bubbling`_ | +-------------+------------------------------------------------------------------------+ | Parent type | :doc:`choice` | +-------------+------------------------------------------------------------------------+ @@ -53,4 +54,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/url.rst b/reference/forms/types/url.rst index 6347727ef94..76501ebdc9e 100644 --- a/reference/forms/types/url.rst +++ b/reference/forms/types/url.rst @@ -18,6 +18,7 @@ have a protocol. | | - `label`_ | | | - `trim`_ | | | - `read_only`_ | +| | - `disabled`_ | | | - `error_bubbling`_ | +-------------+-------------------------------------------------------------------+ | Parent type | :doc:`text` | @@ -52,4 +53,6 @@ These options inherit from the :doc:`field` type: .. include:: /reference/forms/types/options/read_only.rst.inc +.. include:: /reference/forms/types/options/disabled.rst.inc + .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/requirements.rst b/reference/requirements.rst index 7c168fc30f3..28b698b45ad 100644 --- a/reference/requirements.rst +++ b/reference/requirements.rst @@ -19,8 +19,7 @@ Below is the list of required and optional requirements. Required -------- -* PHP needs to be a minimum version of PHP 5.3.2 -* Sqlite3 needs to be enabled +* PHP needs to be a minimum version of PHP 5.3.3 * JSON needs to be enabled * ctype needs to be enabled * Your PHP.ini needs to have the date.timezone setting diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst index b4a0eb40e08..d2ff0dc9e31 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -5,21 +5,43 @@ Symfony2 Twig Extensions ======================== Twig is the default template engine for Symfony2. By itself, it already contains -a lot of build-in functions, filters and tags (`http://twig.sensiolabs.org/documentation`_ +a lot of build-in functions, filters, tags and tests (`http://twig.sensiolabs.org/documentation`_ then scroll to the bottom). Symfony2 adds more custom extension on top of Twig to integrate some components into the Twig templates. Below is information about all the custom functions, -filters and tags that are added when using the Symfony2 Core Framework. +filters, tags and tests that are added when using the Symfony2 Core Framework. There may also be tags in bundles you use that aren't listed here. Functions --------- +.. versionadded:: 2.1 + The ``csrf_token``, ``logout_path`` and ``logout_url`` functions were added in Symfony2.1 + +.. versionadded:: 2.2 + The ``render`` and ``controller`` functions are new in Symfony 2.2. Prior, + the ``{% render %}`` tag was used and had a different signature. + +----------------------------------------------------+--------------------------------------------------------------------------------------------+ | Function Syntax | Usage | +====================================================+============================================================================================+ +| ``render(uri, options = {})`` | This will render the fragment for the given controller or URL | +| ``render(controller('B:C:a', {params}))`` | For more information, see :ref:`templating-embedding-controller`. | +| ``render(path('route', {params}))`` | | +| ``render(url('route', {params}))`` | | ++----------------------------------------------------+--------------------------------------------------------------------------------------------+ +| ``render_esi(controller('B:C:a', {params}))`` | This will generates an ESI tag when possible or fallback to the ``render`` | +| ``render_esi(url('route', {params}))`` | behavior otherwise. For more information, see :ref:`templating-embedding-controller`. | +| ``render_esi(path('route', {params}))`` | | ++----------------------------------------------------+--------------------------------------------------------------------------------------------+ +| ``render_hinclude(controller(...))`` | This will generates an Hinclude tag for the given controller or URL. | +| ``render_hinclude(url('route', {params}))`` | For more information, see :ref:`templating-embedding-controller`. | +| ``render_hinclude(path('route', {params}))`` | | ++----------------------------------------------------+--------------------------------------------------------------------------------------------+ +| ``controller(attributes = {}, query = {})`` | Used along with the ``render`` tag to refer to the controller that you want to render. | ++----------------------------------------------------+--------------------------------------------------------------------------------------------+ | ``asset(path, packageName = null)`` | Get the public path of the asset, more information in | | | ":ref:`book-templating-assets`". | +----------------------------------------------------+--------------------------------------------------------------------------------------------+ @@ -45,13 +67,16 @@ Functions | ``form_rest(view, variables = {})`` | This will render all fields that have not yet been rendered, more | | | information in :ref:`the Twig Form reference`. | +----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``_form_is_choice_group(label)`` | This will return ``true`` if the label is a choice group. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``_form_is_choice_selected(view, choice)`` | This will return ``true`` if the given choice is selected. | +| ``csrf_token(intention)`` | This will render a CSRF token. Use this function if you want CSRF protection without | +| | creating a form | +----------------------------------------------------+--------------------------------------------------------------------------------------------+ | ``is_granted(role, object = null, field = null)`` | This will return ``true`` if the current user has the required role, more | | | information in ":ref:`book-security-template`" | +----------------------------------------------------+--------------------------------------------------------------------------------------------+ +| ``logout_path(key)`` | This will generate the relative logout URL for the given firewall | ++----------------------------------------------------+--------------------------------------------------------------------------------------------+ +| ``logout_url(key)`` | Equal to ``logout_path(...)`` but this will generate an absolute url | ++----------------------------------------------------+--------------------------------------------------------------------------------------------+ | ``path(name, parameters = {})`` | Get a relative url for the given route, more information in | | | ":ref:`book-templating-pages`". | +----------------------------------------------------+--------------------------------------------------------------------------------------------+ @@ -61,14 +86,21 @@ Functions Filters ------- +.. versionadded:: 2.1 + The ``humanize`` filter was added in Symfony2.1 + +---------------------------------------------------------------------------------+-------------------------------------------------------------------+ | Filter Syntax | Usage | +=================================================================================+===================================================================+ +| ``text|humanize`` | Makes a technical name human readable (replaces underscores by | +| | spaces and capitalizes the string) | ++---------------------------------------------------------------------------------+-------------------------------------------------------------------+ | ``text|trans(arguments = {}, domain = 'messages', locale = null)`` | This will translate the text into the current language, more | -| | information in :ref:`book-translation-twig`. | +| | information in . | +| | :ref:`Translation Filters`. | +---------------------------------------------------------------------------------+-------------------------------------------------------------------+ | ``text|transchoice(count, arguments = {}, domain = 'messages', locale = null)`` | This will translate the text with pluralization, more information | -| | in :ref:`book-translation-twig`. | +| | in :ref:`Translation Filters`. | +---------------------------------------------------------------------------------+-------------------------------------------------------------------+ | ``variable|yaml_encode(inline = 0)`` | This will transform the variable text into a YAML syntax. | +---------------------------------------------------------------------------------+-------------------------------------------------------------------+ @@ -98,23 +130,34 @@ Filters Tags ---- -+---------------------------------------------------+-------------------------------------------------------------------+ -| Tag Syntax | Usage | -+===================================================+===================================================================+ -| ``{% render url('route', {parameters}) %}`` | This will render the Response Content for the given controller | -| | that the URL points to. For more information, | -| | see :ref:`templating-embedding-controller`. | -+---------------------------------------------------+-------------------------------------------------------------------+ -| ``{% form_theme form 'file' %}`` | This will look inside the given file for overridden form blocks, | -| | more information in :doc:`/cookbook/form/form_customization`. | -+---------------------------------------------------+-------------------------------------------------------------------+ -| ``{% trans with {variables} %}...{% endtrans %}`` | This will translate and render the text, more information in | -| | :ref:`book-translation-twig` | -+---------------------------------------------------+-------------------------------------------------------------------+ -| ``{% transchoice count with {variables} %}`` | This will translate and render the text with pluralization, more | -| ... | information in :ref:`book-translation-twig` | -| ``{% endtranschoice %}`` | | -+---------------------------------------------------+-------------------------------------------------------------------+ ++---------------------------------------------------+--------------------------------------------------------------------+ +| Tag Syntax | Usage | ++===================================================+====================================================================+ +| ``{% form_theme form 'file' %}`` | This will look inside the given file for overridden form blocks, | +| | more information in :doc:`/cookbook/form/form_customization`. | ++---------------------------------------------------+--------------------------------------------------------------------+ +| ``{% trans with {variables} %}...{% endtrans %}`` | This will translate and render the text, more information in | +| | :ref:`book-translation-tags` | ++---------------------------------------------------+--------------------------------------------------------------------+ +| ``{% transchoice count with {variables} %}`` | This will translate and render the text with pluralization, more | +| ... | information in :ref:`book-translation-tags` | +| ``{% endtranschoice %}`` | | ++---------------------------------------------------+--------------------------------------------------------------------+ +| ``{% trans_default_domain language %}`` | This will set the default domain for message catalogues in the | +| | current template | ++---------------------------------------------------+--------------------------------------------------------------------+ + +Tests +----- + +.. versionadded:: 2.1 + The ``selectedchoice`` test was added in Symfony2.1 + ++---------------------------------------------------+------------------------------------------------------------------------------+ +| Test Syntax | Usage | ++===================================================+==============================================================================+ +| ``selectedchoice(choice, selectedValue)`` | This will return ``true`` if the choice is selected for the given form value | ++---------------------------------------------------+------------------------------------------------------------------------------+ Global Variables ----------------