diff --git a/security/remember_me.rst b/security/remember_me.rst index b14b012202f..f7b68a974ff 100644 --- a/security/remember_me.rst +++ b/security/remember_me.rst @@ -354,3 +354,42 @@ service you created before: ->tokenProvider(DoctrineTokenProvider::class) ; }; + +Activating Remember Me When Using a Custom Authenticator +-------------------------------------------------------- + +When you use a custom authenticator, you must add a ``RememberMeBadge`` to the ``Passport`` +for the remember me function to be activated. Without the badge, remember me will not be +active, regardless of any other remember me settings. + +For example:: + + // src/Service/LoginAuthenticator.php + namespace App\Service; + + // ... + use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + use Symfony\Component\Security\Http\Authenticator\Passport\Passport; + use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; + + class LoginAuthenticator extends AbstractAuthenticator + { + public function authenticate(Request $request): PassportInterface + { + $password = $request->request->get('password'); + $username = $request->request->get('username'); + $csrfToken = $request->request->get('csrf_token'); + + return new Passport( + new UserBadge($username), + new PasswordCredentials($password), + [ + new CsrfTokenBadge('login', $csrfToken), + new RememberMeBadge(), + ] + ); + } + } diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst index 4c7f2ed0158..36f7d200808 100644 --- a/service_container/service_decoration.rst +++ b/service_container/service_decoration.rst @@ -12,12 +12,12 @@ When overriding an existing definition, the original service is lost: # config/services.yaml services: - App\Mailer: ~ + App\Mailer\Mailer: ~ - # this replaces the old App\Mailer definition with the new one, the + # this replaces the old App\Mailer\Mailer definition with the new one, the # old definition is lost - App\Mailer: - class: App\NewMailer + App\Mailer\Mailer: + class: App\Mailer\NewMailer .. code-block:: xml @@ -28,11 +28,11 @@ When overriding an existing definition, the original service is lost: xsd:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> - + - - + @@ -41,15 +41,15 @@ When overriding an existing definition, the original service is lost: // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\Mailer; - use App\NewMailer; + use App\Mailer\Mailer; + use App\Mailer\NewMailer; return function(ContainerConfigurator $configurator) { $services = $configurator->services(); $services->set(Mailer::class); - // this replaces the old App\Mailer definition with the new one, the + // this replaces the old App\Mailer\Mailer definition with the new one, the // old definition is lost $services->set(Mailer::class, NewMailer::class); }; @@ -57,7 +57,7 @@ When overriding an existing definition, the original service is lost: Most of the time, that's exactly what you want to do. But sometimes, you might want to decorate the old one instead (i.e. apply the `Decorator pattern`_). In this case, the old service should be kept around to be able to reference -it in the new one. This configuration replaces ``App\Mailer`` with a new one, +it in the new one. This configuration replaces ``App\Mailer\Mailer`` with a new one, but keeps a reference of the old one as ``.inner``: .. configuration-block:: @@ -66,12 +66,12 @@ but keeps a reference of the old one as ``.inner``: # config/services.yaml services: - App\Mailer: ~ + App\Mailer\Mailer: ~ - App\DecoratingMailer: - # overrides the App\Mailer service + App\Mailer\DecoratingMailer: + # overrides the App\Mailer\Mailer service # but that service is still available as ".inner" - decorates: App\Mailer + decorates: App\Mailer\Mailer .. code-block:: xml @@ -82,12 +82,12 @@ but keeps a reference of the old one as ``.inner``: xsd:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> - + - - @@ -98,8 +98,8 @@ but keeps a reference of the old one as ``.inner``: // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\DecoratingMailer; - use App\Mailer; + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; return function(ContainerConfigurator $configurator) { $services = $configurator->services(); @@ -107,13 +107,13 @@ but keeps a reference of the old one as ``.inner``: $services->set(Mailer::class); $services->set(DecoratingMailer::class) - // overrides the App\Mailer service + // overrides the App\Mailer\Mailer service // but that service is still available as ".inner" ->decorate(Mailer::class); }; -The ``decorates`` option tells the container that the ``App\DecoratingMailer`` -service replaces the ``App\Mailer`` service. If you're using the +The ``decorates`` option tells the container that the ``App\Mailer\DecoratingMailer`` +service replaces the ``App\Mailer\Mailer`` service. If you're using the :ref:`default services.yaml configuration `, the decorated service is automatically injected when the constructor of the decorating service has one argument type-hinted with the decorated service class. @@ -129,10 +129,10 @@ automatically changed to ``'.inner'``): # config/services.yaml services: - App\Mailer: ~ + App\Mailer\Mailer: ~ - App\DecoratingMailer: - decorates: App\Mailer + App\Mailer\DecoratingMailer: + decorates: App\Mailer\Mailer # pass the old service as an argument arguments: ['@.inner'] @@ -145,10 +145,10 @@ automatically changed to ``'.inner'``): xsd:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> - + - @@ -161,8 +161,8 @@ automatically changed to ``'.inner'``): // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\DecoratingMailer; - use App\Mailer; + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; return function(ContainerConfigurator $configurator) { $services = $configurator->services(); @@ -183,14 +183,14 @@ automatically changed to ``'.inner'``): .. tip:: - The visibility of the decorated ``App\Mailer`` service (which is an alias - for the new service) will still be the same as the original ``App\Mailer`` + The visibility of the decorated ``App\Mailer\Mailer`` service (which is an alias + for the new service) will still be the same as the original ``App\Mailer\Mailer`` visibility. .. note:: The generated inner id is based on the id of the decorator service - (``App\DecoratingMailer`` here), not of the decorated service (``App\Mailer`` + (``App\Mailer\DecoratingMailer`` here), not of the decorated service (``App\Mailer\Mailer`` here). You can control the inner service name via the ``decoration_inner_name`` option: @@ -200,10 +200,10 @@ automatically changed to ``'.inner'``): # config/services.yaml services: - App\DecoratingMailer: + App\Mailer\DecoratingMailer: # ... - decoration_inner_name: App\DecoratingMailer.wooz - arguments: ['@App\DecoratingMailer.wooz'] + decoration_inner_name: App\Mailer\DecoratingMailer.wooz + arguments: ['@App\Mailer\DecoratingMailer.wooz'] .. code-block:: xml @@ -217,12 +217,12 @@ automatically changed to ``'.inner'``): - + @@ -233,8 +233,8 @@ automatically changed to ``'.inner'``): // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\DecoratingMailer; - use App\Mailer; + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; return function(ContainerConfigurator $configurator) { $services = $configurator->services(); @@ -246,6 +246,213 @@ automatically changed to ``'.inner'``): ->args([service(DecoratingMailer::class.'.wooz')]); }; +Two Different Ways to Decorate a Service +---------------------------------------- + +A service can be decorated by either making the decorating service: + +- Implement the same interface as the decorated service, or +- Extend the decorated service. + +Implementing The Same Interface As The Decorated Service +-------------------------------------------------------- + +.. tip:: + + This first method is the recommended way to decorate a service. + + However, it only works when the decorated class implements an injectable interface. + +Assume the following for the decorated class:: + + // src/Mailer/Mailer.php + namespace App\Mailer; + + class Mailer implements MailerInterface { + } + +Also assume that your service definitions are configured so that you would type-hint +``App\Mailer\MailerInterface`` instead of ``App\Mailer\Mailer`` to inject the service into other services. +(If not, and you instead must type-hint ``App\Mailer\Mailer``, then skip to "Extend The Decorated Service".) + +In this case, you would inject the decorated mailer service as follows:: + + // src/Services/AcmeService.php + namespace App\Services; + + use App\Mailer\MailerInterface; + + class AcmeService { + public function __construct(MailerInterface $mailer) { + } + } + +Create your decorating class as follows:: + + // src/Mailer/DecoratingMailer.php + namespace App\Mailer; + + class DecoratingMailer implements MailerInterface { + public function __construct(MailerInterface $mailer) { + } + } + +Your decoration configuration will be as follows: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\Mailer\MailerInterface: + class: App\Mailer\Mailer + + App\Mailer\DecoratingMailer: + decorates: App\Mailer\MailerInterface + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; + use App\Mailer\MailerInterface; + + return function(ContainerConfigurator $configurator) { + $services = $configurator->services(); + + $services->set(MailerInterface::class) + ->class(Mailer::class); + + $services->set(DecoratingMailer::class) + ->decorate(MailerInterface::class); + }; + +Extend The Decorated Service +---------------------------- + +.. tip:: + + This second method of the decorating service extending the decorated service is + not strictly speaking "decoration" in accordance with the `Decorator pattern`_, but it does + allow, in Symfony, for your "decorating" class to be automatically injected when the "decorated" class is + type-hinted. In other words, the end result is the same as the first option. It could help you + with decorating services from third-party bundles that don't adhere to interface implementation + best-practices. + + You might also notice that the ".inner" part of the decoration configuration is meaningless with this + decoration option, because the "decorated" service is not injected into the "decorating" service. + +Assume the following:: + + // src/Mailer/Mailer.php + namespace App\Mailer; + + class Mailer { + } + +In this case, you would inject the ``App\Mailer\Mailer`` service as follows into another service:: + + // src/Services/AcmeService.php + namespace App\Services; + + use App\Mailer\Mailer; + + class AcmeService { + public function __construct(Mailer $mailer) { + } + } + +Create your decorating class as follows:: + + // src/Mailer/DecoratingMailer.php + namespace App\Mailer; + + use App\Mailer\Mailer; + + class DecoratingMailer extends Mailer { + public function __construct() { + } + } + +Your decoration configuration will be as follows: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\Mailer\Mailer: ~ + + App\Mailer\DecoratingMailer: + decorates: App\Mailer\Mailer + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; + + return function(ContainerConfigurator $configurator) { + $services = $configurator->services(); + + $services->set(Mailer::class); + + $services->set(DecoratingMailer::class) + ->decorate(Mailer::class); + }; + +.. tip:: + + If the ``App\Mailer\Mailer`` class is marked as ``final``, and it does not implement an injectable + interface, then you will not be able to decorate it, because a final class cannot be extended. + +Congratulations! With both options, the ``App\Mailer\DecoratingMailer`` class is automatically injected +instead of the ``App\Mailer\Mailer`` class. + Decoration Priority -------------------