Skip to content

Commit 3fb163c

Browse files
committed
Merge branch '2.8' into 3.4
* 2.8: Update sessions.rst Removed some repeated content and minor rewords doc(testing/http_authentication.rst); Update locale_sticky_session.rst Undefined variable $userName in example Fixed the code of the custom password authenticator example Documented PHPUnit data providers
2 parents 3c80d1a + 67b783c commit 3fb163c

File tree

8 files changed

+161
-116
lines changed

8 files changed

+161
-116
lines changed

best_practices/tests.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ functional tests, you can quickly spot any big errors before you deploy them:
2626
Define a functional test that at least checks if your application pages
2727
are successfully loading.
2828

29-
A functional test can be as easy as this::
29+
A functional test like this is simple to implement thanks to
30+
:ref:`PHPUnit data providers <testing-data-providers>`::
3031

3132
// tests/AppBundle/ApplicationAvailabilityFunctionalTest.php
3233
namespace Tests\AppBundle;

components/http_foundation/sessions.rst

+57-50
Original file line numberDiff line numberDiff line change
@@ -94,29 +94,12 @@ Session Workflow
9494
Session Attributes
9595
..................
9696

97-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::set`
98-
Sets an attribute by key.
97+
The session attributes are stored internally in a "Bag", a PHP object that acts
98+
like an array. They can be set, removed, checked, etc. using the methods
99+
explained later in this article for the ``AttributeBagInterface`` class. See
100+
:ref:`attribute-bag-interface`.
99101

100-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::get`
101-
Gets an attribute by key.
102-
103-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::all`
104-
Gets all attributes as an array of key => value.
105-
106-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::has`
107-
Returns true if the attribute exists.
108-
109-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::replace`
110-
Sets multiple attributes at once: takes a keyed array and sets each key => value pair.
111-
112-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::remove`
113-
Deletes an attribute by key.
114-
115-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::clear`
116-
Clear all attributes.
117-
118-
The attributes are stored internally in a "Bag", a PHP object that acts like
119-
an array. A few methods exist for "Bag" management:
102+
In addition, a few methods exist for "Bag" management:
120103

121104
:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::registerBag`
122105
Registers a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface`.
@@ -168,19 +151,65 @@ the following API which is intended mainly for internal purposes:
168151
:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getName`
169152
Returns the name of the session bag.
170153

154+
.. _attribute-bag-interface:
155+
171156
Attributes
172157
~~~~~~~~~~
173158

174159
The purpose of the bags implementing the :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface`
175160
is to handle session attribute storage. This might include things like user ID,
176-
and remember me login settings or other user based state information.
161+
and "Remember Me" login settings or other user based state information.
177162

178163
:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag`
179164
This is the standard default implementation.
180165

181166
:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag`
182167
This implementation allows for attributes to be stored in a structured namespace.
183168

169+
:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface`
170+
has a simple API
171+
172+
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set`
173+
Sets an attribute by name (``set('name', 'value')``).
174+
175+
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::get`
176+
Gets an attribute by name (``get('name')``) and can define a default
177+
value when the attribute doesn't exist (``get('name', 'default_value')``).
178+
179+
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::all`
180+
Gets all attributes as an associative array of ``name => value``.
181+
182+
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::has`
183+
Returns ``true`` if the attribute exists.
184+
185+
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::replace`
186+
Sets multiple attributes at once using an associative array (``name => value``).
187+
If the attributes existed, they are replaced; if not, they are created.
188+
189+
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove`
190+
Deletes an attribute by name and returns its value.
191+
192+
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::clear`
193+
Deletes all attributes.
194+
195+
Example::
196+
197+
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
198+
use Symfony\Component\HttpFoundation\Session\Session;
199+
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
200+
201+
$session = new Session(new NativeSessionStorage(), new AttributeBag());
202+
$session->set('token', 'a6c1e0b6');
203+
// ...
204+
$token = $session->get('token');
205+
// if the attribute may or may not exist, you can define a default value for it
206+
$token = $session->get('attribute-name', 'default-attribute-value');
207+
// ...
208+
$session->clear();
209+
210+
Namespaced Attributes
211+
.....................
212+
184213
Any plain key-value storage system is limited in the extent to which
185214
complex data can be stored since each key must be unique. You can achieve
186215
namespacing by introducing a naming convention to the keys so different parts of
@@ -205,35 +234,13 @@ the array::
205234
$session->set('tokens', $tokens);
206235

207236
With structured namespacing, the key can be translated to the array
208-
structure like this using a namespace character (defaults to ``/``)::
209-
210-
$session->set('tokens/c', $value);
211-
212-
This way you can easily access a key within the stored array directly and easily.
213-
214-
:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface`
215-
has a simple API
216-
217-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set`
218-
Sets an attribute by key.
219-
220-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::get`
221-
Gets an attribute by key.
222-
223-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::all`
224-
Gets all attributes as an array of key => value.
225-
226-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::has`
227-
Returns true if the attribute exists.
228-
229-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::replace`
230-
Sets multiple attributes at once: takes a keyed array and sets each key => value pair.
237+
structure like this using a namespace character (which defaults to ``/``)::
231238

232-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove`
233-
Deletes an attribute by key.
239+
// ...
240+
use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;
234241

