Skip to content

Commit

Permalink
Merge branch '3.4' into 4.0
Browse files Browse the repository at this point in the history
* 3.4:
  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
  [Cache] minor tweaks
  Fixed the code of the custom password authenticator example
  Documented PHPUnit data providers
  • Loading branch information
javiereguiluz committed Jul 24, 2018
2 parents cef13f8 + 3fb163c commit ca653aa
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 123 deletions.
3 changes: 2 additions & 1 deletion best_practices/tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ functional tests, you can quickly spot any big errors before you deploy them:
Define a functional test that at least checks if your application pages
are successfully loading.

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

// tests/ApplicationAvailabilityFunctionalTest.php
namespace App\Tests;
Expand Down
7 changes: 4 additions & 3 deletions components/cache.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ The Cache Component

The Cache component provides an extended `PSR-6`_ implementation as well as
a `PSR-16`_ "Simple Cache" implementation for adding cache to your applications.
It is designed to have a low overhead and it ships with ready to use adapters
for the most common caching backends.
It is designed for performance and resiliency, and ships with ready to use
adapters for the most common caching backends, including proxies for adapting
from/to `Doctrine Cache`_.

Installation
------------
Expand Down Expand Up @@ -189,4 +190,4 @@ Advanced Usage (PSR-6)

.. _`PSR-6`: http://www.php-fig.org/psr/psr-6/
.. _`PSR-16`: http://www.php-fig.org/psr/psr-16/
.. _Packagist: https://packagist.org/packages/symfony/cache
.. _Doctrine Cache: https://www.doctrine-project.org/projects/cache.html
2 changes: 1 addition & 1 deletion components/cache/adapters/pdo_doctrine_dbal_adapter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ third, and forth parameters::
// until the database table is truncated or its rows are otherwise deleted)
$defaultLifetime = 0,

// an array of options for configuring the database connection
// an array of options for configuring the database table and connection
$options = array()
);

Expand Down
4 changes: 2 additions & 2 deletions components/cache/cache_invalidation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ several cached items, keeping them in sync can be difficult.

The Symfony Cache component provides two mechanisms to help solving this problem:

* :ref:`Tags based invalidation <cache-component-tags>` for managing data dependencies;
* :ref:`Tags-based invalidation <cache-component-tags>` for managing data dependencies;
* :ref:`Expiration based invalidation <cache-component-expiration>` for time related dependencies.

.. _cache-component-tags:

Using Cache Tags
----------------

To benefit from tags based invalidation, you need to attach the proper tags to
To benefit from tags-based invalidation, you need to attach the proper tags to
each cached item. Each tag is a plain string identifier that you can use at any
time to trigger the removal of all items associated with this tag.

Expand Down
2 changes: 1 addition & 1 deletion components/cache/cache_pools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ allowing manual removal of stale cache items::

The :ref:`ChainAdapter <component-cache-chain-adapter>` implementation does not directly
contain any pruning logic itself. Instead, when calling the chain adapter's
:method:`Symfony\\Component\\Cache\\ChainAdapter::prune` method, the call is delegated to all
:method:`Symfony\\Component\\Cache\\Adapter\\ChainAdapter::prune` method, the call is delegated to all
its compatible cache adapters (and those that do not implement ``PruneableInterface`` are
silently ignored)::

Expand Down
107 changes: 57 additions & 50 deletions components/http_foundation/sessions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,29 +94,12 @@ Session Workflow
Session Attributes
..................

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

: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::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 a "Bag", a PHP object that acts like
an array. A few methods exist for "Bag" management:
In addition, a few methods exist for "Bag" management:

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

.. _attribute-bag-interface:

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.
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.

:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface`
has a simple API

:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set`
Sets an attribute by name (``set('name', 'value')``).

:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::get`
Gets an attribute by name (``get('name')``) and can define a default
value when the attribute doesn't exist (``get('name', 'default_value')``).

:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::all`
Gets all attributes as an associative array of ``name => value``.

:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::has`
Returns ``true`` if the attribute exists.

:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::replace`
Sets multiple attributes at once using an associative array (``name => value``).
If the attributes existed, they are replaced; if not, they are created.

:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove`
Deletes an attribute by name and returns its value.

:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::clear`
Deletes all attributes.

Example::

use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;

$session = new Session(new NativeSessionStorage(), new AttributeBag());
$session->set('token', 'a6c1e0b6');
// ...
$token = $session->get('token');
// if the attribute may or may not exist, you can define a default value for it
$token = $session->get('attribute-name', 'default-attribute-value');
// ...
$session->clear();

