Skip to content

Commit 8673b59

Browse files
committed
Merge branch '2.4'
Conflicts: book/routing.rst components/routing/introduction.rst
2 parents dcf8e6e + 68e8c04 commit 8673b59

15 files changed

+287
-94
lines changed

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-11
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,23 @@ Using Prefixes
138138

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

145146
$rootCollection = new RouteCollection();
146147

147148
$subCollection = new RouteCollection();
148149
$subCollection->add(...);
149150
$subCollection->add(...);
150-
$subCollection->addPrefix(
151-
'/prefix', // prefix
152-
array(), // requirements
153-
array(), // options
154-
'admin.example.com', // host
155-
array('https') // schemes
156-
);
151+
$subCollection->addPrefix('/prefix');
152+
$subCollection->addDefaults(array(...));
153+
$subCollection->addRequirements(array(...));
154+
$subCollection->addOptions(array(...));
155+
$subCollection->setHost('admin.example.com');
156+
$subCollection->setMethods(array('POST'));
157+
$subCollection->setSchemes(array('https'));
157158

158159
$rootCollection->addCollection($subCollection);
159160

@@ -170,7 +171,9 @@ with this class via its constructor::
170171
$host = 'localhost',
171172
$scheme = 'http',
172173
$httpPort = 80,
173-
$httpsPort = 443
174+
$httpsPort = 443,
175+
$path = '/',
176+
$queryString = ''
174177
)
175178

176179
.. _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
@@ -471,6 +471,7 @@ sport like this::
471471
// src/Acme/DemoBundle/Form/Type/SportMeetupType.php
472472
namespace Acme\DemoBundle\Form\Type;
473473

474+
use Symfony\Component\Form\AbstractType;
474475
use Symfony\Component\Form\FormBuilderInterface;
475476
use Symfony\Component\Form\FormEvent;
476477
use Symfony\Component\Form\FormEvents;
@@ -481,7 +482,10 @@ sport like this::
481482
public function buildForm(FormBuilderInterface $builder, array $options)
482483
{
483484
$builder
484-
->add('sport', 'entity', array(...))
485+
->add('sport', 'entity', array(
486+
'class' => 'AcmeDemoBundle:Sport',
487+
'empty_value' => '',
488+
))
485489
;
486490

487491
$builder->addEventListener(
@@ -492,12 +496,19 @@ sport like this::
492496
// this would be your entity, i.e. SportMeetup
493497
$data = $event->getData();
494498

495-
$positions = $data->getSport()->getAvailablePositions();
499+
$sport = $data->getSport();
500+
$positions = null === $sport ? array() : $sport->getAvailablePositions();
496501

497-
$form->add('position', 'entity', array('choices' => $positions));
502+
$form->add('position', 'entity', array(
503+
'class' => 'AcmeDemoBundle:Position',
504+
'empty_value' => '',
505+
'choices' => $positions,
506+
));
498507
}
499508
);
500509
}
510+
511+
// ...
501512
}
502513

503514
When you're building this form to display to the user for the first time,
@@ -530,21 +541,28 @@ The type would now look like::
530541
namespace Acme\DemoBundle\Form\Type;
531542

532543
// ...
533-
use Acme\DemoBundle\Entity\Sport;
534544
use Symfony\Component\Form\FormInterface;
545+
use Acme\DemoBundle\Entity\Sport;
535546

536547
class SportMeetupType extends AbstractType
537548
{
538549
public function buildForm(FormBuilderInterface $builder, array $options)
539550
{
540551
$builder
541-
->add('sport', 'entity', array(...))
552+
->add('sport', 'entity', array(
553+
'class' => 'AcmeDemoBundle:Sport',
554+
'empty_value' => '',
555+
));
542556
;
543557

544-
$formModifier = function(FormInterface $form, Sport $sport) {
545-
$positions = $sport->getAvailablePositions();
558+
$formModifier = function(FormInterface $form, Sport $sport = null) {
559+
$positions = null === $sport ? array() : $sport->getAvailablePositions();
546560

547-
$form->add('position', 'entity', array('choices' => $positions));
561+
$form->add('position', 'entity', array(
562+
'class' => 'AcmeDemoBundle:Position',
563+
'empty_value' => '',
564+
'choices' => $positions,
565+
));
548566
};
549567

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

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

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

585664
.. _cookbook-dynamic-form-modification-suppressing-form-validation:
586665

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)