diff --git a/book/controller.rst b/book/controller.rst index 4650d9ab948..d09129f8716 100644 --- a/book/controller.rst +++ b/book/controller.rst @@ -327,8 +327,8 @@ working with forms, for example:: public function updateAction(Request $request) { $form = $this->createForm(...); - - $form->bindRequest($request); + + $form->bind($request); // ... } @@ -449,7 +449,7 @@ object that's returned from that controller:: )); // ... further modify the response or return it directly - + return $response; } @@ -478,7 +478,7 @@ value to each variable. a shortcut for core Symfony2 functionality. A forward can be accomplished directly via the ``http_kernel`` service. A forward returns a ``Response`` object:: - + $httpKernel = $this->container->get('http_kernel'); $response = $httpKernel->forward('AcmeHelloBundle:Hello:fancy', array( 'name' => $name, @@ -517,7 +517,7 @@ The Symfony templating engine is explained in great detail in the The ``renderView`` method is a shortcut to direct use of the ``templating`` service. The ``templating`` service can also be used directly:: - + $templating = $this->get('templating'); $content = $templating->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name)); @@ -617,8 +617,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. @@ -640,11 +640,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(...)); } @@ -663,19 +663,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')): ?> + + getFlashBag()->get('notice') as $message): ?>
- getFlash('notice') ?> + $message
" ?> - + By design, flash messages are meant to live for exactly one request (they're "gone in a flash"). They're designed to be used across redirects exactly as @@ -694,7 +694,7 @@ headers and content that's sent back to the client:: // create a simple Response with a 200 status code (the default) $response = new Response('Hello '.$name, 200); - + // create a JSON-response with a 200 status code $response = new Response(json_encode(array('name' => $name))); $response->headers->set('Content-Type', 'application/json'); diff --git a/book/doctrine.rst b/book/doctrine.rst index 57210da00fb..edc39aa911f 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: @@ -405,7 +407,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(); @@ -543,7 +545,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) { @@ -607,7 +609,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'); @@ -783,7 +785,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(); @@ -977,7 +979,7 @@ Now, let's see the 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(); @@ -1408,14 +1410,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 6b24d9f1dbb..dad3ff8d8bd 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -214,8 +214,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 @@ -227,13 +227,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. @@ -385,19 +390,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 @@ -750,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')); @@ -801,11 +850,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:: @@ -818,7 +869,9 @@ the choice is ultimately up to 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``:: - public function buildForm(FormBuilder $builder, array $options) + use Symfony\Component\Form\FormBuilderInterface; + + public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('property_path' => false)); @@ -846,7 +899,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(); @@ -928,20 +981,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() @@ -957,7 +1011,9 @@ class: .. code-block:: php - public function buildForm(FormBuilder $builder, array $options) + use Symfony\Component\Form\FormBuilderInterface; + + public function buildForm(FormBuilderInterface $builder, array $options) { // ... @@ -965,7 +1021,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:: + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Acme\TaskBundle\Entity\Category', + 'cascade_validation' => true, + )); + } + +Render the ``Category`` fields in the same way as the original ``Task`` fields: .. configuration-block:: @@ -1044,7 +1111,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) }} @@ -1052,19 +1119,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: @@ -1090,8 +1157,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 @@ -1105,6 +1172,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:: @@ -1129,10 +1209,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``, @@ -1141,13 +1221,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:: @@ -1169,16 +1249,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:: @@ -1249,9 +1330,9 @@ to define form output. {% form_theme form _self %} {# make the form fragment customization #} - {% block field_row %} + {% block form_row %} {# custom field row output #} - {% endblock field_row %} + {% endblock form_row %} {% block content %} {# ... #} @@ -1345,19 +1426,21 @@ that all un-rendered fields are output. The CSRF token can be customized on a form-by-form basis. For example:: + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + class TaskType extends AbstractType { // ... - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( + $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Task', 'csrf_protection' => true, 'csrf_field_name' => '_token', // a unique key to help generate the secret token 'intention' => 'task_item', - ); + )); } // ... @@ -1399,8 +1482,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(); @@ -1465,14 +1548,15 @@ but here's a short example:: // ... ; -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`` +Now, when you call `$form->bind($request)`, the constraints setup here are run +against your form's data. If you're using a form class, override the ``setDefaultOptions()`` method to specify the option:: namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\MinLength; use Symfony\Component\Validator\Constraints\Collection; @@ -1481,14 +1565,16 @@ method to specify the option:: { // ... - public function getDefaultOptions(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { $collectionConstraint = new Collection(array( 'name' => new MinLength(5), 'email' => new Email(array('message' => 'Invalid email address')), )); - return array('validation_constraint' => $collectionConstraint); + $resolver->setDefaults(array( + 'validation_constraint' => $collectionConstraint + )); } } diff --git a/book/from_flat_php_to_symfony2.rst b/book/from_flat_php_to_symfony2.rst index f624c34eb0d..5a9dd919a81 100644 --- a/book/from_flat_php_to_symfony2.rst +++ b/book/from_flat_php_to_symfony2.rst @@ -11,7 +11,7 @@ better software than with flat PHP, you'll see for yourself. In this chapter, you'll write a simple application in flat PHP, and then refactor it to be more organized. You'll travel through time, seeing the decisions behind why web development has evolved over the past several years -to where it is now. +to where it is now. By the end, you'll see how Symfony2 can rescue you from mundane tasks and let you take back control of your code. @@ -133,7 +133,7 @@ to the area of *your* code that processes user input and prepares the response. In this case, our controller prepares data from the database and then includes a template to present that data. With the controller isolated, you could easily change *just* the template file if you needed to render the blog -entries in some other format (e.g. ``list.json.php`` for JSON format). +entries in some other format (e.g. ``list.json.php`` for JSON format). Isolating the Application (Domain) Logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -424,7 +424,7 @@ 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. -First, `download symfony`_ and place it into a ``vendor/symfony/`` directory. +First, `download symfony`_ and place it into a ``vendor/symfony/symfony/`` directory. Next, create an ``app/bootstrap.php`` file. Use it to ``require`` the two files in the application and to configure the autoloader: @@ -434,11 +434,11 @@ files in the application and to configure the autoloader: // bootstrap.php require_once 'model.php'; require_once 'controllers.php'; - require_once 'vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; + require_once 'vendor/symfony/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; $loader = new Symfony\Component\ClassLoader\UniversalClassLoader(); $loader->registerNamespaces(array( - 'Symfony' => __DIR__.'/../vendor/symfony/src', + 'Symfony' => __DIR__.'/../vendor/symfony/symfony/src', )); $loader->register(); @@ -551,7 +551,7 @@ them for you. Here's the same sample application, now built in Symfony2: { public function listAction() { - $posts = $this->get('doctrine')->getEntityManager() + $posts = $this->get('doctrine')->getManager() ->createQuery('SELECT p FROM AcmeBlogBundle:Post p') ->execute(); @@ -561,10 +561,10 @@ 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); - + if (!$post) { // cause the 404 page not found to be displayed throw $this->createNotFoundException(); @@ -581,7 +581,7 @@ now quite a bit simpler: .. code-block:: html+php - + extend('::layout.html.php') ?> set('title', 'List of Posts') ?> diff --git a/book/http_cache.rst b/book/http_cache.rst index e66887e51de..376a5417c91 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. diff --git a/book/installation.rst b/book/installation.rst index 97c8f13660d..e3733e2bff1 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,47 @@ 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. -When you're finished, you should have a ``Symfony/`` directory that looks -something like this: +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 dev-master + +.. tip:: + + For an exact version, replace `dev-master` with the latest Symfony version + (e.g. 2.1.1). For details, see the `Symfony Installation Page`_ + +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 +87,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 + 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 +198,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. @@ -135,9 +226,9 @@ If there are any issues, correct them now before moving on. **2. Using Acl on a system that does not support chmod +a** - Some systems don't support ``chmod +a``, but do support another utility + Some systems don't support ``chmod +a``, but do support another utility called ``setfacl``. You may need to `enable ACL support`_ on your partition - and install setfacl before using it (as is the case with Ubuntu), like + and install setfacl before using it (as is the case with Ubuntu), like so: .. code-block:: bash @@ -175,7 +266,7 @@ 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! @@ -194,6 +285,9 @@ If you're new to Symfony, join us in the ":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 -------------------- @@ -215,16 +309,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 \ No newline at end of file diff --git a/book/internals.rst b/book/internals.rst index bc4f0134380..6d4a85273fc 100644 --- a/book/internals.rst +++ b/book/internals.rst @@ -377,6 +377,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 @@ -510,7 +519,6 @@ the configuration for the development environment: web_profiler: toolbar: true intercept_redirects: true - verbose: true .. code-block:: xml @@ -550,10 +558,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 02f362aaa20..420c3fb6eb2 100644 --- a/book/page_creation.rst +++ b/book/page_creation.rst @@ -105,7 +105,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: / @@ -255,10 +255,10 @@ application should greet you: .. code-block:: text http://localhost/app.php/hello/Ryan - + If you get an error, it's likely because you need to clear your cache by running: - + .. code-block:: bash $ php app/console cache:clear --env=prod --no-debug @@ -729,20 +729,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: @@ -755,19 +748,13 @@ format you prefer: - + - - + + - - - - - - - + @@ -777,23 +764,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, + // ... ), )); @@ -832,6 +809,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 cee46849e6d..92702b77cbb 100644 --- a/book/performance.rst +++ b/book/performance.rst @@ -55,20 +55,23 @@ 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 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:: -To use this class loader, simply adapt your ``autoloader.php`` as follows: - -.. code-block:: php + // app.php + // ... - // app/autoload.php - require __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php'; + $loader = require_once __DIR__.'/../app/bootstrap.php.cache'; - use Symfony\Component\ClassLoader\ApcUniversalClassLoader; + // 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:: @@ -109,7 +112,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 @@ -122,5 +125,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 +.. _`autoloader.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 915f6554270..5c9e8603153 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 61385ff42e0..c67f93031a6 100644 --- a/book/routing.rst +++ b/book/routing.rst @@ -786,7 +786,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 @@ -1055,6 +1060,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 5a8f9d5e1c4..3fb2b2ba27f 100644 --- a/book/security.rst +++ b/book/security.rst @@ -54,9 +54,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 @@ -83,8 +84,10 @@ authentication (i.e. the old-school username/password box): - - + + + + @@ -109,9 +112,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'), + ), ), ), ), @@ -399,8 +404,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 @@ -912,9 +921,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 @@ -922,8 +932,10 @@ In fact, you've seen this already in the example in this chapter. - - + + + + @@ -934,9 +946,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'), + ), ), ), ), @@ -1008,6 +1022,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, @@ -1081,9 +1101,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: @@ -1097,8 +1118,10 @@ do the following: - - + + + + @@ -1111,9 +1134,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()`` @@ -1238,10 +1273,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 } @@ -1250,11 +1287,15 @@ a new provider that chains the two together: - in_memory - user_db + + in_memory + user_db + - + + + @@ -1267,11 +1308,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( @@ -1298,16 +1343,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 - + + + @@ -1318,8 +1368,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'), ), @@ -1505,9 +1557,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 diff --git a/book/service_container.rst b/book/service_container.rst index 27d942834b3..efa60776f07 100644 --- a/book/service_container.rst +++ b/book/service_container.rst @@ -466,7 +466,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" } @@ -475,7 +474,7 @@ invokes the service container extension inside the ``FrameworkBundle``: .. code-block:: xml - + @@ -487,7 +486,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'), @@ -508,7 +506,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 363f926b9b9..29fdf0d26d6 100644 --- a/book/templating.rst +++ b/book/templating.rst @@ -127,15 +127,15 @@ Throughout this chapter, template examples will be shown in both Twig and PHP. not program logic. The more you use Twig, the more you'll appreciate and benefit from this distinction. And of course, you'll be loved by web designers everywhere. - + Twig can also do things that PHP can't, such as whitespace control, sandboxing, and the inclusion of custom functions and filters that only affect templates. Twig contains little features that make writing templates easier and more concise. Take the following example, which combines a loop with a logical ``if`` statement: - + .. code-block:: html+jinja - +
    {% for user in users %}
  • {{ user.username }}
  • @@ -638,6 +638,60 @@ 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 asyncronously 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 '...:news' with {}, {'standalone': 'js'} %} + + .. code-block:: php + + render('...:news', array(), 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 @@ -810,7 +864,7 @@ advantage of Symfony's template inheritance. This section will teach you the philosophy behind including stylesheet and Javascript assets in Symfony. Symfony also packages another library, called Assetic, which follows this philosophy but allows you to do much - more interesting things with those assets. For more information on + more interesting things with those assets. For more information on using Assetic see :doc:`/cookbook/assetic/asset_management`. @@ -851,13 +905,13 @@ page. From inside that contact page's template, do the following: {% block stylesheets %} {{ parent() }} - + {% endblock %} - + {# ... #} -In the child template, you simply override the ``stylesheets`` block and +In the child template, you simply override the ``stylesheets`` block and put your new stylesheet tag inside of that block. Of course, since you want to add to the parent block's content (and not actually *replace* it), you should use the ``parent()`` Twig function to include everything from the ``stylesheets`` @@ -1258,6 +1312,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 ---------------- @@ -1282,7 +1356,7 @@ pattern is to do the following:: public function indexAction() { $format = $this->getRequest()->getRequestFormat(); - + return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig'); } @@ -1349,3 +1423,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 990b75da287..680902853c0 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 @@ -414,13 +415,19 @@ HTTP layer. For a list of services available in your application, use the Accessing the Profiler Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -On each request, the Symfony profiler collects and stores a lot of data about -the internal handling of that request. For example, the profiler could be -used to verify that a given page executes less than a certain number of database +On each request, you can enable the Symfony profiler to collect data about the +internal handling of that request. For example, the profiler could be used to +verify that a given page executes less than a certain number of database queries when loading. To get the Profiler for the last request, do the following:: + // enable the profiler for the very next request + $client->enableProfiler(); + + $crawler = $client->request('GET', '/profiler'); + + // get the profile $profile = $client->getProfile(); For specific details on using the profiler inside a test, see the diff --git a/book/translation.rst b/book/translation.rst index cbf522e9b40..80169df1b4d 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -36,7 +36,8 @@ the process has several common steps: 3. Create translation resources for each supported locale that translate each message in the application; -4. Determine, set and manage the user's locale in the session. +4. Determine, set and manage the user's locale for the request and optionally + on the user's entire session. .. index:: single: Translations; Configuration @@ -80,7 +81,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 @@ -147,7 +149,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 overriden 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 @@ -483,18 +494,31 @@ 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: .. code-block:: php - $locale = $this->get('session')->getLocale(); + // access the reqest object in a standard controller + $request = $this->getRequest(); + + $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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -502,8 +526,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:: @@ -511,28 +535,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, @@ -774,6 +803,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 ~~~~~~~~~~~~~ @@ -793,7 +833,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: @@ -950,7 +990,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 4b8202f8f0f..fff30e78b6c 100644 --- a/book/validation.rst +++ b/book/validation.rst @@ -225,8 +225,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 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 90089ed2d02..1c0758e8e4e 100644 --- a/components/class_loader.rst +++ b/components/class_loader.rst @@ -34,6 +34,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:: @@ -43,6 +46,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(); @@ -71,11 +77,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(); @@ -86,11 +92,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(); @@ -105,10 +111,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(); diff --git a/components/config/definition.rst b/components/config/definition.rst index dd867c82a55..cc2a610ef08 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -212,6 +212,35 @@ has a certain value: ->end() ; +Optional Sections +----------------- + +.. versionadded:: 2.1 + The ``canBeEnabled`` and ``canBeDisabled`` methods are new in Symfony 2.2 + +If you have entire sections which are optional and can be enabled/disabled, +you can take advantage of the shortcut +:method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeEnabled` and +:method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeDisabled` methods:: + + $arrayNode + ->canBeEnabled() + ; + + // is equivalent to + + $arrayNode + ->treatFalseLike(array('enabled' => false)) + ->treatTrueLike(array('enabled' => true)) + ->treatNullLike(array('enabled' => true)) + ->children() + ->booleanNode('enabled') + ->defaultFalse() + ; + +The ``canBeDisabled`` method looks about the same except that the section +would be enabled by default. + Merging options --------------- diff --git a/components/console/introduction.rst b/components/console/introduction.rst index d36909fe5f5..7e42c0a4f46 100755 --- a/components/console/introduction.rst +++ b/components/console/introduction.rst @@ -288,6 +288,77 @@ if you needed to know the name of something, you might do the following:: 'foo' ); +Displaying a Progress Bar +------------------------- + +.. versionadded:: 2.2 + The ``progress`` helper was added in Symfony 2.2. + +When executing longer-running commands, it may be helpful to show progress +information, which updates as your command runs: + +.. image:: /images/components/console/progress.png + +To display progress details, use the :class:`Symfony\\Component\\Console\\Helper\\ProgressHelper`, +pass it a total number of units, and advance the progress as your command executes:: + + $progress = $app->getHelperSet()->get('progress'); + + $progress->start($output, 50); + $i = 0; + while ($i++ < 50) { + // do some work + + // advance the progress bar 1 unit + $progress->advance(); + } + + $progress->finish(); + +The appearance of the progress output can be customized as well, with a number +of different levels of verbosity. Each of these displays different possible +items - like percentage completion, a moving progress bar, or current/total +information (e.g. 10/50):: + + $progress->setFormat(ProgressHelper::FORMAT_QUIET); + $progress->setFormat(ProgressHelper::FORMAT_NORMAL); + $progress->setFormat(ProgressHelper::FORMAT_VERBOSE); + $progress->setFormat(ProgressHelper::FORMAT_QUIET_NOMAX); + // the default value + $progress->setFormat(ProgressHelper::FORMAT_NORMAL_NOMAX); + $progress->setFormat(ProgressHelper::FORMAT_VERBOSE_NOMAX); + +You can also control the different characters and the width used for the +progress bar:: + + // the finished part of the bar + $progress->setBarCharacter('='); + // the unfinished part of the bar + $progress->setEmptyBarCharacter(' '); + $progress->setProgressChar('|'); + $progress->setBarWidth(50); + +To see other available options, check the API documentation for +:class:`Symfony\\Component\\Console\\Helper\\ProgressHelper`. + +.. caution:: + + For performance reasons, be careful to not set the total number of steps + to a high number. For example, if you're iterating over a large number + of items, consider a smaller "step" number that updates on only some + iterations:: + + $progress->start($output, 500); + $i = 0; + while ($i++ < 50000) { + // ... do some work + + // advance every 100 iterations + if ($i % 100 == 0) { + $progress->advance(); + } + } + Testing Commands ---------------- diff --git a/components/dependency_injection/advanced.rst b/components/dependency_injection/advanced.rst index 5c9eafd21e4..d7a7e5280c9 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..08e222f493b --- /dev/null +++ b/components/event_dispatcher/container_aware_dispatcher.rst @@ -0,0 +1,99 @@ +.. 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..9ca94c39b8b --- /dev/null +++ b/components/event_dispatcher/generic_event.rst @@ -0,0 +1,107 @@ +.. 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 a4cbd0a18b8..0330e318569 100644 --- a/components/event_dispatcher/introduction.rst +++ b/components/event_dispatcher/introduction.rst @@ -117,9 +117,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; @@ -134,9 +132,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')); @@ -163,9 +159,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; @@ -176,9 +170,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; @@ -197,9 +189,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 @@ -230,9 +220,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; @@ -268,9 +256,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; @@ -301,9 +287,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; @@ -320,9 +304,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; @@ -333,51 +315,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 @@ -394,9 +331,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; @@ -442,9 +377,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; @@ -471,9 +404,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; @@ -487,6 +418,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 .. _`Symfony2 HttpKernel component`: https://github.com/symfony/HttpKernel .. _Closures: http://php.net/manual/en/functions.anonymous.php diff --git a/components/filesystem.rst b/components/filesystem.rst new file mode 100644 index 00000000000..21d93946448 --- /dev/null +++ b/components/filesystem.rst @@ -0,0 +1,238 @@ +.. 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 PEAR ( `pear.symfony.com/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 occured 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, 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. diff --git a/components/finder.rst b/components/finder.rst index efdbce57729..734c4365edc 100644 --- a/components/finder.rst +++ b/components/finder.rst @@ -158,6 +158,26 @@ The ``notName()`` method excludes files matching a pattern:: $finder->files()->notName('*.rb'); +File Contents +~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + Methods ``contains()`` and ``notContains()`` have been + introduced 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 ~~~~~~~~~ @@ -170,8 +190,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 @@ -220,6 +243,25 @@ it is called with the file as a :class:`Symfony\\Component\\Finder\\SplFileInfo` instance. The file is excluded from the result set if the Closure returns ``false``. +Reading contents of returned files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.1 + Method ``getContents()`` have been introduced in version 2.1. + +The contents of returned files can be read with +:method:`Symfony\\Component\\Finder\\SplFileInfo::getContents`:: + + use Symfony\Component\Finder\Finder; + + $finder = new Finder(); + $finder->files()->in(__DIR__); + + foreach ($finder as $file) { + $contents = $file->getContents(); + ... + } + .. _strtotime: http://www.php.net/manual/en/datetime.formats.php .. _Iterator: http://www.php.net/manual/en/spl.iterators.php .. _protocol: http://www.php.net/manual/en/wrappers.php 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 966cde9d372..374d7ee7cea 100644 --- a/components/http_foundation/introduction.rst +++ b/components/http_foundation/introduction.rst @@ -336,8 +336,47 @@ 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); + 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`. diff --git a/components/http_foundation/session_configuration.rst b/components/http_foundation/session_configuration.rst new file mode 100644 index 00000000000..bbe8e9585c3 --- /dev/null +++ b/components/http_foundation/session_configuration.rst @@ -0,0 +1,262 @@ +.. 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\\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 setting, 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 expire 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()->getLastUpdate() > $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 allow 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..4d7ded9bbbb --- /dev/null +++ b/components/http_foundation/sessions.rst @@ -0,0 +1,332 @@ +.. 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 funtions. + 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 settings 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/index.rst b/components/index.rst index 355580d0d1b..ea482e29589 100644 --- a/components/index.rst +++ b/components/index.rst @@ -11,6 +11,7 @@ The Components dom_crawler dependency_injection/index event_dispatcher/index + filesystem finder http_foundation/index locale diff --git a/components/map.rst.inc b/components/map.rst.inc index eb6fff76cd9..83788540c27 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -37,7 +37,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` @@ -45,6 +51,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` * **Locale** diff --git a/components/process.rst b/components/process.rst index 8887e14f9ed..361235a2356 100644 --- a/components/process.rst +++ b/components/process.rst @@ -62,3 +62,15 @@ instead:: EOF); $process->run(); + +.. 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(); + diff --git a/components/routing.rst b/components/routing.rst index e1b8e2079ed..323e67653ff 100644 --- a/components/routing.rst +++ b/components/routing.rst @@ -137,7 +137,6 @@ the POST method and a secure connection:: array('suffix' => '.*') ); - Using Prefixes ~~~~~~~~~~~~~~ diff --git a/components/yaml.rst b/components/yaml.rst index 41c49826a01..4795b0f88e3 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -127,15 +127,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 17764296577..ebb7a7ffd5f 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 ~~~~~~~~~~~~~ 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 81a38ee7d6a..ef32d150a59 100644 --- a/cookbook/bundles/extension.rst +++ b/cookbook/bundles/extension.rst @@ -466,6 +466,7 @@ that an unsupported option was passed:: public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); // ... @@ -481,6 +482,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 43a12b89c59..382c5f2d7b5 100644 --- a/cookbook/configuration/pdo_session_storage.rst +++ b/cookbook/configuration/pdo_session_storage.rst @@ -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/controller/error_pages.rst b/cookbook/controller/error_pages.rst index af60f1fb737..91860207604 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..693552fd0eb 100644 --- a/cookbook/debugging.rst +++ b/cookbook/debugging.rst @@ -46,7 +46,7 @@ below:: // ... // require_once __DIR__.'/../app/bootstrap.php.cache'; - require_once __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; + require_once __DIR__.'/../vendor/symfony/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; require_once __DIR__.'/../app/autoload.php'; require_once __DIR__.'/../app/AppKernel.php'; 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 d0e05216ff4..516304c5ce2 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 \ No newline at end of file diff --git a/cookbook/doctrine/file_uploads.rst b/cookbook/doctrine/file_uploads.rst index 5ebb1c29a46..71111b5fe28 100644 --- a/cookbook/doctrine/file_uploads.rst +++ b/cookbook/doctrine/file_uploads.rst @@ -151,10 +151,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(); @@ -190,7 +190,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(); @@ -307,7 +307,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 b227f509e9c..09d6c975352 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() @@ -184,11 +187,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('terms', 'checkbox', array('property_path' => 'termsAccepted')); @@ -250,7 +253,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..c2b661ae674 --- /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 7f5fac8dd7f..38b2665a53f 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, we will assume 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 2b03002f4e3..0e07a58d428 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 f046637541e..ef92710eec9 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 b3b40f5a866..fe91589dbdd 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,13 +245,13 @@ 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/data_transformers.rst b/cookbook/form/data_transformers.rst index b4eb83dc58c..3614e51a2b7 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,7 +121,7 @@ issue field in some form. // add a normal text field, but add our transformer to it $builder->add( $builder->create('issue', 'text') - ->prependNormTransformer($transformer) + ->addModelTransformer($transformer) ); } @@ -152,51 +153,56 @@ 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); -Norm and Client Transformers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Model and View Transformers +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In the above example, the transformer was used as a "norm" transformer. +.. 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 "model" transformer. In fact, there are two different type of transformers and three different types of underlying data. In any form, the 3 different types of data are: -1) **App data** - This is the data in the format used in your application +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 "app" data. +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 our 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 did we use the norm transformer? ---------------------------------------- +So why did we use the model transformer? +---------------------------------------- In our example, the field is a ``text`` field, and we always expect a text -field 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* +field 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 +229,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 +249,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 +301,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 79a07ab9d86..a1b06fa7505 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'); @@ -56,13 +56,13 @@ to an Event Subscriber:: // src/Acme/DemoBundle/Form/Type/ProductType.php namespace Acme\DemoBundle\Form\Type; - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\AbstractType + 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); @@ -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')); } } } diff --git a/cookbook/form/dynamic_form_with_services.rst b/cookbook/form/dynamic_form_with_services.rst new file mode 100644 index 00000000000..c2830360b39 --- /dev/null +++ b/cookbook/form/dynamic_form_with_services.rst @@ -0,0 +1,162 @@ +.. index:: + single: Form; Events + +How to Dynamically Generate Forms based on user data +==================================================== + +Sometimes you want a form to be generated dynamically based not only on data +from this form (see :doc:`Dynamic form generation `) +but also on something else. For example depending on the user currently using +the application. If you have a social website where a user can only message +people who are his friends on the website, then the current user doesn't need to +be included as a field of your form, but a "choice list" of whom to message +should only contain users that are the current user's friends. + +Creating the form type +---------------------- + +Using an event listener, our form could be built like this:: + + namespace Acme\WhateverBundle\FormType; + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\Form\FormEvents; + use Symfony\Component\Form\FormEvent; + use Symfony\Component\Security\Core\SecurityContext; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + use Acme\WhateverBundle\FormSubscriber\UserListener; + + class FriendMessageFormType extends AbstractType + { + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('subject', 'text') + ->add('body', 'textarea') + ; + $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){ + // Add a choice list of friends of the current application user + }); + } + + public function getName() + { + return 'acme_friend_message'; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + } + } + +The problem is now to get the current application user and create a choice field +that would contain only this user's friends. + +Luckily it is pretty easy to inject a service inside of the form. This can be +done in the constructor.:: + + private $security_context; + + public function __construct(SecurityContext $security_context) + { + $this->security_context = $security_context; + } + +.. note:: + + You might wonder, now that we have access to the User (through) the security + context, why don't we just use that inside of the buildForm function and + still use a listener? + This is because doing so in the buildForm method would result in the whole + form type being modified and not only one form instance. + +Customizing the form type +------------------------- + +Now that we have all the basics in place, we can put everything in place and add +our listener:: + + class FriendMessageFormType extends AbstractType + { + private $security_context; + + public function __construct(SecurityContext $security_context) + { + $this->security_context = $security_context; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('subject', 'text') + ->add('body', 'textarea') + ; + $user = $this->security_context->getToken()->getUser(); + $factory = $builder->getFormFactory(); + + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + function(FormEvent $event) use($user, $factory){ + $form = $event->getForm(); + $user_id = $user->getId(); + + $form_options = [ + 'class' => 'Acme\WhateverBundle\Document\User', + 'multiple' => false, + 'expanded' => false, + 'property' => 'fullName', + 'query_builder' => function(DocumentRepository $dr) use ($user_id) { + return $dr->createQueryBuilder()->field('friends.$id')->equals(new \MongoId($user_id)); + } + ]; + + $form->add($factory->createNamed('friend', 'document', null, $form_options)); + } + ); + } + + public function getName() + { + return 'acme_friend_message'; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + } + } + +Using the form +-------------- + +Our form is now ready to use. We have two possible ways to use it inside of a +controller. Either by creating it everytime and remembering to pass the security +context, or by defining it as a service. This is the option we will show here. + +To define your form as a service, you simply add the configuration to your +config.yml file:: + + acme.form.friend_message: + class: Acme\WhateverBundle\FormType\FriendMessageType + arguments: [@security.context] + tags: + - { name: form.type, alias: acme_friend_message} + +By adding the form as a service, we make sure that this form can now be used +simply from anywhere. If you need to add it to another form, you will just need +to use:: + + $builder->add('message', 'acme_friend_message'); + +If you wish to create it from within a controller or any other service that has +access to the form factory, you then use:: + + // FriendMessageController.php + public function friendMessageAction() + { + $form = $this->get('form.factory')->create('acme_friend_message'); + $form = $form->createView(); + + return compact('form'); + } \ No newline at end of file diff --git a/cookbook/form/form_collections.rst b/cookbook/form/form_collections.rst index 96c82c3e096..7acda5e8d16 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 we 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 our 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 we'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: @@ -393,9 +400,9 @@ one example: // Get the data-prototype we explained earlier var prototype = collectionHolder.attr('data-prototype'); - // Replace '$$name$$' in the prototype's HTML to + // Replace '__name__' in the prototype's HTML to // instead be a number based on the current collection's length. - var newForm = prototype.replace(/\$\$name\$\$/g, collectionHolder.children().length); + var newForm = prototype.replace(/__name__/g, collectionHolder.children().length); // Display the form in the page in an li, before the "Add a tag" link li var $newFormLi = $('
        • ').append(newForm); @@ -502,8 +509,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'); @@ -589,7 +597,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) { @@ -603,8 +611,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 b77590c7ada..e60e3c0c7f5 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') ?> /> diff --git a/cookbook/form/use_virtuals_forms.rst b/cookbook/form/use_virtuals_forms.rst index 2b75538076f..2c286c9b07e 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 we absolutely want to have a dedicated form type to deal with location (reme The ``virtual`` form field option is the solution. -We can set the option ``'virtual' => true`` in the ``getDefaultOptions`` method +We 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 38d669b940b..8b5325fab57 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: syslog: @@ -123,6 +124,7 @@ easily. Your formatter must implement .. code-block:: yaml + # app/config/config.yml services: my_formatter: class: Monolog\Formatter\JsonFormatter @@ -177,7 +179,7 @@ using a processor. namespace Acme\MyBundle; - use Symfony\Component\HttpFoundation\Session; + use Symfony\Component\HttpFoundation\Session\Session; class SessionRequestProcessor { @@ -205,10 +207,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 e838e76be3b..bb8551345cc 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -48,6 +48,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` @@ -79,6 +80,7 @@ * :doc:`/cookbook/logging/monolog` * :doc:`/cookbook/logging/monolog_email` + * :doc:`/cookbook/logging/channels_handlers` * :doc:`/cookbook/profiler/index` @@ -117,6 +119,7 @@ * :doc:`/cookbook/security/securing_services` * :doc:`/cookbook/security/custom_provider` * :doc:`/cookbook/security/custom_authentication_provider` + * :doc:`/cookbook/security/target_path` * :doc:`/cookbook/templating/index` diff --git a/cookbook/security/custom_authentication_provider.rst b/cookbook/security/custom_authentication_provider.rst index 9559668490c..6f2d474a7b5 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 b1f75d26104..8a23e5b0d3c 100644 --- a/cookbook/security/entity_provider.rst +++ b/cookbook/security/entity_provider.rst @@ -132,34 +132,41 @@ focus on the most important methods that come from the public function eraseCredentials() { } - - /** - * @inheritDoc - */ - public function equals(UserInterface $user) - { - return $this->username === $user->getUsername(); - } } 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 -as we don't care about it in 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(); + } Below is an export of my ``User`` table from MySQL. For details on how to create user records and encode their password, see :ref:`book-security-encoding-user-password`. diff --git a/cookbook/security/form_login.rst b/cookbook/security/form_login.rst index 3ea50f33672..dcae87bddc7 100644 --- a/cookbook/security/form_login.rst +++ b/cookbook/security/form_login.rst @@ -113,6 +113,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 ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -203,7 +211,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:: @@ -241,6 +249,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 4e8463579e4..c1b7a6afc1f 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 ~~~~~~~~~~~~~~~~~~~~~~ @@ -131,13 +131,13 @@ 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: +the directory that the file should live in +(``vendor/sensio/framework-extra-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/sensio`` directory: .. code-block:: php @@ -146,7 +146,7 @@ namespace in the ``vendor/bundle`` directory: // ... $loader->registerNamespaces(array( ..., - 'Sensio' => __DIR__.'/../vendor/bundles', + 'Sensio' => __DIR__.'/../vendor/sensio/framework-extra-bundle', )); If the file did *not* live at this exact location, you'd receive a @@ -159,7 +159,7 @@ 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/`` +example, the ``Doctrine`` namespace lives in the ``vendor/doctrine/orm/lib/`` directory. This mapping is entirely controlled by you via the ``app/autoload.php`` file. @@ -251,7 +251,7 @@ In Symfony2, the bundles are activated inside the application kernel:: ..., new Acme\DemoBundle\AcmeDemoBundle(), ); - + return $bundles; } diff --git a/cookbook/templating/global_variables.rst b/cookbook/templating/global_variables.rst index 9cbe2b36039..0d45ebdad06 100644 --- a/cookbook/templating/global_variables.rst +++ b/cookbook/templating/global_variables.rst @@ -26,7 +26,7 @@ system, which lets you isolate or reuse the value: .. code-block:: ini - ; app/config/parameters.ini + ; app/config/parameters.yml [parameters] ga_tracking: UA-xxxxx-x diff --git a/cookbook/testing/profiling.rst b/cookbook/testing/profiling.rst index 14d1a9ebcbf..ee92790c760 100644 --- a/cookbook/testing/profiling.rst +++ b/cookbook/testing/profiling.rst @@ -11,15 +11,19 @@ various things and enforce some metrics. The Symfony2 :ref:`Profiler ` gathers a lot of data for each request. Use this data to check the number of database calls, the time -spent in the framework, ... But before writing assertions, always check that -the profiler is indeed available (it is enabled by default in the ``test`` -environment):: +spent in the framework, ... But before writing assertions, enable the profiler +and check that the profiler is indeed available (it is enabled by default in +the ``test`` environment):: class HelloControllerTest extends WebTestCase { public function testIndex() { $client = static::createClient(); + + // Enable the profiler for the next request (it does nothing if the profiler is not available) + $client->enableProfiler(); + $crawler = $client->request('GET', '/hello/Fabien'); // ... write some assertions about the Response @@ -30,7 +34,7 @@ environment):: $this->assertLessThan(10, $profile->getCollector('db')->getQueryCount()); // check the time spent in the framework - $this->assertLessThan(0.5, $profile->getCollector('timer')->getTime()); + $this->assertLessThan(500, $profile->getCollector('time')->getTotalTime()); } } } diff --git a/cookbook/validation/custom_constraint.rst b/cookbook/validation/custom_constraint.rst index 1e898be1bdf..f275464dea9 100644 --- a/cookbook/validation/custom_constraint.rst +++ b/cookbook/validation/custom_constraint.rst @@ -5,18 +5,18 @@ How to create a Custom Validation Constraint ============================================ You can create a custom constraint by extending the base constraint class, -:class:`Symfony\\Component\\Validator\\Constraint`. -As an example we're going to create a simple validator that checks if a string +:class:`Symfony\\Component\\Validator\\Constraint`. +As an example we're going to create a simple validator that checks if a string contains only alphanumeric characters. Creating Constraint class ------------------------- -First you need to create a Constraint class and extend :class:`Symfony\\Component\\Validator\\Constraint`:: +First you need to create a Constraint class and extend :class:`Symfony\\Component\\Validator\\Constraint`:: // src/Acme/DemoBundle/Validator/constraints/ContainsAlphanumeric.php namespace Acme\DemoBundle\Validator\Constraints; - + use Symfony\Component\Validator\Constraint; /** @@ -32,11 +32,11 @@ First you need to create a Constraint class and extend :class:`Symfony\\Componen The ``@Annotation`` annotation is necessary for this new constraint in order to make it available for use in classes via annotations. Options for your constraint are represented as public properties on the - constraint class. + constraint class. Creating the Validator itself ----------------------------- - + As you can see, a constraint class is fairly minimal. The actual validation is performed by a another "constraint validator" class. The constraint validator class is specified by the constraint's ``validatedBy()`` method, which @@ -52,33 +52,38 @@ 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; - + use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; 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 ----------------------- @@ -87,7 +92,7 @@ Using custom validators is very easy, just as the ones provided by Symfony2 itse .. configuration-block:: .. code-block:: yaml - + # src/Acme/BlogBundle/Resources/config/validation.yml Acme\DemoBundle\Entity\AcmeEntity: properties: @@ -100,22 +105,22 @@ Using custom validators is very easy, just as the ones provided by Symfony2 itse // src/Acme/DemoBundle/Entity/AcmeEntity.php use Symfony\Component\Validator\Constraints as Assert; use Acme\DemoBundle\Validator\Constraints as AcmeAssert; - + class AcmeEntity { // ... - + /** * @Assert\NotBlank * @AcmeAssert\ContainsAlphanumeric */ protected $name; - + // ... } .. code-block:: xml - + .. code-block:: php - + // src/Acme/DemoBundle/Entity/AcmeEntity.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; @@ -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 865076ccc68..6b8de94960e 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,44 @@ 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. + +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``. + +To learn more about Composer, see `GetComposer.org`_: 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 our repository), any other developer can use our -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`` +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 our repository, any other developer can use our 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. .. sidebar:: Upgrading Symfony Since Symfony is just a group of third-party libraries and third-party - libraries are entirely controlled through ``deps`` and ``deps.lock``, + 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. - Of course, if you've added new entries to ``deps`` or ``deps.lock``, be sure + 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). -.. caution:: - - 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. - -Hacking vendor libraries -~~~~~~~~~~~~~~~~~~~~~~~~ - -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:: text - - [AcmeAwesomeBundle] - git=http://github.com/johndoe/Acme/AwesomeBundle.git - target=/bundles/Acme/AwesomeBundle - version=the-awesome-version - -* The ``git`` option sets the URL of the library. It can use various protocols, - like ``http://`` as well as ``git://``. - -* 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. - -* 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``. - -Updating workflow -~~~~~~~~~~~~~~~~~ - -When you execute the ``php bin/vendors install``, for every library, the script first -checks if the install directory exists. - -If it does not (and ONLY if it does not), it runs a ``git clone``. - -Then, it does a ``git fetch origin`` and a ``git reset --hard the-awesome-version``. +.. _Composer: http://getcomposer.org/ +.. _GetComposer.org: http://getcomposer.org/ -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. diff --git a/cookbook/workflow/new_project_git.rst b/cookbook/workflow/new_project_git.rst index 628d262a8e7..0ec2977479e 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 cc094ff6e5c..a17022cfaf0 100644 --- a/cookbook/workflow/new_project_svn.rst +++ b/cookbook/workflow/new_project_svn.rst @@ -78,13 +78,13 @@ To get started, you'll need to download Symfony2 and get the basic Subversion se $ svn propset svn:ignore "vendor" . $ svn propset svn:ignore "bootstrap*" app/ - $ svn propset svn:ignore "parameters.ini" app/config/ + $ svn propset svn:ignore "parameters.yml" app/config/ $ svn propset svn:ignore "*" app/cache/ $ svn propset svn:ignore "*" app/logs/ $ svn propset svn:ignore "bundles" web - $ svn ci -m "commit basic symfony ignore list (vendor, app/bootstrap*, app/config/parameters.ini, app/cache/*, app/logs/*, web/bundles)" + $ svn ci -m "commit basic symfony ignore list (vendor, app/bootstrap*, app/config/parameters.yml, app/cache/*, app/logs/*, web/bundles)" 6. The rest of the files can now be added and committed to the project: @@ -93,24 +93,20 @@ To get started, you'll need to download Symfony2 and get the basic Subversion se $ svn add --force . $ svn ci -m "add basic Symfony Standard 2.X.Y" -7. Copy ``app/config/parameters.ini`` to ``app/config/parameters.ini.dist``. - The ``parameters.ini`` file is ignored by svn (see above) so that +7. Copy ``app/config/parameters.yml`` to ``app/config/parameters.yml.dist``. + The ``parameters.yml`` file is ignored by svn (see above) so that machine-specific settings like database passwords aren't committed. By - creating the ``parameters.ini.dist`` file, new developers can quickly clone - the project, copy this file to ``parameters.ini``, customize it, and start + creating the ``parameters.yml.dist`` file, new developers can quickly clone + the project, copy this file to ``parameters.yml``, customize it, and start developing. -8. Finally, download all of the third-party vendor libraries: - - .. code-block:: bash - - $ php bin/vendors install +8. Finally, download all of the third-party vendor libraries by + executing composer. For details, see :ref:`installation-updating-vendors`. .. tip:: - `git`_ has to be installed to run ``bin/vendors``, this is the protocol - used to fetch vendor libraries. This only means that ``git`` is used as - a tool to basically help download the libraries in the ``vendor/`` directory. + If you rely on any "dev" versions, then git may be used to install + those libraries, since there is no archive available for download. At this point, you have a fully-functional Symfony2 project stored in your Subversion repository. The development can start with commits in the Subversion diff --git a/images/components/console/progress.png b/images/components/console/progress.png new file mode 100644 index 00000000000..c126bff5252 Binary files /dev/null and b/images/components/console/progress.png differ diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index 21db8c2520c..e6b74a973f4 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -65,19 +65,19 @@ PHP autoloading can be configured via ``app/autoload.php``:: $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( - 'Symfony' => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'), + 'Symfony' => array(__DIR__.'/../vendor/symfony/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', + 'JMS' => __DIR__.'/../vendor/jms/', + 'Doctrine\\Common' => __DIR__.'/../vendor/doctrine/common/lib', + 'Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine/dbal/lib', + 'Doctrine' => __DIR__.'/../vendor/doctrine/orm/lib', + 'Monolog' => __DIR__.'/../vendor/monolog/monolog/src', + 'Assetic' => __DIR__.'/../vendor/kriswallsmith/assetic/src', + 'Metadata' => __DIR__.'/../vendor/jms/metadata/src', )); $loader->registerPrefixes(array( - 'Twig_Extensions_' => __DIR__.'/../vendor/twig-extensions/lib', - 'Twig_' => __DIR__.'/../vendor/twig/lib', + 'Twig_Extensions_' => __DIR__.'/../vendor/twig/extensions/lib', + 'Twig_' => __DIR__.'/../vendor/twig/twig/lib', )); // ... @@ -162,19 +162,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 +187,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 +201,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%" diff --git a/quick_tour/the_big_picture.rst b/quick_tour/the_big_picture.rst index 996d14a4716..fdd131ad432 100644 --- a/quick_tour/the_big_picture.rst +++ b/quick_tour/the_big_picture.rst @@ -17,25 +17,8 @@ Downloading Symfony2 -------------------- First, check that you have installed and configured a Web server (such as -Apache) with PHP 5.3.2 or higher. - -.. tip:: - - If you have PHP 5.4, you could use the built-in web server. The built-in - server should be used only for development purpose, but it can help you - to start your project quickly and easily. - - Just use this command to launch the server: - - .. code-block:: bash - - $ php -S localhost:80 -t /path/to/www - - where "/path/to/www" is the path to some directory on your machine that - you'll extract Symfony into so that the eventual URL to your application - is "http://localhost/Symfony/app_dev.php". You can also extract Symfony - first and then start the web server in the Symfony "web" directory. If - you do this, the URL to your application will be "http://localhost/app_dev.php". +Apache) with the most recent PHP version possible (PHP 5.3.8 or newer is +recommended). Ready? Start by downloading the "`Symfony2 Standard Edition`_", a Symfony :term:`distribution` that is preconfigured for the most common use cases and @@ -71,12 +54,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 + + $ composer.phar create-project symfony/framework-standard-edition path/to/install dev-master + + # remove the Git history + $ rm -rf .git + + For an exact version, replace `dev-master` 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 - $ php bin/vendors install + # 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 +93,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,7 +113,7 @@ 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! @@ -124,7 +141,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 @@ -381,14 +398,14 @@ to production. That's why you will find another front controller in the .. 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 +464,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 \ No newline at end of file diff --git a/quick_tour/the_controller.rst b/quick_tour/the_controller.rst old mode 100644 new mode 100755 index d3c13da4955..563f627b07f --- 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->set('filters', array()); You can also store small messages that will only be available for the very next request:: // store a message for the very next request (in a controller) - $session->setFlash('notice', 'Congratulations, your action succeeded!'); + $session->getFlashBag()->add('notice', 'Congratulations, your action succeeded!'); - // display the message back in the next request (in a template) - {{ app.session.flash('notice') }} + // display any messages back in the next request (in a template) + + {% for flashMessage in app.session.flashbag.get('notice') %} +
        {{ flashMessage }}
        + {% endfor %} This is useful when you need to set a success message before redirecting -the user to another page (which will then show the message). +the user to another page (which will then show the message). Please note that +when you use has() instead of get(), the flash message will not be cleared and +thus remains available during the following requests. Securing Resources ------------------ @@ -164,9 +169,10 @@ fits most common needs: providers: in_memory: - users: - user: { password: userpass, roles: [ 'ROLE_USER' ] } - admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } + memory: + users: + user: { password: userpass, roles: [ 'ROLE_USER' ] } + admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } firewalls: dev: @@ -197,7 +203,7 @@ Moreover, the ``admin`` user has a ``ROLE_ADMIN`` role, which includes the configuration, but you can use any hashing algorithm by tweaking the ``encoders`` section. -Going to the ``http://localhost/Symfony/web/app_dev.php/demo/secured/hello`` +Going to the ``http://localhost/app_dev.php/demo/secured/hello`` URL will automatically redirect you to the login form because this resource is protected by a ``firewall``. diff --git a/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 c24a110b931..443627df117 100644 --- a/reference/configuration/doctrine.rst +++ b/reference/configuration/doctrine.rst @@ -12,62 +12,161 @@ 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 ommited) + 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 ommited) + 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 @@ 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 diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index bba7379cf3c..ce6216bb916 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 4c340da37cf..99eb3e2c348 100644 --- a/reference/configuration/monolog.rst +++ b/reference/configuration/monolog.rst @@ -18,6 +18,8 @@ Configuration Reference level: ERROR bubble: false formatter: my_formatter + processors: + - some_callable main: type: fingers_crossed action_level: WARNING @@ -27,8 +29,8 @@ 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 @@ 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 61d138064a9..e98da61f58f 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: check_path: /login_check login_path: /login @@ -126,20 +143,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: @@ -165,7 +213,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..3598b812458 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 @@ -37,13 +40,16 @@ Validation Constraints Reference constraints/File constraints/Image + constraints/Luhn + constraints/Callback - constraints/Valid constraints/All + constraints/UserPassword + constraints/Valid The Validator is designed to validate objects against *constraints*. In real life, a constraint could be: "The cake must not be burned". In -Symfony2, constraints are similar: They are assertions that a condition is +Symfony2, constraints are similar: They are assertions that a condition is true. Supported Constraints diff --git a/reference/constraints/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/Count.rst b/reference/constraints/Count.rst new file mode 100644 index 00000000000..169fe523bc0 --- /dev/null +++ b/reference/constraints/Count.rst @@ -0,0 +1,101 @@ +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 5 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 5 emails" + * ) + */ + protected $emails = array(); + } + +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..7a722f172b9 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..785833d5c65 --- /dev/null +++ b/reference/constraints/Length.rst @@ -0,0 +1,111 @@ +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 2 characters length + maxMessage: Your first name cannot be longer than than 50 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 2 characters length", + * maxMessage = "Your first name cannot be longer than than 50 characters length" + * ) + */ + protected $firstName; + } + +Options +------- + +min +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "min" length value. Validation will fail if the given +value's length is **less** than this min value. + +max +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "max" length value. Validation will fail if the given +value's length is **greater** than this max value. + +charset +~~~~~~~ + +**type**: ``string`` **default**: ``UTF-8`` + +The charset to be used when computing value's length. The :phpfunction:`grapheme_strlen` PHP +function is used if available. If not, the the :phpfunction:`mb_strlen` PHP function +is used if available. If neither are available, the :phpfunction:`strlen` PHP function +is used. + +minMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value is too short. It should have {{ limit }} characters or more.``. + +The message that will be shown if the underlying value's length is less than the `min`_ option. + +maxMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value is too long. It should have {{ limit }} characters or less.``. + +The message that will be shown if the underlying value's length is more than the `max`_ option. + +exactMessage +~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should have exactly {{ limit }} characters.``. + +The message that will be shown if min and max values are equal and the underlying +value's length is not exactly this value. diff --git a/reference/constraints/Luhn.rst b/reference/constraints/Luhn.rst new file mode 100644 index 00000000000..f854fb8f7cb --- /dev/null +++ b/reference/constraints/Luhn.rst @@ -0,0 +1,87 @@ +Luhn +====== + +This constraint is used to ensure that a credit card number passes the `Luhn algorithm`_. +It is useful as a first step to validating a credit card: before communicating with a +payment gateway. + ++----------------+-----------------------------------------------------------------------+ +| Applies to | :ref:`property or method` | ++----------------+-----------------------------------------------------------------------+ +| Options | - `message`_ | ++----------------+-----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Luhn` | ++----------------+-----------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\LuhnValidator` | ++----------------+-----------------------------------------------------------------------+ + +Basic Usage +----------- + +To use the Luhn validator, simply apply it to a property on an object that +will contain a credit card number. + +.. configuration-block:: + + .. code-block:: yaml + + # src/Acme/SubscriptionBundle/Resources/config/validation.yml + Acme\SubscriptionBundle\Entity\Transaction: + properties: + cardNumber: + - Luhn: + message: Please check your credit card number. + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php-annotations + + // src/Acme/SubscriptionBundle/Entity/Transaction.php + use Symfony\Component\Validator\Constraints as Assert; + + class Transaction + { + /** + * @Assert\Luhn(message = "Please check your credit card number.") + */ + protected $cardNumber; + } + + .. code-block:: php + + // src/Acme/SubscriptionBundle/Entity/Transaction.php + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints\Luhn; + + class Transaction + { + protected $cardNumber; + + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('luhn', new Luhn(array( + 'message' => 'Please check your credit card number', + ))); + } + } + +Available Options +----------------- + +message +~~~~~~~ + +**type**: ``string`` **default**: ``Invalid card number`` + +The default message supplied when the value does not pass the Luhn check. + +.. _`Luhn algorithm`: http://en.wikipedia.org/wiki/Luhn_algorithm \ No newline at end of file diff --git a/reference/constraints/Range.rst b/reference/constraints/Range.rst new file mode 100644 index 00000000000..6391a5d6060 --- /dev/null +++ b/reference/constraints/Range.rst @@ -0,0 +1,104 @@ +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; + } + +Options +------- + +min +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "min" value. Validation will fail if the given +value is **less** than this min value. + +max +~~~ + +**type**: ``integer`` [:ref:`default option`] + +This required option is the "max" value. Validation will fail if the given +value is **greater** than this max value. + +minMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should be {{ limit }} or more.`` + +The message that will be shown if the underlying value is less than the `min`_ +option. + +maxMessage +~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should be {{ limit }} or less.`` + +The message that will be shown if the underlying value is more than the `max`_ +option. + +invalidMessage +~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should be a valid number.`` + +The message that will be shown if the underlying value is not a number (per +the `is_numeric`_ PHP function). + +.. _`is_numeric`: http://www.php.net/manual/en/function.is-numeric.php diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index 1c2ab1ea989..81bd5364633 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..af972f61372 --- /dev/null +++ b/reference/constraints/UserPassword.rst @@ -0,0 +1,74 @@ +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. + +When applied to an array (or Traversable object), this constraint allows +you to apply a collection of constraints to each element of the array. + ++----------------+-------------------------------------------------------------------------------------------+ +| 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/map.rst.inc b/reference/constraints/map.rst.inc index 08d26f02c63..3bcc9ad3a3a 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 ` @@ -51,9 +54,15 @@ File Constraints * :doc:`File ` * :doc:`Image ` +Financial Constraints +~~~~~~~~~~~~~~~~~~~~~ + +* :doc:`Luhn ` + Other Constraints ~~~~~~~~~~~~~~~~~ * :doc:`Callback ` * :doc:`All ` -* :doc:`Valid ` \ No newline at end of file +* :doc:`UserPassword ` +* :doc:`Valid ` diff --git a/reference/dic_tags.rst b/reference/dic_tags.rst index 1795711b601..48e7e3297de 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 diff --git a/reference/forms/twig_reference.rst b/reference/forms/twig_reference.rst index 63eaf85d96e..4661eb055b4 100644 --- a/reference/forms/twig_reference.rst +++ b/reference/forms/twig_reference.rst @@ -20,8 +20,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'}}) }} form_errors(form.name) ---------------------- @@ -44,7 +44,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 @@ -60,7 +60,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 diff --git a/reference/forms/types/collection.rst b/reference/forms/types/collection.rst index cc661477193..569a0703e28 100644 --- a/reference/forms/types/collection.rst +++ b/reference/forms/types/collection.rst @@ -127,9 +127,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 +170,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 our 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 our list @@ -283,8 +283,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 +299,7 @@ collection field: .. code-block:: php - row($form['emails']->get('prototype')) ?> + row($form['emails']->getVar('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. 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..3b5df983409 100644 --- a/reference/forms/types/form.rst +++ b/reference/forms/types/form.rst @@ -4,4 +4,23 @@ 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/cascade_validation.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 + +.. include:: /reference/forms/types/options/translation_domain.rst.inc \ No newline at end of file diff --git a/reference/forms/types/options/by_reference.rst.inc b/reference/forms/types/options/by_reference.rst.inc index 6c0f55f5648..062bd668846 100644 --- a/reference/forms/types/options/by_reference.rst.inc +++ b/reference/forms/types/options/by_reference.rst.inc @@ -20,7 +20,7 @@ To understand this further, let's look at 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/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 3d6798f3796..cca93635a1a 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