Namespaced Attributes
.....................

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
Expand All @@ -205,35 +234,13 @@ the array::
$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::replace`
Sets multiple attributes at once: takes a keyed array and sets each key => value pair.
structure like this using a namespace character (which defaults to ``/``)::

:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove`
Deletes an attribute by key.
// ...
use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;

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

Flash Messages
~~~~~~~~~~~~~~
Expand Down
58 changes: 5 additions & 53 deletions form/unit_testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ widgets you want to display are available in the children property::
$this->assertArrayHasKey($key, $children);
}

.. tip::

Use :ref:`PHPUnit data providers <testing-data-providers>` to test multiple
form conditions using the same test code.

Testings Types from the Service Container
-----------------------------------------

Expand Down Expand Up @@ -213,56 +218,3 @@ guessers using the :method:`Symfony\\Component\\Form\\Test\\FormIntegrationTestC
:method:`Symfony\\Component\\Form\\Test\\FormIntegrationTestCase::getTypeExtensions`
and :method:`Symfony\\Component\\Form\\Test\\FormIntegrationTestCase::getTypeGuessers`
methods.

Testing against Different Sets of Data
--------------------------------------

If you are not familiar yet with PHPUnit's `data providers`_, this might be
a good opportunity to use them::

// tests/Form/Type/TestedTypeTest.php
namespace App\Tests\Form\Type;

use App\Form\Type\TestedType;
use Symfony\Component\Form\Test\TypeTestCase;

class TestedTypeTest extends TypeTestCase
{
/**
* @dataProvider getValidTestData
*/
public function testForm($data)
{
// ... your test
}

public function getValidTestData()
{
return array(
array(
'data' => array(
'test' => 'test',
'test2' => 'test2',
),
),
array(
'data' => array(),
),
array(
'data' => array(
'test' => null,
'test2' => null,
),
),
);
}
}

The code above will run your test three times with 3 different sets of
data. This allows for decoupling the test fixtures from the tests and
easily testing against multiple sets of data.

You can also pass another argument, such as a boolean if the form has to
be synchronized with the given set of data or not etc.

.. _`data providers`: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
16 changes: 15 additions & 1 deletion security/custom_password_authenticator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ the user::
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
Expand All @@ -52,7 +53,20 @@ the user::
throw new CustomUserMessageAuthenticationException('Invalid username or password');
}

$isPasswordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());
$currentUser = $token->getUser();

if ($currentUser instanceof UserInterface) {
if ($currentUser->getPassword() !== $user->getPassword()) {
throw new BadCredentialsException('The credentials were changed from another session.');
}
} else {
if ('' === ($givenPassword = $token->getCredentials())) {
throw new BadCredentialsException('The given password cannot be empty.');
}
if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $givenPassword, $user->getSalt())) {
throw new BadCredentialsException('The given password is invalid.');
}
}

if ($isPasswordValid) {
$currentHour = date('G');
Expand Down
2 changes: 2 additions & 0 deletions security/custom_provider.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ Here's an example of how this might look::
);
}

$username = $user->getUsername();

return $this->fetchUser($username);
}

Expand Down
53 changes: 42 additions & 11 deletions session/locale_sticky_session.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,19 +171,50 @@ event::
$this->session->set('_locale', $user->getLocale());
}
}

public static function getSubscribedEvents()
{
return array(
SecurityEvents::INTERACTIVE_LOGIN => array(array('onInteractiveLogin', 15)),
);
}
}

If you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`,
you're done! Symfony will automatically know about the event subscriber will pass
your the ``session`` service. Now, when you login, the user's locale will be set
into the session.
Then register the listener:

.. configuration-block::

.. code-block:: yaml
# config/services.yaml
services:
App\EventListener\UserLocaleListener:
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: onInteractiveLogin, priority: 15 }
.. code-block:: xml
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="App\EventListener\UserLocaleListener">
<tag name="kernel.event_listener"
event="security.interactive_login"
method="onInteractiveLogin" priority=15 />
</service>
</services>
</container>
.. code-block:: php
// config/services.php
use AppBundle\EventListener\UserLocaleListener;
use Symfony\Component\DependencyInjection\Reference;
$container
->register(UserLocaleListener::class)
->addTag(
'kernel.event_listener',
array('event' => 'security.interactive_login', 'method' => 'onInteractiveLogin', 'priority' => 15)
);
.. caution::

Expand Down
Loading

0 comments on commit ca653aa

Please sign in to comment.