Skip to content

Commit 68e8c04

Browse files
committed
Merge branch '2.3' into 2.4
2 parents 07822b8 + a1050eb commit 68e8c04

15 files changed

+284
-96
lines changed

book/routing.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ The route is simple:
6969
return $collection;
7070
7171
.. versionadded:: 2.2
72-
The ``path`` option is new in Symfony2.2, ``pattern`` is used in older
72+
The ``path`` option is new in Symfony 2.2, ``pattern`` is used in older
7373
versions.
7474

7575
The path defined by the ``blog_show`` route acts like ``/blog/*`` where
@@ -705,7 +705,7 @@ be accomplished with the following route configuration:
705705
return $collection;
706706
707707
.. versionadded:: 2.2
708-
The ``methods`` option is added in Symfony2.2. Use the ``_method``
708+
The ``methods`` option is added in Symfony 2.2. Use the ``_method``
709709
requirement in older versions.
710710

711711
Despite the fact that these two routes have identical paths (``/contact``),

book/testing.rst

+51-28
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ it has its own excellent `documentation`_.
2121
needed to test the Symfony core code itself.
2222

2323
Each test - whether it's a unit test or a functional test - is a PHP class
24-
that should live in the `Tests/` subdirectory of your bundles. If you follow
24+
that should live in the ``Tests/`` subdirectory of your bundles. If you follow
2525
this rule, then you can run all of your application's tests with the following
2626
command:
2727

@@ -783,47 +783,70 @@ PHPUnit Configuration
783783
~~~~~~~~~~~~~~~~~~~~~
784784

785785
Each application has its own PHPUnit configuration, stored in the
786-
``phpunit.xml.dist`` file. You can edit this file to change the defaults or
787-
create a ``phpunit.xml`` file to tweak the configuration for your local machine.
786+
``app/phpunit.xml.dist`` file. You can edit this file to change the defaults or
787+
create an ``app/phpunit.xml`` file to setup a configuration for your local
788+
machine only.
788789

789790
.. tip::
790791

791-
Store the ``phpunit.xml.dist`` file in your code repository, and ignore the
792+
Store the ``phpunit.xml.dist`` file in your code repository and ignore the
792793
``phpunit.xml`` file.
793794

794-
By default, only the tests stored in "standard" bundles are run by the
795-
``phpunit`` command (standard being tests in the ``src/*/Bundle/Tests`` or
796-
``src/*/Bundle/*Bundle/Tests`` directories) But you can easily add more
797-
directories. For instance, the following configuration adds the tests from
798-
the installed third-party bundles:
795+
By default, only the tests from your own custom bundles stored in the standard
796+
directories ``src/*/*Bundle/Tests`` or ``src/*/Bundle/*Bundle/Tests`` are run
797+
by the ``phpunit`` command, as configured in the ``phpunit.xml.dist`` file:
799798

800799
.. code-block:: xml
801800
802-
<!-- hello/phpunit.xml.dist -->
803-
<testsuites>
804-
<testsuite name="Project Test Suite">
805-
<directory>../src/*/*Bundle/Tests</directory>
806-
<directory>../src/Acme/Bundle/*Bundle/Tests</directory>
807-
</testsuite>
808-
</testsuites>
801+
<!-- app/phpunit.xml.dist -->
802+
<phpunit>
803+
<!-- ... -->
804+
<testsuites>
805+
<testsuite name="Project Test Suite">
806+
<directory>../src/*/*Bundle/Tests</directory>
807+
<directory>../src/*/Bundle/*Bundle/Tests</directory>
808+
</testsuite>
809+
</testsuites>
810+
<!-- ... -->
811+
</phpunit>
812+
813+
But you can easily add more directories. For instance, the following
814+
configuration adds tests from a custom ``lib/tests`` directory:
815+
816+
.. code-block:: xml
817+
818+
<!-- app/phpunit.xml.dist -->
819+
<phpunit>
820+
<!-- ... -->
821+
<testsuites>
822+
<testsuite name="Project Test Suite">
823+
<!-- ... --->
824+
<directory>../lib/tests</directory>
825+
</testsuite>
826+
</testsuites>
827+
<!-- ... --->
828+
</phpunit>
809829
810830
To include other directories in the code coverage, also edit the ``<filter>``
811831
section:
812832

813833
.. code-block:: xml
814834
815-
<!-- ... -->
816-
<filter>
817-
<whitelist>
818-
<directory>../src</directory>
819-
<exclude>
820-
<directory>../src/*/*Bundle/Resources</directory>
821-
<directory>../src/*/*Bundle/Tests</directory>
822-
<directory>../src/Acme/Bundle/*Bundle/Resources</directory>
823-
<directory>../src/Acme/Bundle/*Bundle/Tests</directory>
824-
</exclude>
825-
</whitelist>
826-
</filter>
835+
<!-- app/phpunit.xml.dist -->
836+
<phpunit>
837+
<!-- ... -->
838+
<filter>
839+
<whitelist>
840+
<!-- ... -->
841+
<directory>../lib</directory>
842+
<exclude>
843+
<!-- ... -->
844+
<directory>../lib/tests</directory>
845+
</exclude>
846+
</whitelist>
847+
</filter>
848+
<!-- ... --->
849+
</phpunit>
827850
828851
Learn more
829852
----------

components/form/type_guesser.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ that the type guesser cannot guess the type.
7474

7575
The ``TypeGuess`` constructor requires 3 options:
7676

77-
* The type name (one of the :doc:`form types </reference/forms/types`);
77+
* The type name (one of the :doc:`form types </reference/forms/types>`);
7878
* Additional options (for instance, when the type is ``entity``, you also
7979
want to set the ``class`` option). If no types are guessed, this should be
8080
set to an empty array;

components/routing/introduction.rst

+14-14
Original file line numberDiff line numberDiff line change
@@ -141,28 +141,26 @@ Using Prefixes
141141

142142
You can add routes or other instances of
143143
:class:`Symfony\\Component\\Routing\\RouteCollection` to *another* collection.
144-
This way you can build a tree of routes. Additionally you can define a prefix,
145-
default requirements, default options and host to all routes of a subtree with
146-
the :method:`Symfony\\Component\\Routing\\RouteCollection::addPrefix` method::
144+
This way you can build a tree of routes. Additionally you can define a prefix
145+
and default values for the parameters, requirements, options, schemes and the
146+
host to all routes of a subtree using methods provided by the
147+
``RouteCollection`` class::
147148

148149
$rootCollection = new RouteCollection();
149150

150151
$subCollection = new RouteCollection();
151152
$subCollection->add(...);
152153
$subCollection->add(...);
153-
$subCollection->addPrefix(
154-
'/prefix', // prefix
155-
array(), // requirements
156-
array(), // options
157-
'admin.example.com', // host
158-
array('https') // schemes
159-
);
154+
$subCollection->addPrefix('/prefix');
155+
$subCollection->addDefaults(array(...));
156+
$subCollection->addRequirements(array(...));
157+
$subCollection->addOptions(array(...));
158+
$subCollection->setHost('admin.example.com');
159+
$subCollection->setMethods(array('POST'));
160+
$subCollection->setSchemes(array('https'));
160161

161162
$rootCollection->addCollection($subCollection);
162163

163-
.. versionadded:: 2.2
164-
The ``addPrefix`` method is added in Symfony2.2. This was part of the
165-
``addCollection`` method in older versions.
166164

167165
Set the Request Parameters
168166
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -177,7 +175,9 @@ with this class via its constructor::
177175
$host = 'localhost',
178176
$scheme = 'http',
179177
$httpPort = 80,
180-
$httpsPort = 443
178+
$httpsPort = 443,
179+
$path = '/',
180+
$queryString = ''
181181
)
182182

183183
.. _components-routing-http-foundation:

components/security/authentication.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,8 @@ own, it just needs to follow these rules:
204204
:method:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface::isPasswordValid`
205205
must first of all make sure the password is not too long, i.e. the password length is no longer
206206
than 4096 characters. This is for security reasons (see `CVE-2013-5750`_), and you can use the
207-
:method:`Symfony\\Component\\Security\\Core\\Encoder\\BasePasswordEncoder::isPasswordTooLong`_
208-
method for this check:
207+
:method:`Symfony\\Component\\Security\\Core\\Encoder\\BasePasswordEncoder::isPasswordTooLong`
208+
method for this check::
209209

210210
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
211211

cookbook/email/cloud.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ How to use the Cloud to Send Emails
77
Requirements for sending emails from a production system differ from your
88
development setup as you don't want to be limited in the number of emails,
99
the sending rate or the sender address. Thus,
10-
:doc:`using Gmail </cookbook/email/gmail>`_ or similar services is not an
10+
:doc:`using Gmail </cookbook/email/gmail>` or similar services is not an
1111
option. If setting up and maintaining your own reliable mail server causes
1212
you a headache there's a simple solution: Leverage the cloud to send your
1313
emails.

cookbook/form/dynamic_form_modification.rst

+95-16
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ sport like this::
476476
// src/Acme/DemoBundle/Form/Type/SportMeetupType.php
477477
namespace Acme\DemoBundle\Form\Type;
478478

479+
use Symfony\Component\Form\AbstractType;
479480
use Symfony\Component\Form\FormBuilderInterface;
480481
use Symfony\Component\Form\FormEvent;
481482
use Symfony\Component\Form\FormEvents;
@@ -486,7 +487,10 @@ sport like this::
486487
public function buildForm(FormBuilderInterface $builder, array $options)
487488
{
488489
$builder
489-
->add('sport', 'entity', array(...))
490+
->add('sport', 'entity', array(
491+
'class' => 'AcmeDemoBundle:Sport',
492+
'empty_value' => '',
493+
))
490494
;
491495

492496
$builder->addEventListener(
@@ -497,12 +501,19 @@ sport like this::
497501
// this would be your entity, i.e. SportMeetup
498502
$data = $event->getData();
499503

500-
$positions = $data->getSport()->getAvailablePositions();
504+
$sport = $data->getSport();
505+
$positions = null === $sport ? array() : $sport->getAvailablePositions();
501506

502-
$form->add('position', 'entity', array('choices' => $positions));
507+
$form->add('position', 'entity', array(
508+
'class' => 'AcmeDemoBundle:Position',
509+
'empty_value' => '',
510+
'choices' => $positions,
511+
));
503512
}
504513
);
505514
}
515+
516+
// ...
506517
}
507518

508519
When you're building this form to display to the user for the first time,
@@ -539,21 +550,28 @@ The type would now look like::
539550
namespace Acme\DemoBundle\Form\Type;
540551

541552
// ...
542-
use Acme\DemoBundle\Entity\Sport;
543553
use Symfony\Component\Form\FormInterface;
554+
use Acme\DemoBundle\Entity\Sport;
544555

545556
class SportMeetupType extends AbstractType
546557
{
547558
public function buildForm(FormBuilderInterface $builder, array $options)
548559
{
549560
$builder
550-
->add('sport', 'entity', array(...))
561+
->add('sport', 'entity', array(
562+
'class' => 'AcmeDemoBundle:Sport',
563+
'empty_value' => '',
564+
));
551565
;
552566

553-
$formModifier = function(FormInterface $form, Sport $sport) {
554-
$positions = $sport->getAvailablePositions();
567+
$formModifier = function(FormInterface $form, Sport $sport = null) {
568+
$positions = null === $sport ? array() : $sport->getAvailablePositions();
555569

556-
$form->add('position', 'entity', array('choices' => $positions));
570+
$form->add('position', 'entity', array(
571+
'class' => 'AcmeDemoBundle:Position',
572+
'empty_value' => '',
573+
'choices' => $positions,
574+
));
557575
};
558576

559577
$builder->addEventListener(
@@ -579,17 +597,78 @@ The type would now look like::
579597
}
580598
);
581599
}
600+
601+
// ...
602+
}
603+
604+
You can see that you need to listen on these two events and have different
605+
callbacks only because in two different scenarios, the data that you can use is
606+
available in different events. Other than that, the listeners always perform
607+
exactly the same things on a given form.
608+
609+
One piece that is still missing is the client-side updating of your form after
610+
the sport is selected. This should be handled by making an AJAX call back to
611+
your application. Assume that you have a sport meetup creation controller::
612+
613+
// src/Acme/DemoBundle/Controller/MeetupController.php
614+
namespace Acme\DemoBundle\Controller;
615+
616+
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
617+
use Symfony\Component\HttpFoundation\Request;
618+
use Acme\DemoBundle\Entity\SportMeetup;
619+
use Acme\DemoBundle\Form\Type\SportMeetupType;
620+
// ...
621+
622+
class MeetupController extends Controller
623+
{
624+
public function createAction(Request $request)
625+
{
626+
$meetup = new SportMeetup();
627+
$form = $this->createForm(new SportMeetupType(), $meetup);
628+
$form->handleRequest($request);
629+
if ($form->isValid()) {
630+
// ... save the meetup, redirect etc.
631+
}
632+
633+
return $this->render(
634+
'AcmeDemoBundle:Meetup:create.html.twig',
635+
array('form' => $form->createView())
636+
);
637+
}
638+
639+
// ...
582640
}
583641

584-
You can see that you need to listen on these two events and have different callbacks
585-
only because in two different scenarios, the data that you can use is available in different events.
586-
Other than that, the listeners always perform exactly the same things on a given form.
642+
The associated template uses some JavaScript to update the ``position`` form
643+
field according to the current selection in the ``sport`` field:
644+
645+
.. configuration-block::
646+
647+
.. code-block:: html+jinja
648+
649+
{# src/Acme/DemoBundle/Resources/views/Meetup/create.html.twig #}
650+
{{ form_start(form) }}
651+
{{ form_row(form.sport) }} {# <select id="meetup_sport" ... #}
652+
{{ form_row(form.position) }} {# <select id="meetup_position" ... #}
653+
{# ... #}
654+
{{ form_end(form) }}
655+
656+
.. include:: /cookbook/form/dynamic_form_modification_ajax_js.rst.inc
657+
658+
.. code-block:: html+php
659+
660+
<!-- src/Acme/DemoBundle/Resources/views/Meetup/create.html.php -->
661+
<?php echo $view['form']->start($form) ?>
662+
<?php echo $view['form']->row($form['sport']) ?> <!-- <select id="meetup_sport" ... -->
663+
<?php echo $view['form']->row($form['position']) ?> <!-- <select id="meetup_position" ... -->
664+
<!-- ... -->
665+
<?php echo $view['form']->end($form) ?>
666+
667+
.. include:: /cookbook/form/dynamic_form_modification_ajax_js.rst.inc
587668

588-
One piece that may still be missing is the client-side updating of your form
589-
after the sport is selected. This should be handled by making an AJAX call
590-
back to your application. In that controller, you can submit your form, but
591-
instead of processing it, simply use the submitted form to render the updated
592-
fields. The response from the AJAX call can then be used to update the view.
669+
The major benefit of submitting the whole form to just extract the updated
670+
``position`` field is that no additional server-side code is needed; all the
671+
code from above to generate the submitted form can be reused.
593672

594673
.. _cookbook-dynamic-form-modification-suppressing-form-validation:
595674

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
var $sport = $('#meetup_sport');
3+
// When sport gets selected ...
4+
$sport.change(function(){
5+
// ... retrieve the corresponding form.
6+
var $form = $(this).closest('form');
7+
// Simulate form data, but only include the selected sport value.
8+
var data = {};
9+
data[$sport.attr('name')] = $sport.val();
10+
// Submit data via AJAX to the form's action path.
11+
$.ajax({
12+
url : $form.attr('action'),
13+
type: $form.attr('method'),
14+
data : data,
15+
success: function(html) {
16+
// Replace current position field ...
17+
$('#meetup_position').replaceWith(
18+
// ... with the returned one from the AJAX response.
19+
$(html).find('#meetup_position')
20+
);
21+
// Position field now displays the appropriate positions.
22+
}
23+
});
24+
});
25+
</script>

0 commit comments

Comments
 (0)