diff --git a/book/controller.rst b/book/controller.rst index 9e70aa32004..efd310c8b33 100644 --- a/book/controller.rst +++ b/book/controller.rst @@ -325,7 +325,7 @@ working with forms, for example:: { $form = $this->createForm(...); - $form->bindRequest($request); + $form->bind($request); // ... } @@ -630,8 +630,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. @@ -653,14 +653,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(...)); } @@ -679,19 +676,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:: php - hasFlash('notice')): ?> + getFlash('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 @@ -723,6 +720,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 60b32770259..b0f6f73c74f 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: @@ -406,7 +408,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(); @@ -553,7 +555,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) { @@ -619,7 +621,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'); @@ -795,7 +797,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(); @@ -988,7 +990,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(); @@ -1379,12 +1381,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 @@ -1425,14 +1422,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 2038047b0db..c5abc1af869 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 @@ -212,8 +216,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 @@ -225,13 +229,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. @@ -383,19 +392,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 @@ -512,7 +565,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. @@ -522,8 +575,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:: @@ -746,11 +799,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')); @@ -795,11 +848,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:: @@ -810,12 +865,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 @@ -840,7 +897,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(); @@ -922,20 +979,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() @@ -951,7 +1009,9 @@ class: .. code-block:: php - public function buildForm(FormBuilder $builder, array $options) + use Symfony\Component\Form\FormBuilderInterface; + + public function buildForm(FormBuilderInterface $builder, array $options) { // ... @@ -959,7 +1019,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:: @@ -1038,7 +1109,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) }} @@ -1046,19 +1117,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: @@ -1084,8 +1155,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 @@ -1099,6 +1170,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:: @@ -1123,10 +1207,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``, @@ -1135,13 +1219,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:: @@ -1163,16 +1247,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:: @@ -1243,9 +1328,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 %} {# ... #} @@ -1339,19 +1424,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', - ); + )); } // ... @@ -1393,8 +1480,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(); @@ -1433,64 +1520,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; +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. - $collectionConstraint = new Collection(array( - 'name' => new MinLength(5), - 'email' => new Email(array('message' => 'Invalid email address')), - )); +.. _form-option-constraints: - // create a form, no default values, pass in the constraint option - $form = $this->createFormBuilder(null, array( - 'validation_constraint' => $collectionConstraint, - ))->add('email', 'email') - // ... - ; +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? -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:: +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: - namespace Acme\TaskBundle\Form\Type; +.. 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 - 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; - - class ContactType extends AbstractType - { - // ... - - 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\NotBlank; + + $builder + ->add('firstName', 'text', array( + 'constraints' => new MinLength(3), + )) + ->add('lastName', 'text', array( + 'constraints' => array( + new NotBlank(), + new MinLength(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 -------------- @@ -1522,6 +1598,6 @@ 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 +.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.1/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 diff --git a/book/from_flat_php_to_symfony2.rst b/book/from_flat_php_to_symfony2.rst index 7a5c21c40dd..3796084c3e7 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) ; @@ -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 4603c5fc5db..266a238005a 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. @@ -878,13 +881,12 @@ matter), Symfony2 uses the standard ``render`` helper to configure ESI tags: .. code-block:: jinja - {% render url('latest_news', { 'max': 5 }) with {}, {'standalone': true} %} + {% render url('latest_news', { 'max': 5 }), {'standalone': true} %} .. code-block:: php render( $view['router']->generate('latest_news', array('max' => 5), true), - array(), array('standalone' => true) ) ?> diff --git a/book/installation.rst b/book/installation.rst index 24f3b3610b8..14565ac2c71 100644 --- a/book/installation.rst +++ b/book/installation.rst @@ -14,16 +14,17 @@ 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 - requirements, see the :doc:`requirements reference`. - For information on configuring your specific web server document root, see the - following documentation: `Apache`_ | `Nginx`_ . + First, check that you have installed and configured a Web server (such as + Apache) with the most recent PHP version possible (PHP 5.3.8 or newer is + recommended). For more information on Symfony2 requirements, see the + :doc:`requirements reference`. For information on + configuring your specific web server document root, see the following + documentation: `Apache`_ | `Nginx`_ . Symfony2 packages "distributions", which are fully-functional applications that include the Symfony2 core libraries, a selection of useful bundles, a @@ -33,34 +34,53 @@ 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.1.x-dev + +.. tip:: -When you're finished, you should have a ``Symfony/`` directory that looks -something like this: + For an exact version, replace `2.1.x-dev` with the latest Symfony version + (e.g. 2.1.1). For details, see the `Symfony Installation Page`_ + +.. tip:: + + To download the vendor files faster and without unnecessary directories + (e.g. "Tests"), add the ``--prefer-dist`` option at the end of any Composer + command. + +This command may take several minutes to run as Composer download 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/ @@ -73,26 +93,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.1.###.tgz + + # for a .zip file + $ unzip Symfony_Standard_Vendors_2.1.###.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 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -107,7 +204,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. @@ -173,11 +270,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 Beginning Development --------------------- @@ -192,6 +289,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. + Using Source Control -------------------- @@ -213,16 +313,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 fba3d0143b5..58f57d31dc2 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 ec01b55e01a..b07b7b43b96 100644 --- a/book/page_creation.rst +++ b/book/page_creation.rst @@ -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: / @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -733,20 +728,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: @@ -759,19 +747,13 @@ format you prefer: - + - + - - - - - - - + @@ -781,23 +763,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 eee45951cf2..612257e0ac4 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 7c5d0428795..117ad48a8c7 100644 --- a/book/routing.rst +++ b/book/routing.rst @@ -777,6 +777,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 +795,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 @@ -1054,6 +1065,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 diff --git a/book/security.rst b/book/security.rst index 9f5394b8bff..541e0763e54 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'), + ), ), ), ), @@ -400,8 +405,12 @@ 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`` + (e.g. ``/login``), ``check_path`` (e.g. ``/login_check``) and ``logout`` + (e.g. ``/logout`` - see `Logging Out`_) URLs. 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 @@ -916,9 +925,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 @@ -926,8 +936,10 @@ In fact, you've seen this already in the example in this chapter. - - + + + + @@ -938,9 +950,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'), + ), ), ), ), @@ -1012,6 +1026,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, @@ -1085,9 +1105,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: @@ -1101,8 +1122,10 @@ do the following: - - + + + + @@ -1115,9 +1138,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'), + ), ), ), ), @@ -1203,6 +1228,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()`` @@ -1242,10 +1277,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 } @@ -1254,11 +1291,15 @@ a new provider that chains the two together: - in_memory - user_db + + in_memory + user_db + - + + + @@ -1271,11 +1312,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( @@ -1302,16 +1347,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 - + + + @@ -1322,8 +1372,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'), ), @@ -1509,9 +1561,14 @@ 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: +.. warning:: + + 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 @@ -1784,7 +1841,7 @@ 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 diff --git a/book/service_container.rst b/book/service_container.rst index 9372faa94aa..1fb138c285b 100644 --- a/book/service_container.rst +++ b/book/service_container.rst @@ -465,7 +465,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" } @@ -474,7 +473,7 @@ invokes the service container extension inside the ``FrameworkBundle``: .. code-block:: xml - + @@ -486,7 +485,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'), @@ -507,7 +505,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 03861dffe17..3b9258144d0 100644 --- a/book/templating.rst +++ b/book/templating.rst @@ -689,6 +689,63 @@ 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('...'), {'standalone': 'js'} %} + + .. code-block:: php + + render( + $view['router']->generate('...'), + array('standalone' => 'js') + ) ?> + +.. note:: + + hinclude.js_ needs to be included in your page to work. + +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'), + ), + )); + .. index:: single: Templating; Linking to pages @@ -1320,6 +1377,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 ---------------- @@ -1411,4 +1488,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 9e885daed13..c9a679c3a34 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 diff --git a/book/translation.rst b/book/translation.rst index fec6a23da29..6c6ea3da1c2 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 @@ -146,7 +148,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 @@ -278,13 +281,21 @@ 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 fallback 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 @@ -479,16 +490,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:: + + // access the request object in a standard controller + $request = $this->getRequest(); - $locale = $this->get('session')->getLocale(); + $locale = $request->getLocale(); - $this->get('session')->setLocale('en_US'); + $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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -496,8 +520,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:: @@ -505,28 +529,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, @@ -776,6 +805,17 @@ texts* and complex expressions: {# but static strings are never escaped #} {{ '

foo

'|trans }} +.. versionadded:: 2.1 + You can now 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). + PHP Templates ~~~~~~~~~~~~~ @@ -795,7 +835,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:: @@ -948,7 +988,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 once 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 fd013cd5f2c..9565bd272de 100644 --- a/book/validation.rst +++ b/book/validation.rst @@ -222,8 +222,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 bc401b84d22..166318fe761 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -100,6 +100,7 @@ node definition. Node type are available for: * scalar * boolean * array +* enum (new in 2.1) * variable (no validation) and are created with ``node($name, $type)`` or their associated shortcut diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst index b3ff5172305..ba3cceffbf9 100644 --- a/components/console/single_command_tool.rst +++ b/components/console/single_command_tool.rst @@ -37,7 +37,7 @@ it is possible to remove this need by extending the application:: { // Keep the core default commands to have the HelpCommand // which is used when using the --help option - $defaultCommands = parent::getDefaultCommands() + $defaultCommands = parent::getDefaultCommands(); $defaultCommands[] = new MyCommand(); 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/event_dispatcher/container_aware_dispatcher.rst b/components/event_dispatcher/container_aware_dispatcher.rst new file mode 100644 index 00000000000..74acb144643 --- /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 + { + static public 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..64333c92be0 --- /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 2ac615548bb..7a81daeda05 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 @@ -393,9 +330,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; @@ -441,9 +376,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; @@ -470,9 +403,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; @@ -486,6 +417,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..b4755c1201d --- /dev/null +++ b/components/filesystem.rst @@ -0,0 +1,243 @@ +.. 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::chown`, + :method:`Symfony\\Component\\Filesystem\\Filesystem::chgrp`, + :method:`Symfony\\Component\\Filesystem\\Filesystem::chown`, + :method:`Symfony\\Component\\Filesystem\\Filesystem::remove` and + :method:`Symfony\\Component\\Filesystem\\Filesystem::touch` 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 earlier 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 third 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 a878c7e8053..245b5fd0ad2 100644 --- a/components/finder.rst +++ b/components/finder.rst @@ -157,6 +157,25 @@ 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'); + File Size ~~~~~~~~~ @@ -169,8 +188,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 @@ -219,9 +241,28 @@ 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 .. _Iterator: http://www.php.net/manual/en/spl.iterators.php .. _protocol: http://www.php.net/manual/en/wrappers.php .. _Streams: http://www.php.net/streams .. _IEC standard: http://physics.nist.gov/cuu/Units/binary.html -.. _Packagist: https://packagist.org/packages/symfony/finder \ No newline at end of file +.. _Packagist: https://packagist.org/packages/symfony/finder diff --git a/components/http_foundation/index.rst b/components/http_foundation/index.rst index d36849dc5f3..4db09a9f49e 100644 --- a/components/http_foundation/index.rst +++ b/components/http_foundation/index.rst @@ -5,3 +5,6 @@ HTTP Foundation :maxdepth: 2 introduction + sessions + session_configuration + session_testing diff --git a/components/http_foundation/introduction.rst b/components/http_foundation/introduction.rst index 302d4f409b0..5e27083a2e3 100644 --- a/components/http_foundation/introduction.rst +++ b/components/http_foundation/introduction.rst @@ -365,6 +365,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 +422,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..908d193cd62 --- /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. + +.. warning:: + + 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 fda5055c469..a0fe9e28b70 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 send 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` | +-------------------+-------------------------------+-------------------------------------------------------------------------------------+ @@ -581,6 +625,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 a38c89ab18b..d25d5bd77f2 100644 --- a/components/index.rst +++ b/components/index.rst @@ -12,6 +12,7 @@ The Components dom_crawler dependency_injection/index event_dispatcher/index + filesystem finder http_foundation/index http_kernel/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 318fad978f7..f4f9998fe27 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -41,7 +41,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` @@ -49,6 +55,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_kernel/index` diff --git a/components/process.rst b/components/process.rst index 99aa8732161..973b572e64d 100644 --- a/components/process.rst +++ b/components/process.rst @@ -63,4 +63,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/routing/introduction.rst b/components/routing/introduction.rst index e57f97e8df5..e4ca99543fd 100644 --- a/components/routing/introduction.rst +++ b/components/routing/introduction.rst @@ -136,7 +136,6 @@ the POST method and a secure connection:: array('suffix' => '.*') ); - Using Prefixes ~~~~~~~~~~~~~~ diff --git a/components/using_components.rst b/components/using_components.rst index ad333f1f62c..c7f2f46ebe1 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. @@ -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; @@ -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.rst b/components/yaml.rst index 9c6e88b2d09..46bfac7775a 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -126,15 +126,22 @@ 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. + +Executing PHP Inside YAML Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + The ``Yaml::enablePhpParsing()`` method is new to Symfony 2.1. Prior to 2.1, + PHP was *always* executed when calling the ``parse()`` function. + +By default, if you include PHP inside a YAML file, it will not be parsed. +If you do want PHP to be parsed, you must call ``Yaml::enablePhpParsing()`` +before parsing the file to activate this mode. If you only want to allow +PHP code for a single YAML file, be sure to disable PHP parsing after parsing +the single file by calling ``Yaml::$enablePhpParsing = false;`` (``$enablePhpParsing`` +is a public property). Writing YAML Files ~~~~~~~~~~~~~~~~~~ diff --git a/contributing/code/patches.rst b/contributing/code/patches.rst index 8156cffde71..379e90b25f5 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 ~~~~~~~~~~~~~ @@ -251,8 +251,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: @@ -267,6 +267,9 @@ pull request message, like in: Please use the title with "[WIP]" if the submission is not yet completed or the tests are incomplete or not yet passing. +Pull Request Description +~~~~~~~~~~~~~~~~~~~~~~~~ + The pull request description must include the following check list to ensure that contributions may be reviewed without needless feedback loops and that your contributions can be included into Symfony2 as quickly as possible: 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/bundles/extension.rst b/cookbook/bundles/extension.rst index 14a5fc34e87..aed9e8f4090 100644 --- a/cookbook/bundles/extension.rst +++ b/cookbook/bundles/extension.rst @@ -467,6 +467,7 @@ that an unsupported option was passed:: public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); // ... @@ -482,6 +483,54 @@ 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`_. +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 +``__constructor()`` it will work automatically. If you have a something +different your ``Extension`` class will have to override the +``Extension::getConfiguration()`` method. Have it 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/configuration/pdo_session_storage.rst b/cookbook/configuration/pdo_session_storage.rst index a9f4daf54b7..10ea59a4910 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% @@ -83,7 +89,7 @@ configuration format of your choice): // ... 'session' => array( ..., - 'storage_id' => 'session.storage.pdo', + 'handler_id' => 'session.handler.pdo', ), )); @@ -101,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) diff --git a/cookbook/console/sending_emails.rst b/cookbook/console/sending_emails.rst index d6a6e4a558e..30c65f3b498 100644 --- a/cookbook/console/sending_emails.rst +++ b/cookbook/console/sending_emails.rst @@ -15,7 +15,47 @@ 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 + +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). 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 + + .. code-block:: xml + + + + + + + + example.org + https + + + + .. code-block:: php + + // app/config/config_test.php + $container->setParameter('router.request_context.host', 'example.org'); + $container->setParameter('router.request_context.scheme', 'https'); Configuring the Request Context per Command ------------------------------------------- diff --git a/cookbook/controller/error_pages.rst b/cookbook/controller/error_pages.rst index a82f81b9ba0..d776a1575b4 100644 --- a/cookbook/controller/error_pages.rst +++ b/cookbook/controller/error_pages.rst @@ -90,7 +90,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/debugging.rst b/cookbook/debugging.rst index 2b84571036f..31e5b324d6a 100644 --- a/cookbook/debugging.rst +++ b/cookbook/debugging.rst @@ -46,7 +46,6 @@ 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'; require_once __DIR__.'/../app/AppKernel.php'; diff --git a/cookbook/deployment-tools.rst b/cookbook/deployment-tools.rst index 9b136ef31d5..a8a1179928c 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. @@ -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 75870044716..2898ecc3f4c 100644 --- a/cookbook/doctrine/custom_dql_functions.rst +++ b/cookbook/doctrine/custom_dql_functions.rst @@ -80,4 +80,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/event_listeners_subscribers.rst b/cookbook/doctrine/event_listeners_subscribers.rst index b33995d129a..a4a1d6d6fd0 100644 --- a/cookbook/doctrine/event_listeners_subscribers.rst +++ b/cookbook/doctrine/event_listeners_subscribers.rst @@ -114,4 +114,4 @@ specific type of entity (e.g. a ``Product`` entity but not a ``BlogPost`` entity), you should check for the class name of the entity in your method (as shown above). -.. _`The Event System`: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/events.html +.. _`The Event System`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html diff --git a/cookbook/doctrine/file_uploads.rst b/cookbook/doctrine/file_uploads.rst index 287f8104459..27d35080cca 100644 --- a/cookbook/doctrine/file_uploads.rst +++ b/cookbook/doctrine/file_uploads.rst @@ -157,10 +157,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(); @@ -196,7 +196,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(); @@ -318,7 +318,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 fccd55ba97f..6cf5ec3eef1 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 a57c58beaed..2ca79348be5 100644 --- a/cookbook/email/email.rst +++ b/cookbook/email/email.rst @@ -87,7 +87,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 e1972dfecb2..784046ddb80 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/form/create_custom_field_type.rst b/cookbook/form/create_custom_field_type.rst index 9d094ed72c7..343f2d6654c 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 @@ -150,11 +150,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', @@ -231,6 +231,9 @@ argument 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 @@ -242,11 +245,11 @@ argument 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, - ); + )); } // ... @@ -258,11 +261,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 377b808e563..3e25f512295 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` @@ -174,7 +172,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. @@ -188,10 +186,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 { @@ -208,24 +206,11 @@ it in the view:: /** * Add the image_path option * - * @param array $options - */ - public function getDefaultOptions(array $options) - { - return array('image_path' => null); - } - - /** - * Store the image_path option as a builder attribute - * - * @param \Symfony\Component\Form\FormBuilder $builder - * @param array $options + * @param \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver */ - public function buildForm(FormBuilder $builder, array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - if (null !== $options['image_path']) { - $builder->setAttribute('image_path', $options['image_path']); - } + $resolver->setOptional(array('image_path')); } /** @@ -233,18 +218,20 @@ it in the view:: * * @param \Symfony\Component\Form\FormView $view * @param \Symfony\Component\Form\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); } @@ -273,7 +260,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 %} @@ -307,11 +294,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 802497a79b4..1773f48a7eb 100644 --- a/cookbook/form/data_transformers.rst +++ b/cookbook/form/data_transformers.rst @@ -102,14 +102,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) { // ... @@ -120,10 +121,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', + )); + + // ... + } + // ... } @@ -152,51 +170,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 @@ -223,9 +249,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 { @@ -242,20 +269,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'; } @@ -294,11 +321,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_generation.rst b/cookbook/form/dynamic_form_generation.rst index 8f18049abcd..7cdf3de9155 100644 --- a/cookbook/form/dynamic_form_generation.rst +++ b/cookbook/form/dynamic_form_generation.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'); @@ -57,12 +57,12 @@ 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); @@ -91,10 +91,10 @@ might look like the following:: // src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php namespace Acme\DemoBundle\Form\EventListener; - use Symfony\Component\Form\Event\DataEvent; + use Symfony\Component\Form\FormEvent; + use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use Symfony\Component\Form\FormEvents; class AddNameFieldSubscriber implements EventSubscriberInterface { @@ -112,7 +112,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(); @@ -128,7 +128,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($this->factory->createNamed('name', 'text')); } } } @@ -145,20 +145,16 @@ 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. -.. _`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 9eb030df107..61d805ab9a0 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. @@ -380,10 +384,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: @@ -396,9 +403,9 @@ one example: // count the current form inputs we have (e.g. 2), use that as the new index (e.g. 2) var newIndex = collectionHolder.find(':input').length; - // 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, newIndex); + var newForm = prototype.replace(/__name__/g, newIndex); // Display the form in the page in an li, before the "Add a tag" link li var $newFormLi = $('
      • ').append(newForm); @@ -506,8 +513,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'); @@ -593,7 +601,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) { @@ -609,8 +617,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 1bb3f1598e7..9d9afeb6802 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 @@ -594,7 +594,7 @@ which part of the field is being customized. For example: {% block _product_name_widget %}
        - {{ block('field_widget') }} + {{ block('form_widget_simple') }}
        {% endblock %} @@ -610,7 +610,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 @@ -694,40 +694,55 @@ 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.messagePluralization is null + ? error.messageTemplate|trans(error.messageParameters, 'validators') + : error.messageTemplate|transchoice(error.messagePluralization, error.messageParameters, 'validators') + }}
      • {% endfor %}
      {% endif %} {% endspaceless %} - {% endblock field_errors %} + {% endblock form_errors %} .. code-block:: html+php - +
        -
      • trans( - $error->getMessageTemplate(), - $error->getMessageParameters(), - 'validators' - ) ?>
      • +
      • getMessagePluralization()) { + echo $view['translator']->trans( + $error->getMessageTemplate(), + $error->getMessageParameters(), + 'validators' + ); + } else { + echo $view['translator']->transChoice( + $error->getMessageTemplate(), + $error->getMessagePluralization(), + $error->getMessageParameters(), + 'validators' + ); + }?>
      + .. tip:: See :ref:`cookbook-form-theming-methods` for how to apply this customization. @@ -748,7 +763,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" ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -756,25 +771,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) ?> @@ -788,17 +803,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 %} * @@ -812,7 +827,7 @@ the following: {% extends 'form_div_layout.html.twig' %} - {% block field_label %} + {% block form_label %} {{ parent() }} {% if required %} @@ -825,10 +840,13 @@ original template: .. code-block:: html+php - + - + + + humanize($name); } ?> + @@ -848,10 +866,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 }} @@ -865,7 +883,7 @@ the following: {% extends 'form_div_layout.html.twig' %} - {% block field_widget %} + {% block form_widget_simple %} {{ parent() }} {% if help is defined %} @@ -878,13 +896,13 @@ original template: .. code-block:: html+php - + " - value="escape($value) ?>" - renderBlock('attributes') ?> + type="escape($type) : 'text' ?>" + value="escape($value) ?>" + block($form, 'widget_attributes') ?> /> @@ -931,4 +949,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.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig diff --git a/cookbook/form/use_virtuals_forms.rst b/cookbook/form/use_virtuals_forms.rst index 121e1dfc00a..41b05355ac8 100644 --- a/cookbook/form/use_virtuals_forms.rst +++ b/cookbook/form/use_virtuals_forms.rst @@ -50,9 +50,11 @@ Start by creating a very simple ``CompanyType`` and ``CustomerType``:: // src/Acme/HelloBundle/Form/Type/CompanyType.php namespace Acme\HelloBundle\Form\Type; + 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') @@ -65,9 +67,11 @@ 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; + class CustomerType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('firstName', 'text') @@ -81,9 +85,12 @@ location form type:: // src/Acme/HelloBundle/Form/Type/LocationType.php namespace Acme\HelloBundle\Form\Type; + 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') @@ -92,11 +99,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() @@ -111,23 +118,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/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 e44a1d43c6e..4723058d7a5 100644 --- a/cookbook/logging/monolog.rst +++ b/cookbook/logging/monolog.rst @@ -52,6 +52,7 @@ allows you to log the messages in several ways easily. .. code-block:: yaml + # app/config/config*.yml monolog: handlers: applog: @@ -130,6 +131,7 @@ easily. Your formatter must implement .. code-block:: yaml + # app/config/config.yml services: my_formatter: class: Monolog\Formatter\JsonFormatter @@ -184,7 +186,7 @@ using a processor. namespace Acme\MyBundle; - use Symfony\Component\HttpFoundation\Session; + use Symfony\Component\HttpFoundation\Session\Session; class SessionRequestProcessor { @@ -212,10 +214,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 00a47ebb3f6..5596e66b552 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -53,6 +53,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` @@ -85,6 +86,7 @@ * :doc:`/cookbook/logging/monolog` * :doc:`/cookbook/logging/monolog_email` + * :doc:`/cookbook/logging/channels_handlers` * :doc:`/cookbook/profiler/index` @@ -100,6 +102,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` @@ -113,6 +116,7 @@ * :doc:`/cookbook/security/securing_services` * :doc:`/cookbook/security/custom_provider` * :doc:`/cookbook/security/custom_authentication_provider` + * :doc:`/cookbook/security/target_path` * :doc:`/cookbook/service_container/index` @@ -151,3 +155,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/service_container_parameters.rst b/cookbook/routing/service_container_parameters.rst new file mode 100644 index 00000000000..37c855c588d --- /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: + pattern: /{_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 pattern (or part of your +pattern): + +.. configuration-block:: + + .. code-block:: yaml + + some_route: + pattern: /%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%``. \ No newline at end of file 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 cc2a8be663f..36dad7eaad2 100644 --- a/cookbook/security/custom_authentication_provider.rst +++ b/cookbook/security/custom_authentication_provider.rst @@ -405,65 +405,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 - - - - - - - - - - - -Now, import the factory configuration via the the ``factories`` key in your -security configuration: +.. versionadded:: 2.1 + Before 2.1, the factory below was added via ``security.yml`` instead. -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - factories: - - "%kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factories.yml" +.. code-block:: php - .. code-block:: xml + // src/Acme/DemoBundle/AcmeDemoBundle.php + namespace Acme\DemoBundle; - - - - "%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/entity_provider.rst b/cookbook/security/entity_provider.rst index 2b5f7a132a8..e67be2213f4 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->username === $user->getUsername(); - } - /** * @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 ``username`` 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->username === $user->getUsername(); + } .. note:: diff --git a/cookbook/security/form_login.rst b/cookbook/security/form_login.rst index ae2146baf24..badc3824b7f 100644 --- a/cookbook/security/form_login.rst +++ b/cookbook/security/form_login.rst @@ -31,6 +31,14 @@ 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 ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -121,7 +129,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:: @@ -159,6 +167,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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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..2aa42d9a40a --- /dev/null +++ b/cookbook/security/target_path.rst @@ -0,0 +1,68 @@ +.. 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.target_path``. 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.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/symfony1.rst b/cookbook/symfony1.rst index 3ceb8d69deb..fc39f841256 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,23 +133,12 @@ example:: } The file itself lives at -``vendor/bundle/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/bundle/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/bundle`` 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.`` @@ -161,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: +``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: -.. code-block:: php - - // app/autoload.php +.. code-block:: yaml - // ... - $loader->registerNamespaceFallbacks(array( - __DIR__.'/../src', - )); + "autoload": { + "psr-0": { "": "src/" } + } Using the Console ----------------- @@ -316,3 +305,4 @@ primarily to configure objects that you can use. For more information, see the chapter titled ":doc:`/book/service_container`". .. _`Symfony2 Standard`: https://github.com/symfony/symfony-standard +.. _`Composer`: http://getcomposer.org diff --git a/cookbook/templating/global_variables.rst b/cookbook/templating/global_variables.rst index 93dddeaefb3..56f98dde6b2 100644 --- a/cookbook/templating/global_variables.rst +++ b/cookbook/templating/global_variables.rst @@ -24,10 +24,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 .. code-block:: yaml 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..59b67c9977f 100644 --- a/cookbook/testing/profiling.rst +++ b/cookbook/testing/profiling.rst @@ -34,8 +34,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 c906516dae8..9ebab7a1669 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,27 @@ 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. + +.. versionadded:: 2.1 + The ``isValid`` method was renamed to ``validate`` in Symfony 2.1. The + ``setMessage`` method was also deprecated, in favor of calling ``addViolation`` + on the context. Using the new Validator ----------------------- @@ -209,22 +214,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->addViolationAtSubPath('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 d2ae68faf66..1df01861cf6 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. @@ -125,11 +90,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 a55df4fac93..37f2436e145 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/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/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index 4a7668bab32..16f7fbbcb14 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,19 +133,20 @@ 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" } form: true csrf_protection: true validation: { enable_annotations: true } templating: { engines: ['twig'] } #assets_version: SomeVersionScheme + default_locale: "%locale%" session: - default_locale: "%locale%" auto_start: true # Twig Configuration @@ -186,6 +158,8 @@ PHP. Have a look at the default configuration: assetic: debug: "%kernel.debug%" use_controller: false + bundles: [ ] + # java: /usr/bin/java filters: cssrewrite: ~ # closure: @@ -198,6 +172,7 @@ PHP. Have a look at the default configuration: dbal: driver: "%database_driver%" host: "%database_host%" + port: "%database_port%" dbname: "%database_name%" user: "%database_user%" password: "%database_password%" @@ -364,3 +339,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 ee512d738a5..16f11ac4a9d 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.1.x-dev + + # remove the Git history + $ rm -rf .git + + For an exact version, replace `2.1.x-dev` 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 If there are any outstanding issues listed, correct them. You might also tweak your configuration by following any given recommendations. When everything is @@ -96,11 +130,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 @@ -124,7 +158,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 @@ -375,20 +409,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 @@ -447,3 +486,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..c11c5473140 --- a/quick_tour/the_controller.rst +++ b/quick_tour/the_controller.rst @@ -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->get('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/reference/configuration/assetic.rst b/reference/configuration/assetic.rst index 4ca8ef8b0e3..1f234e9311c 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,23 +36,19 @@ 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: [] 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 bcfff139329..04b60301ff6 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -15,7 +15,6 @@ routing and more. Configuration ------------- -* `charset`_ * `secret`_ * `ide`_ * `test`_ @@ -32,14 +31,6 @@ Configuration * `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 ~~~~~~ @@ -141,6 +132,15 @@ is `protocol-relative`_ (i.e. starts with `//`) it will be added to both collections. URL's 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 @@ -247,11 +247,11 @@ Full Default Configuration framework: # general configuration - charset: ~ + trust_proxy_headers: false secret: ~ # Required ide: ~ test: ~ - trust_proxy_headers: false + default_locale: en # form configuration form: @@ -268,13 +268,15 @@ Full Default Configuration profiler: only_exceptions: false only_master_requests: false - dsn: "sqlite:%kernel.cache_dir%/profiler.db" + 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 @@ -283,40 +285,65 @@ Full Default Configuration type: ~ http_port: 80 https_port: 443 + # if false, an empty URL will be generated if a route is missing required parameters + strict_requirements: %kernel.debug% # session configuration session: - auto_start: ~ - default_locale: en + auto_start: false storage_id: session.storage.native + handler_id: session.handler.native_file name: ~ - lifetime: 0 + cookie_lifetime: ~ + cookie_path: ~ + cookie_domain: ~ + cookie_secure: ~ + cookie_httponly: ~ + gc_divisor: ~ + gc_probability: ~ + gc_maxlifetime: ~ + save_path: %kernel.cache_dir%/sessions + + # DEPRECATED! Please use: cookie_lifetime + lifetime: ~ + + # DEPRECATED! Please use: cookie_path path: ~ + + # DEPRECATED! Please use: cookie_domain domain: ~ + + # DEPRECATED! Please use: cookie_secure secure: ~ + + # DEPRECATED! Please use: cookie_httponly httponly: ~ # 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 loaders: [] packages: - # Prototype - name: + # A collection of named packages + some_package_name: version: ~ - version_format: ~ + version_format: %%s?%%s base_urls: http: [] ssl: [] 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 c27c08c528e..f892a6a4047 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -19,72 +19,89 @@ 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: + # Examples: + Acme\DemoBundle\Entity\User1: sha512 + Acme\DemoBundle\Entity\User2: + algorithm: sha512 + encode_as_base64: true + iterations: 5000 + + # Example options/values for what a custom encoder might look like + Acme\Your\Class\Name: + algorithm: ~ + ignore_case: false + encode_as_base64: true + iterations: 5000 + id: ~ + + providers: # Required + # Examples: memory: - name: memory + name: memory users: - foo: { password: foo, roles: ROLE_USER } - bar: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] } + 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: + entity: + class: SecurityBundle:User + property: username + + # Example custom provider + some_custom_provider: + id: ~ + chain: + providers: [] + + 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 context: name 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 @@ -141,20 +158,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: @@ -182,7 +230,7 @@ The Login Form and Process 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. - + Be sure that this URL is covered by your main firewall (i.e. don't create a separate firewall just for ``check_path`` URL). diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index 7af917cce9f..87515db26d2 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -9,11 +9,12 @@ TwigBundle Configuration Reference .. code-block:: yaml twig: + exception_controller: Symfony\Bundle\TwigBundle\Controller\ExceptionController::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 diff --git a/reference/configuration/web_profiler.rst b/reference/configuration/web_profiler.rst index b22058f3cbe..455be0deca3 100644 --- a/reference/configuration/web_profiler.rst +++ b/reference/configuration/web_profiler.rst @@ -12,12 +12,12 @@ 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 + intercept_redirects: false - # gives you the opportunity to look at the collected data before following the redirect - intercept_redirects: false \ No newline at end of file diff --git a/reference/constraints.rst b/reference/constraints.rst index 54d67eaf8a1..b0ac0823113 100644 --- a/reference/constraints.rst +++ b/reference/constraints.rst @@ -16,12 +16,14 @@ Validation Constraints Reference 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 +31,7 @@ Validation Constraints Reference constraints/Choice constraints/Collection + constraints/Count constraints/UniqueEntity constraints/Language constraints/Locale @@ -38,8 +41,9 @@ Validation Constraints Reference constraints/Image 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 diff --git a/reference/constraints/All.rst b/reference/constraints/All.rst index 4601d443ffa..839a93a6d1a 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(); diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst index 027e534b922..fa36d443e87 100644 --- a/reference/constraints/Callback.rst +++ b/reference/constraints/Callback.rst @@ -85,9 +85,7 @@ those errors should be attributed:: // 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->addViolationAtSubPath('firstname', 'This name sounds totally fake!', array(), null); } } diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst index 83a0e24ed24..e62f9924d9d 100644 --- a/reference/constraints/Choice.rst +++ b/reference/constraints/Choice.rst @@ -280,4 +280,4 @@ strict If true, the validator will also check the type of the input value. Specifically, this value is passed to as the third argument to the PHP :phpfunction:`in_array` method -when checking to see if a value is in the valid choices array. \ No newline at end of file +when checking to see if a value is in the valid choices array. diff --git a/reference/constraints/Collection.rst b/reference/constraints/Collection.rst index 8013166b7c7..7cf0cea7270 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. @@ -60,9 +60,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 @@ -78,9 +78,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!" * ) * } * }, @@ -105,9 +105,9 @@ blank but is no longer than 100 characters in length, you would do the following - - - + + + @@ -122,7 +122,7 @@ blank but is no longer than 100 characters in length, you would do the following use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\Email; - use Symfony\Component\Validator\Constraints\MaxLength; + use Symfony\Component\Validator\Constraints\Length; class Author { @@ -133,7 +133,9 @@ blank but is no longer than 100 characters in length, you would do the following $metadata->addPropertyConstraint('profileData', new Collection(array( 'fields' => array( 'personal_email' => new Email(), - 'lastName' => array(new NotBlank(), new MaxLength(100)), + 'lastName' => array( + new NotBlank(), + new Length(array("max" => 100)), ), 'allowMissingFields' => true, ))); diff --git a/reference/constraints/Count.rst b/reference/constraints/Count.rst new file mode 100644 index 00000000000..b954fea7af6 --- /dev/null +++ b/reference/constraints/Count.rst @@ -0,0 +1,116 @@ +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 + 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 + + + + + + + + + + + + + + +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 ed549978b99..2edc84995b3 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` | +----------------+---------------------------------------------------------------------+ @@ -46,17 +47,17 @@ Basic Usage - + .. 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 @@ -80,7 +81,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 \ No newline at end of file +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..194d2f8f9ce --- /dev/null +++ b/reference/constraints/Length.rst @@ -0,0 +1,125 @@ +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 + 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 + + + + + + + + + + + + + +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/Max.rst b/reference/constraints/Max.rst index 084483fec71..ba9d102750f 100644 --- a/reference/constraints/Max.rst +++ b/reference/constraints/Max.rst @@ -1,6 +1,12 @@ Max === +.. caution:: + + The Max constraint is deprecated since version 2.1 and will be removed + in Symfony 2.3. Use :doc:`/reference/constraints/Range` with the ``max`` + option instead. + Validates that a given number is *less* than some maximum number. +----------------+--------------------------------------------------------------------+ @@ -81,4 +87,4 @@ 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). \ No newline at end of file +the :phpfunction:`is_numeric` PHP function). diff --git a/reference/constraints/MaxLength.rst b/reference/constraints/MaxLength.rst index 2c379b19df1..d28815a3db8 100644 --- a/reference/constraints/MaxLength.rst +++ b/reference/constraints/MaxLength.rst @@ -1,6 +1,12 @@ MaxLength ========= +.. caution:: + + The MaxLength constraint is deprecated since version 2.1 and will be removed + in Symfony 2.3. Use :doc:`/reference/constraints/Length` with the ``max`` + option instead. + Validates that the length of a string is not larger than the given limit. +----------------+-------------------------------------------------------------------------+ @@ -78,4 +84,4 @@ charset 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. \ No newline at end of file +option is passed as the second argument to that function. diff --git a/reference/constraints/Min.rst b/reference/constraints/Min.rst index 72935b36eaf..968d3784814 100644 --- a/reference/constraints/Min.rst +++ b/reference/constraints/Min.rst @@ -1,6 +1,12 @@ Min === +.. caution:: + + The Min constraint is deprecated since version 2.1 and will be removed + in Symfony 2.3. Use :doc:`/reference/constraints/Range` with the ``min`` + option instead. + Validates that a given number is *greater* than some minimum number. +----------------+--------------------------------------------------------------------+ @@ -69,4 +75,4 @@ 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). \ No newline at end of file +the :phpfunction:`is_numeric` PHP function). diff --git a/reference/constraints/MinLength.rst b/reference/constraints/MinLength.rst index 9bcd3cc2a67..e4fe931ab43 100644 --- a/reference/constraints/MinLength.rst +++ b/reference/constraints/MinLength.rst @@ -1,6 +1,12 @@ MinLength ========= +.. caution:: + + The MinLength constraint is deprecated since version 2.1 and will be removed + in Symfony 2.3. Use :doc:`/reference/constraints/Length` with the ``min`` + option instead. + Validates that the length of a string is at least as long as the given limit. +----------------+-------------------------------------------------------------------------+ @@ -82,4 +88,4 @@ charset 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. \ No newline at end of file +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..e246a0e9730 --- /dev/null +++ b/reference/constraints/Range.rst @@ -0,0 +1,118 @@ +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 + 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 + + + + + + + + + + + + + +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/Regex.rst b/reference/constraints/Regex.rst index b277756122d..30bc07fd5fc 100644 --- a/reference/constraints/Regex.rst +++ b/reference/constraints/Regex.rst @@ -138,4 +138,4 @@ message **type**: ``string`` **default**: ``This value is not valid`` -This is the message that will be shown if this validator fails. \ No newline at end of file +This is the message that will be shown if this validator fails. diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index 4820291fb92..84207fff390 100644 --- a/reference/constraints/UniqueEntity.rst +++ b/reference/constraints/UniqueEntity.rst @@ -106,4 +106,6 @@ 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. diff --git a/reference/constraints/UserPassword.rst b/reference/constraints/UserPassword.rst new file mode 100644 index 00000000000..abfacd54754 --- /dev/null +++ b/reference/constraints/UserPassword.rst @@ -0,0 +1,71 @@ +UserPassword +============ + +.. versionadded:: 2.1 + This constraint is new in version 2.1. + +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\\Constraint\\UserPassword` | ++----------------+-------------------------------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Security\\Core\\Validator\\Constraint\\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\Constraint\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\Constraint as SecurityAssert; + + class ChangePassword + { + /** + * @SecurityAssert\UserPassword( + * message = "Wrong value for your current password" + * ) + */ + protected $oldPassword; + } + +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 a0d88b14399..7bc49fc416a 100644 --- a/reference/constraints/Valid.rst +++ b/reference/constraints/Valid.rst @@ -50,13 +50,15 @@ an ``Address`` instance in the ``$address`` property. - NotBlank: ~ zipCode: - NotBlank: ~ - - MaxLength: 5 + - Length: + max: 5 Acme\HelloBundle\Author: properties: firstName: - NotBlank: ~ - - MinLength: 4 + - Length: + min: 4 lastName: - NotBlank: ~ @@ -69,14 +71,18 @@ an ``Address`` instance in the ``$address`` property. - 5 + + + - 4 + + + @@ -97,7 +103,7 @@ an ``Address`` instance in the ``$address`` property. /** * @Assert\NotBlank - * @Assert\MaxLength(5) + * @Assert\Length(max = "5") */ protected $zipCode; } @@ -107,7 +113,7 @@ an ``Address`` instance in the ``$address`` property. { /** * @Assert\NotBlank - * @Assert\MinLength(4) + * @Assert\Length(min = "4") */ protected $firstName; @@ -124,7 +130,7 @@ an ``Address`` instance in the ``$address`` property. // src/Acme/HelloBundle/Address.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; - use Symfony\Component\Validator\Constraints\MaxLength; + use Symfony\Component\Validator\Constraints\Length; class Address { @@ -136,14 +142,16 @@ an ``Address`` instance in the ``$address`` property. { $metadata->addPropertyConstraint('street', new NotBlank()); $metadata->addPropertyConstraint('zipCode', new NotBlank()); - $metadata->addPropertyConstraint('zipCode', new MaxLength(5)); + $metadata->addPropertyConstraint( + 'zipCode', + new Length(array("max" => 5))); } } // src/Acme/HelloBundle/Author.php 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 { @@ -156,7 +164,7 @@ an ``Address`` instance in the ``$address`` property. public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('firstName', new NotBlank()); - $metadata->addPropertyConstraint('firstName', new MinLength(4)); + $metadata->addPropertyConstraint('firstName', new Length(array("min" => 4))); $metadata->addPropertyConstraint('lastName', new NotBlank()); } } diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc index 08d26f02c63..b9ea2184ca3 100644 --- a/reference/constraints/map.rst.inc +++ b/reference/constraints/map.rst.inc @@ -18,6 +18,7 @@ String Constraints * :doc:`Email ` * :doc:`MinLength ` * :doc:`MaxLength ` +* :doc:`Length ` * :doc:`Url ` * :doc:`Regex ` * :doc:`Ip ` @@ -27,6 +28,7 @@ Number Constraints * :doc:`Max ` * :doc:`Min ` +* :doc:`Range ` Date Constraints ~~~~~~~~~~~~~~~~ @@ -40,6 +42,7 @@ Collection Constraints * :doc:`Choice ` * :doc:`Collection ` +* :doc:`Count ` * :doc:`UniqueEntity ` * :doc:`Language ` * :doc:`Locale ` @@ -56,4 +59,5 @@ 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 952056e76ff..f661fde6e61 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 | @@ -93,7 +95,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 +245,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 +283,8 @@ kernel.response +-------------------------------------------------------------------------------------------+----------+ | :class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener` | -128 | +-------------------------------------------------------------------------------------------+----------+ +| :class:`Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener` | -1024 | ++-------------------------------------------------------------------------------------------+----------+ kernel.exception ................ @@ -291,6 +297,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 @@ -712,5 +773,5 @@ 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.1/src/Symfony/Component/HttpKernel/KernelEvents.php .. _`SwiftMailer's Plugin Documentation`: http://swiftmailer.org/docs/plugins.html diff --git a/reference/forms/twig_reference.rst b/reference/forms/twig_reference.rst index d37123a4249..55a4704390e 100644 --- a/reference/forms/twig_reference.rst +++ b/reference/forms/twig_reference.rst @@ -22,8 +22,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. @@ -53,7 +53,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 @@ -76,7 +76,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 @@ -132,18 +132,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). @@ -174,4 +181,5 @@ to see what options you have available. {# does **not** work - the variables are not recursive #} {{ form_widget(form, { 'attr': {'class': 'foo'} }) }} -.. _`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.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig diff --git a/reference/forms/types/collection.rst b/reference/forms/types/collection.rst index d302801e56b..2a975a7f00a 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`_ | @@ -127,9 +128,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 @@ -170,10 +171,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 @@ -283,8 +284,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. @@ -299,7 +300,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. @@ -313,6 +314,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/entity.rst b/reference/forms/types/entity.rst index f2f7a2cfc9b..4617049b299 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`_ | +-------------+------------------------------------------------------------------+ @@ -89,6 +90,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 ~~~~~~~~~~~~~ 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/form.rst b/reference/forms/types/form.rst index 1121a0befb2..31c1f877c57 100644 --- a/reference/forms/types/form.rst +++ b/reference/forms/types/form.rst @@ -4,4 +4,29 @@ 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/disabled.rst.inc + +.. include:: /reference/forms/types/options/disabled.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/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/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/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/repeated.rst b/reference/forms/types/repeated.rst index 1555354d0f8..06b00f830a7 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" @@ -80,6 +85,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/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..404b9b1dc2f 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -5,18 +5,21 @@ 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 + +----------------------------------------------------+--------------------------------------------------------------------------------------------+ | Function Syntax | Usage | +====================================================+============================================================================================+ @@ -45,13 +48,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,9 +67,15 @@ 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`. | +---------------------------------------------------------------------------------+-------------------------------------------------------------------+ @@ -116,6 +128,18 @@ Tags | ``{% endtranschoice %}`` | | +---------------------------------------------------+-------------------------------------------------------------------+ +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 ----------------