235-
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::clear`
236-
Clear the bag.
242+
$session = new Session(new NativeSessionStorage(), new NamespacedAttributeBag());
243+
$session->set('tokens/c', $value);
237244

238245
Flash Messages
239246
~~~~~~~~~~~~~~

form/unit_testing.rst

+5-53
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ widgets you want to display are available in the children property::
107107
$this->assertArrayHasKey($key, $children);
108108
}
109109

110+
.. tip::
111+
112+
Use :ref:`PHPUnit data providers <testing-data-providers>` to test multiple
113+
form conditions using the same test code.
114+
110115
Testings Types from the Service Container
111116
-----------------------------------------
112117

@@ -217,56 +222,3 @@ methods.
217222
.. versionadded:: 3.3
218223
The ``getTypes()``, ``getTypeExtensions()`` and ``getTypeGuessers()``
219224
methods were introduced in Symfony 3.3.
220-
221-
Testing against Different Sets of Data
222-
--------------------------------------
223-
224-
If you are not familiar yet with PHPUnit's `data providers`_, this might be
225-
a good opportunity to use them::
226-
227-
// tests/AppBundle/Form/Type/TestedTypeTest.php
228-
namespace Tests\AppBundle\Form\Type;
229-
230-
use AppBundle\Form\Type\TestedType;
231-
use Symfony\Component\Form\Test\TypeTestCase;
232-
233-
class TestedTypeTest extends TypeTestCase
234-
{
235-
/**
236-
* @dataProvider getValidTestData
237-
*/
238-
public function testForm($data)
239-
{
240-
// ... your test
241-
}
242-
243-
public function getValidTestData()
244-
{
245-
return array(
246-
array(
247-
'data' => array(
248-
'test' => 'test',
249-
'test2' => 'test2',
250-
),
251-
),
252-
array(
253-
'data' => array(),
254-
),
255-
array(
256-
'data' => array(
257-
'test' => null,
258-
'test2' => null,
259-
),
260-
),
261-
);
262-
}
263-
}
264-
265-
The code above will run your test three times with 3 different sets of
266-
data. This allows for decoupling the test fixtures from the tests and
267-
easily testing against multiple sets of data.
268-
269-
You can also pass another argument, such as a boolean if the form has to
270-
be synchronized with the given set of data or not etc.
271-
272-
.. _`data providers`: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers

security/custom_password_authenticator.rst

+15-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ the user::
2828
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2929
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
3030
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
31+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
3132
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
3233
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
3334
use Symfony\Component\Security\Core\User\UserProviderInterface;
@@ -52,7 +53,20 @@ the user::
5253
throw new CustomUserMessageAuthenticationException('Invalid username or password');
5354
}
5455

55-
$isPasswordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());
56+
$currentUser = $token->getUser();
57+
58+
if ($currentUser instanceof UserInterface) {
59+
if ($currentUser->getPassword() !== $user->getPassword()) {
60+
throw new BadCredentialsException('The credentials were changed from another session.');
61+
}
62+
} else {
63+
if ('' === ($givenPassword = $token->getCredentials())) {
64+
throw new BadCredentialsException('The given password cannot be empty.');
65+
}
66+
if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $givenPassword, $user->getSalt())) {
67+
throw new BadCredentialsException('The given password is invalid.');
68+
}
69+
}
5670

5771
if ($isPasswordValid) {
5872
$currentHour = date('G');

security/custom_provider.rst

+2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ Here's an example of how this might look::
144144
);
145145
}
146146

147+
$username = $user->getUsername();
148+
147149
return $this->fetchUser($username);
148150
}
149151

session/locale_sticky_session.rst

+42-11
Original file line numberDiff line numberDiff line change
@@ -169,19 +169,50 @@ event::
169169
$this->session->set('_locale', $user->getLocale());
170170
}
171171
}
172-
173-
public static function getSubscribedEvents()
174-
{
175-
return array(
176-
SecurityEvents::INTERACTIVE_LOGIN => array(array('onInteractiveLogin', 15)),
177-
);
178-
}
179172
}
180173

181-
If you're using the :ref:`default services.yml configuration <service-container-services-load-example>`,
182-
you're done! Symfony will automatically know about the event subscriber will pass
183-
your the ``session`` service. Now, when you login, the user's locale will be set
184-
into the session.
174+
Then register the listener:
175+
176+
.. configuration-block::
177+
178+
.. code-block:: yaml
179+
180+
# config/services.yaml
181+
services:
182+
App\EventListener\UserLocaleListener:
183+
tags:
184+
- { name: kernel.event_listener, event: security.interactive_login, method: onInteractiveLogin, priority: 15 }
185+
186+
.. code-block:: xml
187+
188+
<!-- config/services.xml -->
189+
<?xml version="1.0" encoding="UTF-8" ?>
190+
<container xmlns="http://symfony.com/schema/dic/services"
191+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
192+
xsi:schemaLocation="http://symfony.com/schema/dic/services
193+
http://symfony.com/schema/dic/services/services-1.0.xsd">
194+
195+
<services>
196+
<service id="App\EventListener\UserLocaleListener">
197+
<tag name="kernel.event_listener"
198+
event="security.interactive_login"
199+
method="onInteractiveLogin" priority=15 />
200+
</service>
201+
</services>
202+
</container>
203+
204+
.. code-block:: php
205+
206+
// config/services.php
207+
use AppBundle\EventListener\UserLocaleListener;
208+
use Symfony\Component\DependencyInjection\Reference;
209+
210+
$container
211+
->register(UserLocaleListener::class)
212+
->addTag(
213+
'kernel.event_listener',
214+
array('event' => 'security.interactive_login', 'method' => 'onInteractiveLogin', 'priority' => 15)
215+
);
185216
186217
.. caution::
187218

testing.rst

+36
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,41 @@ document::
300300
// ...or simply check that the response is a redirect to any URL
301301
$this->assertTrue($client->getResponse()->isRedirect());
302302

303+
.. _testing-data-providers:
304+
305+
Testing against Different Sets of Data
306+
--------------------------------------
307+
308+
It's common to have to execute the same test against different sets of data to
309+
check the multiple conditions code must handle. This is solved with PHPUnit's
310+
`data providers`_, which work both for unit and functional tests.
311+
312+
First, add one or more arguments to your test method and use them inside the
313+
test code. Then, define another method which returns a nested array with the
314+
arguments to use on each test run. Lastly, add the ``@dataProvider`` annotation
315+
to associate both methods::
316+
317+
/**
318+
* @dataProvider provideUrls
319+
*/
320+
public function testPageIsSuccessful($url)
321+
{
322+
$client = self::createClient();
323+
$client->request('GET', $url);
324+
325+
$this->assertTrue($client->getResponse()->isSuccessful());
326+
}
327+
328+
public function provideUrls()
329+
{
330+
return array(
331+
array('/'),
332+
array('/blog'),
333+
array('/contact'),
334+
// ...
335+
);
336+
}
337+
303338
.. index::
304339
single: Tests; Client
305340

@@ -942,3 +977,4 @@ Learn more
942977
.. _`documentation`: https://phpunit.de/manual/current/en/
943978
.. _`PHPUnit Bridge component`: https://symfony.com/components/PHPUnit%20Bridge
944979
.. _`$_SERVER`: https://php.net/manual/en/reserved.variables.server.php
980+
.. _`data providers`: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers

testing/http_authentication.rst

+2
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ needs::
118118
// See https://symfony.com/doc/current/reference/configuration/security.html#firewall-context
119119
$firewallContext = 'secured_area';
120120

121+
// you may need to use a different token class depending on your application.
122+
// for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken
121123
$token = new UsernamePasswordToken('admin', null, $firewallName, array('ROLE_ADMIN'));
122124
$session->set('_security_'.$firewallContext, serialize($token));
123125
$session->save();

0 commit comments

Comments
 (0)