From 0a6f24d6144b7dea0052fdf7294adadadf036ef8 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 10 May 2017 05:37:11 -0400 Subject: [PATCH 01/10] more tweaks --- event_dispatcher/before_after_filters.rst | 141 +++++++--------------- 1 file changed, 43 insertions(+), 98 deletions(-) diff --git a/event_dispatcher/before_after_filters.rst b/event_dispatcher/before_after_filters.rst index b36620bc5f8..5ef9247c09c 100644 --- a/event_dispatcher/before_after_filters.rst +++ b/event_dispatcher/before_after_filters.rst @@ -67,9 +67,9 @@ parameters key: Tag Controllers to Be Checked ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A ``kernel.controller`` listener gets notified on *every* request, right before -the controller is executed. So, first, you need some way to identify if the -controller that matches the request needs token validation. +A ``kernel.controller`` (aka ``KernelEvents::CONTROLLER``) listener gets notified +on *every* request, right before the controller is executed. So, first, you need +some way to identify if the controller that matches the request needs token validation. A clean and easy way is to create an empty interface and make the controllers implement it:: @@ -97,21 +97,23 @@ A controller that implements this interface simply looks like this:: } } -Creating an Event Listener -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Creating an Event Subscriber +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Next, you'll need to create an event listener, which will hold the logic that you want to be executed before your controllers. If you're not familiar with event listeners, you can learn more about them at :doc:`/event_dispatcher`:: - // src/AppBundle/EventListener/TokenListener.php - namespace AppBundle\EventListener; + // src/AppBundle/EventSubscriber/TokenSubscriber.php + namespace AppBundle\EventSubscriber; use AppBundle\Controller\TokenAuthenticatedController; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Symfony\Component\HttpKernel\KernelEvents; - class TokenListener + class TokenSubscriber implements EventSubscriberInterface { private $tokens; @@ -140,52 +142,28 @@ event listeners, you can learn more about them at :doc:`/event_dispatcher`:: } } } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::CONTROLLER => 'onKernelController', + ); + } } -Registering the Listener -~~~~~~~~~~~~~~~~~~~~~~~~ - -Finally, register your listener as a service and tag it as an event listener. -By listening on ``kernel.controller``, you're telling Symfony that you want -your listener to be called just before any controller is executed. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - app.tokens.action_listener: - class: AppBundle\EventListener\TokenListener - arguments: ['%tokens%'] - tags: - - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } - - .. code-block:: xml - - - - %tokens% - - - - .. code-block:: php +That's it! Your ``services.yml`` file should already be setup to load services from +the ``EventSubscriber`` directory. Symfony takes care of the rest. Your +``TokenSubscriber`` ``onKernelController()`` method will be executed on each request. +If the controller that is about to be executed implements ``TokenAuthenticatedController``, +token authentication is applied. This lets you have a "before" filter on any controller +you want. - // app/config/services.php - use AppBundle\EventListener\TokenListener; +.. tip:: - $container->register('app.tokens.action_listener', TokenListener::class) - ->addArgument('%tokens%') - ->addTag('kernel.event_listener', array( - 'event' => 'kernel.controller', - 'method' => 'onKernelController', - )); - -With this configuration, your ``TokenListener`` ``onKernelController()`` method -will be executed on each request. If the controller that is about to be executed -implements ``TokenAuthenticatedController``, token authentication is -applied. This lets you have a "before" filter on any controller that you -want. + If your subscriber id *not* called on each request, double-check that + you're :ref:`loading services ` from + the ``EventSubscriber`` directory and have :ref:`autoconfigure ` + enabled. You can also manually add the ``kernel.event_subscriber`` tag. After Filters with the ``kernel.response`` Event ------------------------------------------------ @@ -195,12 +173,12 @@ can also add a hook that's executed *after* your controller. For this example, imagine that you want to add a sha1 hash (with a salt using that token) to all responses that have passed this token authentication. -Another core Symfony event - called ``kernel.response`` - is notified on -every request, but after the controller returns a Response object. Creating -an "after" listener is as easy as creating a listener class and registering +Another core Symfony event - called ``kernel.response`` (aka ``KernelEvents::RESPONSE``) - +is notified on every request, but after the controller returns a Response object. +Creating an "after" listener is as easy as creating a listener class and registering it as a service on this event. -For example, take the ``TokenListener`` from the previous example and first +For example, take the ``TokenSubscriber`` from the previous example and first record the authentication token inside the request attributes. This will serve as a basic flag that this request underwent token authentication:: @@ -219,9 +197,9 @@ serve as a basic flag that this request underwent token authentication:: } } -Now, add another method to this class - ``onKernelResponse()`` - that looks -for this flag on the request object and sets a custom header on the response -if it's found:: +Now, configure the subscriber to listen to another event and add ``onKernelResponse()``. +This will look for the ``auth_token`` flag on the request object and set a custom +header on the response if it's found:: // add the new use statement at the top of your file use Symfony\Component\HttpKernel\Event\FilterResponseEvent; @@ -240,49 +218,16 @@ if it's found:: $response->headers->set('X-CONTENT-HASH', $hash); } -Finally, a second "tag" is needed in the service definition to notify Symfony -that the ``onKernelResponse`` event should be notified for the ``kernel.response`` -event: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - app.tokens.action_listener: - class: AppBundle\EventListener\TokenListener - arguments: ['%tokens%'] - tags: - - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } - - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse } - - .. code-block:: xml - - - - %tokens% - - - + public static function getSubscribedEvents() + { + return array( + KernelEvents::CONTROLLER => 'onKernelController', + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } - .. code-block:: php - // app/config/services.php - use AppBundle\EventListener\TokenListener; - - $container->register('app.tokens.action_listener', TokenListener::class) - ->addArgument('%tokens%') - ->addTag('kernel.event_listener', array( - 'event' => 'kernel.controller', - 'method' => 'onKernelController', - )) - ->addTag('kernel.event_listener', array( - 'event' => 'kernel.response', - 'method' => 'onKernelResponse', - )); - -That's it! The ``TokenListener`` is now notified before every controller is +That's it! The ``TokenSubscriber`` is now notified before every controller is executed (``onKernelController()``) and after every controller returns a response (``onKernelResponse()``). By making specific controllers implement the ``TokenAuthenticatedController`` interface, your listener knows which controllers it should take action on. From 5c9ac789b824b9c4736c54f0c50093f3f6b96912 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 10 May 2017 06:09:52 -0400 Subject: [PATCH 02/10] More updates --- _build/redirection_map | 1 + event_dispatcher/class_extension.rst | 125 --------------------------- event_dispatcher/method_behavior.rst | 123 +++++++++++++++++++++----- 3 files changed, 103 insertions(+), 146 deletions(-) delete mode 100644 event_dispatcher/class_extension.rst diff --git a/_build/redirection_map b/_build/redirection_map index 0c1a3a45898..374fd58fb01 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -334,3 +334,4 @@ /form /forms /testing/simulating_authentication /testing/http_authentication /components/dependency_injection/autowiring /service_container/autowiring +/event_dispatcher/class_extension /event_dispatcher diff --git a/event_dispatcher/class_extension.rst b/event_dispatcher/class_extension.rst deleted file mode 100644 index 111def1f811..00000000000 --- a/event_dispatcher/class_extension.rst +++ /dev/null @@ -1,125 +0,0 @@ -.. index:: - single: EventDispatcher - -How to Extend a Class without Using Inheritance -=============================================== - -To allow multiple classes to add methods to another one, you can define the -magic ``__call()`` method in the class you want to be extended like this: - -.. code-block:: php - - class Foo - { - // ... - - public function __call($method, $arguments) - { - // create an event named 'foo.method_is_not_found' - $event = new HandleUndefinedMethodEvent($this, $method, $arguments); - $this->dispatcher->dispatch('foo.method_is_not_found', $event); - - // no listener was able to process the event? The method does not exist - if (!$event->isProcessed()) { - throw new \Exception(sprintf('Call to undefined method %s::%s.', get_class($this), $method)); - } - - // return the listener returned value - return $event->getReturnValue(); - } - } - -This uses a special ``HandleUndefinedMethodEvent`` that should also be -created. This is a generic class that could be reused each time you need to -use this pattern of class extension: - -.. code-block:: php - - use Symfony\Component\EventDispatcher\Event; - - class HandleUndefinedMethodEvent extends Event - { - protected $subject; - protected $method; - protected $arguments; - protected $returnValue; - protected $isProcessed = false; - - public function __construct($subject, $method, $arguments) - { - $this->subject = $subject; - $this->method = $method; - $this->arguments = $arguments; - } - - public function getSubject() - { - return $this->subject; - } - - public function getMethod() - { - return $this->method; - } - - public function getArguments() - { - return $this->arguments; - } - - /** - * Sets the value to return and stops other listeners from being notified - */ - public function setReturnValue($val) - { - $this->returnValue = $val; - $this->isProcessed = true; - $this->stopPropagation(); - } - - public function getReturnValue() - { - return $this->returnValue; - } - - public function isProcessed() - { - return $this->isProcessed; - } - } - -Next, create a class that will listen to the ``foo.method_is_not_found`` event -and *add* the method ``bar()``: - -.. code-block:: php - - class Bar - { - public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event) - { - // only respond to the calls to the 'bar' method - if ('bar' != $event->getMethod()) { - // allow another listener to take care of this unknown method - return; - } - - // the subject object (the foo instance) - $foo = $event->getSubject(); - - // the bar method arguments - $arguments = $event->getArguments(); - - // ... do something - - // set the return value - $event->setReturnValue($someValue); - } - } - -Finally, add the new ``bar()`` method to the ``Foo`` class by registering an -instance of ``Bar`` with the ``foo.method_is_not_found`` event: - -.. code-block:: php - - $bar = new Bar(); - $dispatcher->addListener('foo.method_is_not_found', array($bar, 'onFooMethodIsNotFound')); diff --git a/event_dispatcher/method_behavior.rst b/event_dispatcher/method_behavior.rst index 3b25b0deb1a..4e2e3c77216 100644 --- a/event_dispatcher/method_behavior.rst +++ b/event_dispatcher/method_behavior.rst @@ -11,47 +11,128 @@ If you want to do something just before, or just after a method is called, you can dispatch an event respectively at the beginning or at the end of the method:: - class Foo + class CustomMailer { // ... - public function send($foo, $bar) + public function send($subject, $message) { - // do something before the method - $event = new FilterBeforeSendEvent($foo, $bar); - $this->dispatcher->dispatch('foo.pre_send', $event); + // dispatch an event before the method + $event = new BeforeSendMailEvent($subject, $message); + $this->dispatcher->dispatch('mailer.pre_send', $event); // get $foo and $bar from the event, they may have been modified - $foo = $event->getFoo(); - $bar = $event->getBar(); + $subject = $event->getSubject(); + $message = $event->getMessage(); // the real method implementation is here $ret = ...; // do something after the method - $event = new FilterSendReturnValue($ret); - $this->dispatcher->dispatch('foo.post_send', $event); + $event = new AfterSendMailEvent($ret); + $this->dispatcher->dispatch('mailer.post_send', $event); return $event->getReturnValue(); } } -In this example, two events are thrown: ``foo.pre_send``, before the method is -executed, and ``foo.post_send`` after the method is executed. Each uses a +In this example, two events are thrown: ``mailer.pre_send``, before the method is +executed, and ``mailer.post_send`` after the method is executed. Each uses a custom Event class to communicate information to the listeners of the two -events. These event classes would need to be created by you and should allow, -in this example, the variables ``$foo``, ``$bar`` and ``$ret`` to be retrieved -and set by the listeners. +events. For example, ``BeforeSendMailEvent`` might look like this:: -For example, assuming the ``FilterSendReturnValue`` has a ``setReturnValue()`` -method, one listener might look like this: + // src/AppBundle/Event/BeforeSendMailEvent.php + namespace AppBundle\Event; -.. code-block:: php + use Symfony\Component\EventDispatcher\Event; - public function onFooPostSend(FilterSendReturnValue $event) + class BeforeSendMailEvent extends Event { - $ret = $event->getReturnValue(); - // modify the original ``$ret`` value + private $subject; + private $message; - $event->setReturnValue($ret); + public function __construct($subject, $message) + { + $this->subject = $subject; + $this->message = $message; + } + + public function getSubject() + { + return $this->subject; + } + + public function setSubject($subject) + { + $this->subject = $subject; + } + + public function getMessage() + { + return $this->message; + } + + public function setMessage($message) + { + $this->message = $message; + } } + +And the ``AfterSendMailEvent`` even like this:: + + // src/AppBundle/Event/AfterSendMailEvent.php + namespace AppBundle\Event; + + use Symfony\Component\EventDispatcher\Event; + + class AfterSendMailEvent extends Event + { + private $returnValue; + + public function __construct($returnValue) + { + $this->returnValue = $returnValue; + } + + public function getReturnValue() + { + return $this->returnValue; + } + + public function setReturnValue($returnValue) + { + $this->returnValue = $returnValue; + } + } + +Both events allow you to get some information (e.g. ``getMessage()``) and even change +that information (e.g. ``setMessage()``). + +Now, you can create an event subscriber to hook into this event. For example, you +could listen to the ``mailer.post_send`` event and change the method's return value:: + + // src/AppBundle/EventSubscriber/MailPostSendSubscriber.php + namespace AppBundle\EventSubscriber; + + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use AppBundle\Event\AfterSendMailEvent; + + class MailPostSendSubscriber implements EventSubscriberInterface + { + public function onMailerPostSend(AfterSendMailEvent $event) + { + $ret = $event->getReturnValue(); + // modify the original ``$ret`` value + + $event->setReturnValue($ret); + } + + public static function getSubscribedEvents() + { + return array( + 'mailer.post_send' => 'onMailerPostSend' + ); + } + } + +That's it! From ba77a1575ad1e3272819be339b211ebe17493289 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 14 May 2017 15:18:39 -0400 Subject: [PATCH 03/10] Many more updates to favor types of ids' AND updates for now importing all of src/AppBundle --- controller.rst | 11 +- controller/soap_web_service.rst | 50 +-- event_dispatcher.rst | 2 + event_dispatcher/before_after_filters.rst | 2 +- event_dispatcher/method_behavior.rst | 3 +- form/create_custom_field_type.rst | 129 +----- form/create_form_type_extension.rst | 153 ++----- form/data_transformers.rst | 141 ++---- form/direct_submit.rst | 4 +- form/dynamic_form_modification.rst | 85 ++-- form/form_collections.rst | 4 +- form/form_dependencies.rst | 21 +- form/type_guesser.rst | 19 +- frontend/custom_version_strategy.rst | 32 +- introduction/from_flat_php_to_symfony2.rst | 11 +- logging/formatter.rst | 27 +- logging/monolog_console.rst | 15 +- logging/processors.rst | 120 ++--- service_container.rst | 486 +++++++++++++-------- service_container/parameters.rst | 30 +- 20 files changed, 606 insertions(+), 739 deletions(-) diff --git a/controller.rst b/controller.rst index c93bcb0e49c..40ea2519e84 100644 --- a/controller.rst +++ b/controller.rst @@ -338,13 +338,18 @@ in your controllers. For more information about services, see the :doc:`/service_container` article. +.. _controller-service-arguments-tag: + .. note:: If this isn't working, make sure your controller is registered as a service, is :ref:`autoconfigured ` and extends either :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` or - :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. Or, - you can tag your service manually with ``controller.service_arguments``. All - of this is done for you in a fresh Symfony install. + :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. If + you use the :ref:`services.yml configuration from the Symfony Standard Edition `, + then your controllers are already registered as services and autoconfigured. + + If you're not using the default configuration, you can tag your service manually + with ``controller.service_arguments``. .. _accessing-other-services: .. _controller-access-services-directly: diff --git a/controller/soap_web_service.rst b/controller/soap_web_service.rst index 47412c0fd8f..6d5bba3d04c 100644 --- a/controller/soap_web_service.rst +++ b/controller/soap_web_service.rst @@ -50,53 +50,11 @@ In this case, the SOAP service will allow the client to call a method called } } -Next, make sure that your new class is registered as a service. If you use -:doc:`autowiring ` (enabled by default in the Symfony -Standard Edition), this is easy: +Next, make sure that your new class is registered as a service. If you're using +the :ref:`default services configuration `, +you don't need to do anything! -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - _defaults: - # ... be sure autowiring is enabled - autowire: true - # ... - - # add Service/ to the list of directories to load services from - AppBundle\: - resource: '../../src/AppBundle/{Service,Updates,Command,Form,EventSubscriber,Twig,Security}' - - .. code-block:: xml - - - - - - - - - - - - - - - - .. code-block:: php - - // app/config/services.php - use AppBundle\Service\HelloService; - - $container->autowire(HelloService::class) - ->setPublic(false); - -Below is an example of a controller that is capable of handling a SOAP +Finally, below is an example of a controller that is capable of handling a SOAP request. Because ``indexAction()`` is accessible via ``/soap``, the WSDL document can be retrieved via ``/soap?wsdl``:: diff --git a/event_dispatcher.rst b/event_dispatcher.rst index 34ad82502e1..9e26f2e7d07 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -179,6 +179,8 @@ listen to the same ``kernel.exception`` event:: That's it! Your ``services.yml`` file should already be setup to load services from the ``EventSubscriber`` directory. Symfony takes care of the rest. +.. _ref-event-subscriber-configuration: + .. tip:: If your methods are *not* called when an exception is thrown, double-check that diff --git a/event_dispatcher/before_after_filters.rst b/event_dispatcher/before_after_filters.rst index 5ef9247c09c..18100a97d1f 100644 --- a/event_dispatcher/before_after_filters.rst +++ b/event_dispatcher/before_after_filters.rst @@ -142,7 +142,7 @@ event listeners, you can learn more about them at :doc:`/event_dispatcher`:: } } } - + public static function getSubscribedEvents() { return array( diff --git a/event_dispatcher/method_behavior.rst b/event_dispatcher/method_behavior.rst index 4e2e3c77216..274f8f1983b 100644 --- a/event_dispatcher/method_behavior.rst +++ b/event_dispatcher/method_behavior.rst @@ -135,4 +135,5 @@ could listen to the ``mailer.post_send`` event and change the method's return va } } -That's it! +That's it! Your subscriber should be called automatically (or read more about +:ref:`event subscriber configuration `). diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst index 2425b41a5a5..7b75441b27e 100644 --- a/form/create_custom_field_type.rst +++ b/form/create_custom_field_type.rst @@ -56,6 +56,8 @@ all of the logic and rendering of that field type. To see some of the logic, check out the `ChoiceType`_ class. There are three methods that are particularly important: +.. _form-type-methods-explanation: + ``buildForm()`` Each field type has a ``buildForm()`` method, which is where you configure and build any field(s). Notice that this is the same method @@ -260,135 +262,40 @@ the gender codes were stored in configuration or in a database? The next section explains how more complex field types solve this problem. .. _form-field-service: +.. _creating-your-field-type-as-a-service: -Creating your Field Type as a Service -------------------------------------- - -So far, this entry has assumed that you have a very simple custom field type. -But if you need access to configuration, a database connection, or some other -service, then you'll want to register your custom type as a service. For -example, suppose that you're storing the gender parameters in configuration: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - parameters: - genders: - m: Male - f: Female - - .. code-block:: xml - - - - - Male - Female - - - - .. code-block:: php - - // app/config/config.php - $container->setParameter('genders.m', 'Male'); - $container->setParameter('genders.f', 'Female'); - -To use the parameter, define your custom field type as a service, injecting -the ``genders`` parameter value as the first argument to its to-be-created -``__construct()`` function: - -.. configuration-block:: +Accessing Services and Config +----------------------------- - .. code-block:: yaml - - # src/AppBundle/Resources/config/services.yml - services: - app.form.type.gender: - class: AppBundle\Form\Type\GenderType - arguments: - - '%genders%' - tags: [form.type] - - .. code-block:: xml - - - - %genders% - - - - .. code-block:: php - - // src/AppBundle/Resources/config/services.php - use AppBundle\Form\Type\GenderType; - - $container->register('app.form.type.gender', GenderType::class) - ->addArgument('%genders%') - ->addTag('form.type') - ; - -.. tip:: - - Make sure the services file is being imported. See :ref:`service-container-imports-directive` - for details. - -.. versionadded:: 3.3 - Prior to Symfony 3.3, you needed to define form type services as ``public``. - Starting from Symfony 3.3, you can also define them as ``private``. - -First, add a ``__construct`` method to ``GenderType``, which receives the gender -configuration:: +If you need to access :doc:`services ` from your form class, +just a ``__construct()`` method like normal:: // src/AppBundle/Form/Type/GenderType.php namespace AppBundle\Form\Type; - use Symfony\Component\OptionsResolver\OptionsResolver; - // ... + use Doctrine\ORM\EntityManagerInterface; - // ... class GenderType extends AbstractType { - private $genderChoices; - - public function __construct(array $genderChoices) - { - $this->genderChoices = $genderChoices; - } + private $em; - public function configureOptions(OptionsResolver $resolver) + public function __construct(EntityManagerInterface $em) { - $resolver->setDefaults(array( - 'choices' => $this->genderChoices, - )); + $this->em = $em; } - // ... + // use $this->em down anywhere you want ... } -Great! The ``GenderType`` is now fueled by the configuration parameters and -registered as a service. Because you used the ``form.type`` tag in its configuration, -your service will be used instead of creating a *new* ``GenderType``. In other words, -your controller *does not need to change*, it still looks like this:: - - // src/AppBundle/Form/Type/AuthorType.php - namespace AppBundle\Form\Type; +If you're using the default ``services.yml`` configuration (i.e. services from the +``Form/`` are loaded and ``autoconfigure`` is enabled), this will already work! +See :ref:`service-container-creating-service` for more details. - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use AppBundle\Form\Type\GenderType; +.. tip:: - class AuthorType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('gender_code', GenderType::class, array( - 'placeholder' => 'Choose a gender', - )); - } - } + If you're not using :ref:`autoconfigure `, make sure + to :doc:`tag ` your service with ``form.type``. Have fun! diff --git a/form/create_form_type_extension.rst b/form/create_form_type_extension.rst index a5475c000f4..9b160772a7a 100644 --- a/form/create_form_type_extension.rst +++ b/form/create_form_type_extension.rst @@ -4,48 +4,25 @@ How to Create a Form Type Extension =================================== -:doc:`Custom form field types ` are great when -you need field types with a specific purpose, such as a gender selector, -or a VAT number input. +Form type extensions are *incredibly* powerful: they allow you to *modify* any +existing form field types across the entire system. -But sometimes, you don't really need to add new field types - you want -to add features on top of existing types. This is where form type -extensions come in. +They have 2 main use-cases: -Form type extensions have 2 main use-cases: - -#. You want to add a **specific feature to a single type** (such +#. You want to add a **specific feature to a single form type** (such as adding a "download" feature to the ``FileType`` field type); #. You want to add a **generic feature to several types** (such as adding a "help" text to every "input text"-like type). -It might be possible to achieve your goal with custom form rendering or custom -form field types. But using form type extensions can be cleaner (by limiting the -amount of business logic in templates) and more flexible (you can add several -type extensions to a single form type). - -Form type extensions can achieve most of what custom field types can do, -but instead of being field types of their own, **they plug into existing types**. - -Imagine that you manage a ``Media`` entity, and that each media is associated +Imagine that you have a ``Media`` entity, and that each media is associated to a file. Your ``Media`` form uses a file type, but when editing the entity, you would like to see its image automatically rendered next to the file input. -You could of course do this by customizing how this field is rendered in a -template. But field type extensions allow you to do this in a nice DRY fashion. - Defining the Form Type Extension -------------------------------- -Your first task will be to create the form type extension class (called ``ImageTypeExtension`` -in this article). By standard, form extensions usually live in the ``Form\Extension`` -directory of one of your bundles. - -When creating a form type extension, you can either implement the -:class:`Symfony\\Component\\Form\\FormTypeExtensionInterface` interface -or extend the :class:`Symfony\\Component\\Form\\AbstractTypeExtension` -class. In most cases, it's easier to extend the abstract class:: +First, create the form type extension class:: // src/AppBundle/Form/Extension/ImageTypeExtension.php namespace AppBundle\Form\Extension; @@ -62,19 +39,13 @@ class. In most cases, it's easier to extend the abstract class:: */ public function getExtendedType() { + // use FormType::class to modify (nearly) every field in the system return FileType::class; } } The only method you **must** implement is the ``getExtendedType()`` function. -It is used to indicate the name of the form type that will be extended -by your extension. - -.. tip:: - - The value you return in the ``getExtendedType()`` method corresponds - to the fully qualified class name of the form type class you wish to - extend. +This is used to configure *which* field or field types you want to modify. In addition to the ``getExtendedType()`` function, you will probably want to override one of the following methods: @@ -87,31 +58,29 @@ to override one of the following methods: * ``finishView()`` -For more information on what those methods do, you can refer to the -:doc:`/form/create_custom_field_type` article. +For more information on what those methods do, see the +:ref:`custom form field type ` article. Registering your Form Type Extension as a Service ------------------------------------------------- -The next step is to make Symfony aware of your extension. All you -need to do is to declare it as a service by using the ``form.type_extension`` -tag: +The next step is to make Symfony aware of your extension. Do this by registering +your class as a service and using the ``form.type_extension`` tag: .. configuration-block:: .. code-block:: yaml services: - app.image_type_extension: - class: AppBundle\Form\Extension\ImageTypeExtension + # ... + + AppBundle\Form\Extension\ImageTypeExtension: tags: - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FileType } .. code-block:: xml - + @@ -120,16 +89,16 @@ tag: use AppBundle\Form\Extension\ImageTypeExtension; use Symfony\Component\Form\Extension\Core\Type\FileType; - $container - ->register('app.image_type_extension', ImageTypeExtension::class) + $container->autowire(ImageTypeExtension::class) ->addTag('form.type_extension', array( 'extended_type' => FileType::class )) ; -The ``extended_type`` key of the tag is the type of field that this extension should -be applied to. In your case, as you want to extend the ``Symfony\Component\Form\Extension\Core\Type\FileType`` -field type, you will use that as the ``extended_type``. +The ``extended_type`` key of the tag must match the class you're returning from +the ``getExtendedType()`` method. As *soon* as you do this, any method that you've +overridden (e.g. ``buildForm()``) will be called whenever *any* field of the given +type (``FileType``) is built. Let's see an example next. .. versionadded:: 3.3 Prior to Symfony 3.3, you needed to define type extension services as ``public``. @@ -149,7 +118,7 @@ field type, you will use that as the ``extended_type``. Adding the extension Business Logic ----------------------------------- -The goal of your extension is to display nice images next to file inputs +The goal of your extension is to display a nice image next to file input (when the underlying model contains images). For that purpose, suppose that you use an approach similar to the one described in :doc:`How to handle File Uploads with Doctrine `: @@ -172,11 +141,6 @@ the database:: // ... - /** - * Get the image URL - * - * @return null|string - */ public function getWebPath() { // ... $webPath being the full image URL, to be used in templates @@ -188,15 +152,11 @@ the database:: Your form type extension class will need to do two things in order to extend the ``FileType::class`` form type: -#. Override the ``configureOptions()`` method in order to add an ``image_path`` - option; -#. Override the ``buildView()`` methods in order to pass the image URL to the - view. +#. Override the ``configureOptions()`` method so that any ``FileType`` field can + have an ``image_property`` option; +#. Override the ``buildView()`` methods to pass the image URL to the view. -The logic is the following: when adding a form field of type ``FileType::class``, -you will be able to specify a new option: ``image_path``. This option will -tell the file field how to get the actual image URL in order to display -it in the view:: +For example:: // src/AppBundle/Form/Extension/ImageTypeExtension.php namespace AppBundle\Form\Extension; @@ -210,42 +170,27 @@ it in the view:: class ImageTypeExtension extends AbstractTypeExtension { - /** - * Returns the name of the type being extended. - * - * @return string The name of the type being extended - */ public function getExtendedType() { return FileType::class; } - /** - * Add the image_path option - * - * @param OptionsResolver $resolver - */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefined(array('image_path')); + // makes it legal for FileType fields to have an image_property option + $resolver->setDefined(array('image_property')); } - /** - * Pass the image URL to the view - * - * @param FormView $view - * @param FormInterface $form - * @param array $options - */ public function buildView(FormView $view, FormInterface $form, array $options) { - if (isset($options['image_path'])) { + if (isset($options['image_property'])) { + // this will be whatever class/entity is bound to your form (e.g. Media) $parentData = $form->getParent()->getData(); $imageUrl = null; if (null !== $parentData) { $accessor = PropertyAccess::createPropertyAccessor(); - $imageUrl = $accessor->getValue($parentData, $options['image_path']); + $imageUrl = $accessor->getValue($parentData, $options['image_property']); } // set an "image_url" variable that will be available when rendering this field @@ -262,7 +207,7 @@ Each field type is rendered by a template fragment. Those template fragments can be overridden in order to customize form rendering. For more information, you can refer to the :ref:`form-customization-form-themes` article. -In your extension class, you have added a new variable (``image_url``), but +In your extension class, you added a new variable (``image_url``), but you still need to take advantage of this new variable in your templates. Specifically, you need to override the ``file_widget`` block: @@ -270,7 +215,7 @@ Specifically, you need to override the ``file_widget`` block: .. code-block:: html+twig - {# src/AppBundle/Resources/views/Form/fields.html.twig #} + {# app/Resources/fields.html.twig #} {% extends 'form_div_layout.html.twig' %} {% block file_widget %} @@ -286,24 +231,20 @@ Specifically, you need to override the ``file_widget`` block: .. code-block:: html+php - + widget($form) ?> -.. note:: - - You will need to change your config file or explicitly specify how - you want your form to be themed in order for Symfony to use your overridden - block. See :ref:`form-customization-form-themes` for more - information. +Be sure to :ref:`configure this form theme template ` so that +the form system sees it. Using the Form Type Extension ----------------------------- -From now on, when adding a field of type ``FileType::class`` in your form, you can -specify an ``image_path`` option that will be used to display an image +From now on, when adding a field of type ``FileType::class`` to your form, you can +specify an ``image_property`` option that will be used to display an image next to the file field. For example:: // src/AppBundle/Form/Type/MediaType.php @@ -320,7 +261,7 @@ next to the file field. For example:: { $builder ->add('name', TextType::class) - ->add('file', FileType::class, array('image_path' => 'webPath')); + ->add('file', FileType::class, array('image_property' => 'webPath')); } } @@ -331,14 +272,14 @@ Generic Form Type Extensions ---------------------------- You can modify several form types at once by specifying their common parent -(:doc:`/reference/forms/types`). For example, several form types natively -available in Symfony inherit from the ``TextType`` form type (such as ``EmailType``, -``SearchType``, ``UrlType``, etc.). A form type extension applying to ``TextType`` -(i.e. whose ``getExtendedType()`` method returns ``TextType::class``) would apply -to all of these form types. +(:doc:`/reference/forms/types`). For example, several form types inherit from the +``TextType`` form type (such as ``EmailType``, ``SearchType``, ``UrlType``, etc.). +A form type extension applying to ``TextType`` (i.e. whose ``getExtendedType()`` +method returns ``TextType::class``) would apply to all of these form types. In the same way, since **most** form types natively available in Symfony inherit from the ``FormType`` form type, a form type extension applying to ``FormType`` -would apply to all of these. A notable exception are the ``ButtonType`` form -types. Also keep in mind that a custom form type which extends neither the -``FormType`` nor the ``ButtonType`` type could always be created. +would apply to all of these (notable exceptions are the ``ButtonType`` form +types). Also keep in mind that if you created (or are using) a *custom* form type, +it's possible that it does *not* extend ``FormType``, and so your form type extension +may not be applied to it. diff --git a/form/data_transformers.rst b/form/data_transformers.rst index d25be612166..5c278eed2e0 100644 --- a/form/data_transformers.rst +++ b/form/data_transformers.rst @@ -165,17 +165,17 @@ to and from the issue number and the ``Issue`` object:: namespace AppBundle\Form\DataTransformer; use AppBundle\Entity\Issue; - use Doctrine\Common\Persistence\ObjectManager; + use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; class IssueToNumberTransformer implements DataTransformerInterface { - private $manager; + private $em; - public function __construct(ObjectManager $manager) + public function __construct(EntityManagerInterface $em) { - $this->manager = $manager; + $this->em = $em; } /** @@ -207,7 +207,7 @@ to and from the issue number and the ``Issue`` object:: return; } - $issue = $this->manager + $issue = $this->em ->getRepository('AppBundle:Issue') // query for the issue with this id ->find($issueNumber) @@ -246,29 +246,25 @@ that message with the ``invalid_message`` option (see below). Using the Transformer ~~~~~~~~~~~~~~~~~~~~~ -Next, you need to instantiate the ``IssueToNumberTransformer`` class from inside -``TaskType`` and add it to the ``issue`` field. But to do that, you'll need an instance -of the entity manager (because ``IssueToNumberTransformer`` needs this). - -No problem! Just add a ``__construct()`` function to ``TaskType`` and force this -to be passed in by registering ``TaskType`` as a service:: +Next, you need to use the ``IssueToNumberTransformer`` object inside if ``TaskType`` +and add it to the ``issue`` field. No problem! Just add a ``__construct()`` method +and type-hint the new class:: // src/AppBundle/Form/TaskType.php namespace AppBundle\Form\Type; use AppBundle\Form\DataTransformer\IssueToNumberTransformer; - use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; // ... class TaskType extends AbstractType { - private $manager; + private $transformer; - public function __construct(ObjectManager $manager) + public function __construct(IssueToNumberTransformer $transformer) { - $this->manager = $manager; + $this->transformer = $transformer; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -283,66 +279,21 @@ to be passed in by registering ``TaskType`` as a service:: // ... $builder->get('issue') - ->addModelTransformer(new IssueToNumberTransformer($this->manager)); + ->addModelTransformer($this->transformer); } // ... } -Define the form type as a service in your configuration files. - -.. configuration-block:: - - .. code-block:: yaml - - # src/AppBundle/Resources/config/services.yml - services: - app.form.type.task: - class: AppBundle\Form\Type\TaskType - arguments: ["@doctrine.orm.entity_manager"] - tags: [form.type] - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // src/AppBundle/Resources/config/services.php - use AppBundle\Form\Type\TaskType; - - $definition = new Definition(TaskType::class, array( - new Reference('doctrine.orm.entity_manager'), - )); - $container - ->setDefinition( - 'app.form.type.task', - $definition - ) - ->addTag('form.type') - ; +That's it! As long as you're using :ref:`autowire ` and +:ref:`autoconfigure `, Symfony will automatically +know to pass your ``TaskType`` an instance of the ``IssueToNumberTransformer``. .. tip:: For more information about defining form types as services, read :doc:`register your form type as a service `. -.. versionadded:: 3.3 - Prior to Symfony 3.3, you needed to define form type services as ``public``. - Starting from Symfony 3.3, you can also define them as ``private``. - Now, you can easily use your ``TaskType``:: // e.g. in a controller somewhere @@ -392,17 +343,16 @@ First, create the custom field type class:: class IssueSelectorType extends AbstractType { - private $manager; + private $transformer; - public function __construct(ObjectManager $manager) + public function __construct(IssueToNumberTransformer $transformer) { - $this->manager = $manager; + $this->transformer = $transformer; } public function buildForm(FormBuilderInterface $builder, array $options) { - $transformer = new IssueToNumberTransformer($this->manager); - $builder->addModelTransformer($transformer); + $builder->addModelTransformer($this->transformer); } public function configureOptions(OptionsResolver $resolver) @@ -421,52 +371,8 @@ First, create the custom field type class:: Great! This will act and render like a text field (``getParent()``), but will automatically have the data transformer *and* a nice default value for the ``invalid_message`` option. -Next, register your type as a service and tag it with ``form.type`` so that -it's recognized as a custom field type: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - app.type.issue_selector: - class: AppBundle\Form\IssueSelectorType - arguments: ['@doctrine.orm.entity_manager'] - tags: [form.type] - - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // app/config/services.php - use AppBundle\Form\IssueSelectorType; - use Symfony\Component\DependencyInjection\Reference; - // ... - - $container->register('app.type.issue_selector', IssueSelectorType::class) - ->addArgument(new Reference('doctrine.orm.entity_manager')) - ->addTag('form.type'); - -Now, whenever you need to use your special ``issue_selector`` field type, -it's quite easy:: +As long as you're using :ref:`autowire ` and +:ref:`autoconfigure `, you can start using the form immediately:: // src/AppBundle/Form/TaskType.php namespace AppBundle\Form\Type; @@ -488,6 +394,11 @@ it's quite easy:: // ... } +.. tip:: + + If you're not using ``autowire`` and ``autoconfigure``, see + :doc:`` for how to configure your new ``IssueSelectorType``. + .. _model-and-view-transformers: About Model and View Transformers diff --git a/form/direct_submit.rst b/form/direct_submit.rst index ca8678400d6..855043affc5 100644 --- a/form/direct_submit.rst +++ b/form/direct_submit.rst @@ -24,7 +24,7 @@ submissions:: return $this->redirectToRoute('task_success'); } - return $this->render('AppBundle:Default:new.html.twig', array( + return $this->render('product/new.html.twig', array( 'form' => $form->createView(), )); } @@ -63,7 +63,7 @@ method, pass the submitted data directly to } } - return $this->render('AppBundle:Default:new.html.twig', array( + return $this->render('product/new.html.twig', array( 'form' => $form->createView(), )); } diff --git a/form/dynamic_form_modification.rst b/form/dynamic_form_modification.rst index d948f567217..a618d97ed1c 100644 --- a/form/dynamic_form_modification.rst +++ b/form/dynamic_form_modification.rst @@ -142,27 +142,6 @@ For better reusability or if there is some heavy logic in your event listener, you can also move the logic for creating the ``name`` field to an :ref:`event subscriber `:: - // src/AppBundle/Form/Type/ProductType.php - namespace AppBundle\Form\Type; - - // ... - use AppBundle\Form\EventListener\AddNameFieldSubscriber; - - class ProductType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('price'); - - $builder->addEventSubscriber(new AddNameFieldSubscriber()); - } - - // ... - } - -Now the logic for creating the ``name`` field resides in it own subscriber -class:: - // src/AppBundle/Form/EventListener/AddNameFieldSubscriber.php namespace AppBundle\Form\EventListener; @@ -191,6 +170,25 @@ class:: } } +Great! Now use that in your form class:: + + // src/AppBundle/Form/Type/ProductType.php + namespace AppBundle\Form\Type; + + // ... + use AppBundle\Form\EventListener\AddNameFieldSubscriber; + + class ProductType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('price'); + + $builder->addEventSubscriber(new AddNameFieldSubscriber()); + } + + // ... + } .. _form-events-user-data: @@ -332,46 +330,15 @@ and fill in the listener logic:: Using the Form ~~~~~~~~~~~~~~ -Our form is now ready to use. But first, because it has a ``__construct()`` method, -you need to register it as a service and tag it with :ref:`form.type `: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - services: - app.form.friend_message: - class: AppBundle\Form\Type\FriendMessageFormType - arguments: ['@security.token_storage'] - tags: [form.type] - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config.php - use AppBundle\Form\Type\FriendMessageFormType; - use Symfony\Component\DependencyInjection\Reference; +If you're using :ref:`autowire ` and +:ref:`autoconfigure `, your form is ready to be used! - $container->register('app.form.friend_message', FriendMessageFormType::class) - ->addArgument(new Reference('security.token_storage')) - ->addTag('form.type'); +.. tip:: -.. versionadded:: 3.3 - Prior to Symfony 3.3, you needed to define form type services as ``public``. - Starting from Symfony 3.3, you can also define them as ``private``. + If you're not using autowire and autoconfigure, see :doc:`` + for how to register your form type as a service. -In a controller that extends the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` -class, you can simply call:: +In a controller, create the form like normal:: use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -385,7 +352,7 @@ class, you can simply call:: } } -You can also easily embed the form type into another form:: +You can also embed the form type into another form:: // inside some other "form type" class public function buildForm(FormBuilderInterface $builder, array $options) diff --git a/form/form_collections.rst b/form/form_collections.rst index b1e37a58345..76dc3f72b7b 100644 --- a/form/form_collections.rst +++ b/form/form_collections.rst @@ -678,11 +678,11 @@ the relationship between the removed ``Tag`` and ``Task`` object. // src/AppBundle/Controller/TaskController.php use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\ORM\EntityManagerInterface; // ... - public function editAction($id, Request $request) + public function editAction($id, Request $request, EntityManagerInterface $e,) { - $em = $this->getDoctrine()->getManager(); $task = $em->getRepository('AppBundle:Task')->find($id); if (!$task) { diff --git a/form/form_dependencies.rst b/form/form_dependencies.rst index 8389e224f21..5ba9e2b4a6a 100644 --- a/form/form_dependencies.rst +++ b/form/form_dependencies.rst @@ -71,19 +71,19 @@ Alternatively, you can define your form class as a service. This is a good idea you want to re-use the form in several places - registering it as a service makes this easier. -Suppose you need to access the ``doctrine.orm.entity_manager`` service so that you +Suppose you need to access the ``EntityManager`` service so that you can make a query. First, add this as an argument to your form class:: // src/AppBundle/Form/TaskType.php - use Doctrine\ORM\EntityManager; + use Doctrine\ORM\EntityManagerInterface; // ... class TaskType extends AbstractType { private $em; - public function __construct(EntityManager $em) + public function __construct(EntityManagerInterface $em) { $this->em = $em; } @@ -91,7 +91,13 @@ can make a query. First, add this as an argument to your form class:: // ... } -Next, register this as a service and tag it with ``form.type``: +If you're using :ref:`autowire ` and +:ref:`autoconfigure `, then you don't need to do *anything* +else: Symfony will automatically know to pass the entity manager service to your +form type. + +If you are **not using autowire and autoconfigure**, register your form as a service +manually and tag it with ``form.type``: .. configuration-block:: @@ -99,8 +105,7 @@ Next, register this as a service and tag it with ``form.type``: # src/AppBundle/Resources/config/services.yml services: - app.form.type.task: - class: AppBundle\Form\TaskType + AppBundle\Form\TaskType: arguments: ['@doctrine.orm.entity_manager'] tags: [form.type] @@ -113,7 +118,7 @@ Next, register this as a service and tag it with ``form.type``: xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -126,7 +131,7 @@ Next, register this as a service and tag it with ``form.type``: use AppBundle\Form\TaskType; use Symfony\Component\DependencyInjection\Reference; - $container->register('app.form.type.task', TaskType::class) + $container->register(TaskType::class) ->addArgument(new Reference('doctrine.orm.entity_manager')) ->addTag('form.type') ; diff --git a/form/type_guesser.rst b/form/type_guesser.rst index 36d37378952..df69e998c36 100644 --- a/form/type_guesser.rst +++ b/form/type_guesser.rst @@ -36,9 +36,7 @@ This interface requires four methods: :method:`Symfony\\Component\\Form\\FormTypeGuesserInterface::guessPattern` Tries to guess the value of the ``pattern`` input attribute. -Start by creating the class and these methods. Next, you'll learn how to fill each on. - -.. code-block:: php +Start by creating the class and these methods. Next, you'll learn how to fill each in:: // src/AppBundle/Form/TypeGuesser/PHPDocTypeGuesser.php namespace AppBundle\Form\TypeGuesser; @@ -175,9 +173,12 @@ set. Registering a Type Guesser -------------------------- +If you're using :ref:`autowire ` and +:ref:`autoconfigure `, you're done! Symfony already knows +and is using your form type guesser. -The last thing you need to do is registering your custom type guesser by -creating a service and tagging it as ``form.type_guesser``: +If you're **not** using autowire and autoconfigure, register your service manually +and tag it with ``form.type_guesser``: .. configuration-block:: @@ -185,9 +186,9 @@ creating a service and tagging it as ``form.type_guesser``: # app/config/services.yml services: + # ... - app.phpdoc_type_guesser: - class: AppBundle\Form\TypeGuesser\PHPDocTypeGuesser + AppBundle\Form\TypeGuesser\PHPDocTypeGuesser: tags: [form.type_guesser] .. code-block:: xml @@ -200,7 +201,7 @@ creating a service and tagging it as ``form.type_guesser``: > - + @@ -212,7 +213,7 @@ creating a service and tagging it as ``form.type_guesser``: // app/config/services.php use AppBundle\Form\TypeGuesser\PHPDocTypeGuesser; - $container->register('app.phpdoc_type_guesser', PHPDocTypeGuesser::class) + $container->register(PHPDocTypeGuesser::class) ->addTag('form.type_guesser') ; diff --git a/frontend/custom_version_strategy.rst b/frontend/custom_version_strategy.rst index 2b402372603..940e258b3e8 100644 --- a/frontend/custom_version_strategy.rst +++ b/frontend/custom_version_strategy.rst @@ -114,8 +114,7 @@ After creating the strategy PHP class, register it as a Symfony service. # app/config/services.yml services: - app.assets.versioning.gulp_buster: - class: AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy + AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy: arguments: - "%kernel.project_dir%/busters.json" - "%%s?version=%%s" @@ -131,8 +130,7 @@ After creating the strategy PHP class, register it as a Symfony service. http://symfony.com/schema/dic/services/services-1.0.xsd" > - + %kernel.project_dir%/busters.json %%s?version=%%s @@ -143,17 +141,15 @@ After creating the strategy PHP class, register it as a Symfony service. // app/config/services.php use Symfony\Component\DependencyInjection\Definition; + use AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy; - $definition = new Definition( - 'AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy', - array( - '%kernel.project_dir%/busters.json', - '%%s?version=%%s', - ) - ); - $definition->setPublic(false); - - $container->setDefinition('app.assets.versioning.gulp_buster', $definition); + $container->autowire(GulpBusterVersionStrategy::class) + ->setArguments( + array( + '%kernel.project_dir%/busters.json', + '%%s?version=%%s', + ) + )->setPublic(false); Finally, enable the new asset versioning for all the application assets or just for some :ref:`asset package ` thanks to @@ -167,7 +163,7 @@ the :ref:`version_strategy ` option: framework: # ... assets: - version_strategy: 'app.assets.versioning.gulp_buster' + version_strategy: 'AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy' .. code-block:: xml @@ -180,17 +176,19 @@ the :ref:`version_strategy ` option: http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + .. code-block:: php // app/config/config.php + use AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy; + $container->loadFromExtension('framework', array( // ... 'assets' => array( - 'version_strategy' => 'app.assets.versioning.gulp_buster', + 'version_strategy' => GulpBusterVersionStrategy::class, ), )); diff --git a/introduction/from_flat_php_to_symfony2.rst b/introduction/from_flat_php_to_symfony2.rst index e1ca1af8f71..15501cb15a1 100644 --- a/introduction/from_flat_php_to_symfony2.rst +++ b/introduction/from_flat_php_to_symfony2.rst @@ -545,23 +545,22 @@ them for you. Here's the same sample application, now built in Symfony:: namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Doctrine\ORM\EntityManagerInterface; class BlogController extends Controller { - public function listAction() + public function listAction(EntityManagerInterface $em) { - $posts = $this->get('doctrine') - ->getManager() + $posts = $em ->createQuery('SELECT p FROM AppBundle:Post p') ->execute(); return $this->render('Blog/list.html.php', array('posts' => $posts)); } - public function showAction($id) + public function showAction(EntityManagerInterface $em) { - $post = $this->get('doctrine') - ->getManager() + $post = $em ->getRepository('AppBundle:Post') ->find($id); diff --git a/logging/formatter.rst b/logging/formatter.rst index bcbd3f95395..cb85ede0242 100644 --- a/logging/formatter.rst +++ b/logging/formatter.rst @@ -7,24 +7,30 @@ it. All Monolog handlers use an instance of easily. Your formatter must implement ``Monolog\Formatter\FormatterInterface``. +For example, to use the built-in ``JsonFormatter``, register it as a service then +configure your handler to use it: + .. configuration-block:: .. code-block:: yaml - # app/config/config.yml + # app/config/services.yml services: - my_formatter: - class: Monolog\Formatter\JsonFormatter + # ... + + Monolog\Formatter\JsonFormatter: ~ + + # app/config/config_prod.yml (and/or config_dev.yml) monolog: handlers: file: type: stream level: debug - formatter: my_formatter + formatter: Monolog\Formatter\JsonFormatter .. code-block:: xml - + - + + @@ -53,14 +60,16 @@ easily. Your formatter must implement // app/config/config.php use Monolog\Formatter\JsonFormatter; - $container->register('my_formatter', JsonFormatter::class); + // app/config/services.php + $container->register(JsonFormatter::class); + // app/config/config_prod.php (or config_dev.php) $container->loadFromExtension('monolog', array( 'handlers' => array( 'file' => array( 'type' => 'stream', 'level' => 'debug', - 'formatter' => 'my_formatter', + 'formatter' => JsonFormatter::class', ), ), )); diff --git a/logging/monolog_console.rst b/logging/monolog_console.rst index e7f6fc06855..19296270078 100644 --- a/logging/monolog_console.rst +++ b/logging/monolog_console.rst @@ -113,7 +113,7 @@ information): verbosity_levels: VERBOSITY_NORMAL: NOTICE channels: my_channel - formatter: my_formatter + formatter: Symfony\Bridge\Monolog\Formatter\ConsoleFormatter .. code-block:: xml @@ -125,7 +125,7 @@ information): - my_channel + Symfony\Bridge\Monolog\Formatter\ConsoleFormatter @@ -133,6 +133,8 @@ information): .. code-block:: php // app/config/config.php + use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; + $container->loadFromExtension('monolog', array( 'handlers' => array( 'console' => array( @@ -141,7 +143,7 @@ information): 'VERBOSITY_NORMAL' => 'NOTICE', ), 'channels' => 'my_channel', - 'formatter' => 'my_formatter', + 'formatter' => ConsoleFormatter::class, ), ), )); @@ -152,8 +154,7 @@ information): # app/config/services.yml services: - my_formatter: - class: Symfony\Bridge\Monolog\Formatter\ConsoleFormatter + Symfony\Bridge\Monolog\Formatter\ConsoleFormatter: arguments: - "[%%datetime%%] %%start_tag%%%%message%%%%end_tag%% (%%level_name%%) %%context%% %%extra%%\n" @@ -166,7 +167,7 @@ information): xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + [%%datetime%%] %%start_tag%%%%message%%%%end_tag%% (%%level_name%%) %%context%% %%extra%%\n @@ -179,7 +180,7 @@ information): use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; $container - ->register('my_formatter', ConsoleFormatter::class) + ->autowire(ConsoleFormatter::class) ->addArgument('[%%datetime%%] %%start_tag%%%%message%%%%end_tag%% (%%level_name%%) %%context%% %%extra%%\n') ; diff --git a/logging/processors.rst b/logging/processors.rst index 15022af1ea3..750ebf40fd9 100644 --- a/logging/processors.rst +++ b/logging/processors.rst @@ -20,14 +20,14 @@ using a processor. namespace AppBundle; - use Symfony\Component\HttpFoundation\Session\Session; + use Symfony\Component\HttpFoundation\Session\SessionInterface; class SessionRequestProcessor { private $session; private $token; - public function __construct(Session $session) + public function __construct(SessionInterface $session) { $this->session = $session; } @@ -48,34 +48,28 @@ using a processor. } } +Next, register your class as a service, as well as a formatter that uses the extra +information: + .. configuration-block:: .. code-block:: yaml - # app/config/config.yml + # app/config/services.yml services: monolog.formatter.session_request: class: Monolog\Formatter\LineFormatter arguments: - "[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%%\n" - monolog.processor.session_request: - class: AppBundle\SessionRequestProcessor - arguments: ['@session'] + AppBundle\SessionRequestProcessor: + autowire: true tags: - { name: monolog.processor, method: processRecord } - monolog: - handlers: - main: - type: stream - path: '%kernel.logs_dir%/%kernel.environment%.log' - level: debug - formatter: monolog.formatter.session_request - .. code-block:: xml - + [%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%% - - - + + + + .. code-block:: php + + // app/config/services.php + use AppBundle\SessionRequestProcessor; + use Monolog\Formatter\LineFormatter; + + $container + ->register('monolog.formatter.session_request', LineFormatter::class) + ->addArgument('[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%%\n'); + + $container + ->autowire(SessionRequestProcessor::class) + ->addTag('monolog.processor', array('method' => 'processRecord')); + +Finally, set the formatter to be used on whatever handler you want: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + monolog: + handlers: + main: + type: stream + path: '%kernel.logs_dir%/%kernel.environment%.log' + level: debug + formatter: monolog.formatter.session_request + + .. code-block:: xml + + + + register('monolog.formatter.session_request', LineFormatter::class) - ->addArgument('[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%%\n'); - - $container - ->register('monolog.processor.session_request', SessionRequestProcessor::class) - ->addArgument(new Reference('session')) - ->addTag('monolog.processor', array('method' => 'processRecord')); - $container->loadFromExtension('monolog', array( 'handlers' => array( 'main' => array( @@ -137,11 +158,9 @@ using a processor. ), )); -.. note:: - - If you use several handlers, you can also register a processor at the - handler level or at the channel level instead of registering it globally - (see the following sections). +If you use several handlers, you can also register a processor at the +handler level or at the channel level instead of registering it globally +(see the following sections). Registering Processors per Handler ---------------------------------- @@ -155,9 +174,8 @@ the ``monolog.processor`` tag: # app/config/config.yml services: - monolog.processor.session_request: - class: AppBundle\SessionRequestProcessor - arguments: ['@session'] + AppBundle\SessionRequestProcessor: + autowire: true tags: - { name: monolog.processor, method: processRecord, handler: main } @@ -174,10 +192,7 @@ the ``monolog.processor`` tag: http://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> - - - + @@ -189,11 +204,7 @@ the ``monolog.processor`` tag: // ... $container - ->register( - 'monolog.processor.session_request', - SessionRequestProcessor::class - ) - ->addArgument(new Reference('session')) + ->autowire(SessionRequestProcessor::class) ->addTag('monolog.processor', array('method' => 'processRecord', 'handler' => 'main')); Registering Processors per Channel @@ -208,9 +219,8 @@ the ``monolog.processor`` tag: # app/config/config.yml services: - monolog.processor.session_request: - class: AppBundle\SessionRequestProcessor - arguments: ['@session'] + AppBundle\SessionRequestProcessor: + autowire: true tags: - { name: monolog.processor, method: processRecord, channel: main } @@ -227,10 +237,7 @@ the ``monolog.processor`` tag: http://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> - - - + @@ -242,6 +249,5 @@ the ``monolog.processor`` tag: // ... $container - ->register('monolog.processor.session_request', SessionRequestProcessor::class) - ->addArgument(new Reference('session')) + ->autowire(SessionRequestProcessor::class) ->addTag('monolog.processor', array('method' => 'processRecord', 'channel' => 'main')); diff --git a/service_container.rst b/service_container.rst index b702ef0a223..7a3287e35ee 100644 --- a/service_container.rst +++ b/service_container.rst @@ -5,8 +5,8 @@ Service Container ================= -Your application is *full* of useful objects: one "Mailer" object might help you -send email messages while another object might help you save things to the database. +Your application is *full* of useful objects: a "Mailer" object might help you +send emails while another object might help you save things to the database. Almost *everything* that your app "does" is actually done by one of these objects. And each time you install a new bundle, you get access to even more! @@ -15,7 +15,7 @@ a very special object called the **service container**. If you have the service then you can fetch a service by using that service's id:: $logger = $container->get('logger'); - $entityManager = $container->get('doctrine.entity_manager'); + $entityManager = $container->get('doctrine.orm.entity_manager'); The container allows you to centralize the way objects are constructed. It makes your life easier, promotes a strong architecture and is super fast! @@ -24,7 +24,7 @@ Fetching and using Services --------------------------- The moment you start a Symfony app, your container *already* contains many services. -These are like *tools*, waiting for you to take advantage of them. In your controller, +These are like *tools*: waiting for you to take advantage of them. In your controller, you can "ask" for a service from the container by type-hinting an argument with the service's class or interface name. Want to :doc:`log ` something? No problem:: @@ -45,6 +45,8 @@ service's class or interface name. Want to :doc:`log ` something? No p .. versionadded:: 3.3 The ability to type-hint a service in order to receive it was added in Symfony 3.3. + See the :ref:`controller chapter ` for more + details. .. _container-debug-container: @@ -138,85 +140,94 @@ it can't be re-used. Instead, you decide to create a new class:: } } -Congratulations! You've just created your first service class. Next, you can *teach* -the service container *how* to instantiate it: +Congratulations! You've just created your first service class! You can use it immediately +inside your controller:: -.. _service-container-services-load-example: + use AppBundle\Service\MessageGenerator; -.. configuration-block:: + public function newAction(MessageGenerator $messageGenerator) + { + // thanks to the type-hint, the container will instantiate a + // new MessageGenerator and pass it to you! + // ... - .. code-block:: yaml + $message = $messageGenerator->getHappyMessage(); + $this->addFlash('success', $message); + // ... + } - # app/config/services.yml - services: - # default configuration for services in *this* file - _defaults: - autowire: true - autoconfigure: true - public: false +When you ask for the ``MessageGenerator`` service, the container constructs a new +``MessageGenerator`` object and returns it (see sidebar below). But if you never ask +for the service, it's *never* constructed: saving memory and speed. As a bonus, the +``MessageGenerator`` service is only created *once*: the same instance is returned +each time you ask for it. - # loads services from whatever directories you want (you can update this!) - AppBundle\: - resource: '../../src/AppBundle/{Service,Command,Form,EventSubscriber,Twig,Security}' +.. _service-container-services-load-example: - .. code-block:: xml +.. sidebar:: Automatic Service Loading in services.yml - - - + The documentation assumes you're using + `Symfony Standard Edition (version 3.3) services.yml`_ configuration. The most + important part is this: - - - + .. configuration-block:: - - - - + .. code-block:: yaml - .. code-block:: php + # app/config/services.yml + services: + # default configuration for services in *this* file + _defaults: + autowire: true + autoconfigure: true + public: false - // app/config/services.php - // _defaults and loading entire directories is not possible with PHP configuration - // you need to define your servicess one-by-one - use AppBundle\Service\MessageGenerator; + # makes classes in src/AppBundle available to be used as services + AppBundle\: + resource: '../../src/AppBundle/*' + # you can exclude directories or files + # but if a service is unused, it's removed anyway + exclude: '../../src/AppBundle/{Entity,Repository}' - $container->autowire(MessageGenerator::class) - ->setAutoconfigured(true) - ->setPublic(false); + .. code-block:: xml -.. versionadded:: 3.3 - The ``_defaults`` key and ability to load services from a directory were added - in Symfony 3.3. + + + -That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, a service -will be registered for each class in the ``src/AppBundle/Service`` directory (and -the other directories listed). + + + -You can use it immediately inside your controller:: + + + + - use AppBundle\Service\MessageGenerator; + .. code-block:: php - public function newAction(MessageGenerator $messageGenerator) - { - // thanks to the type-hint, the container will instantiate a - // new MessageGenerator and pass it to you! - // ... + // app/config/services.php + // _defaults and loading entire directories is not possible with PHP configuration + // you need to define your services one-by-one + use AppBundle\Service\MessageGenerator; - $message = $messageGenerator->getHappyMessage(); - $this->addFlash('success', $message); - // ... - } + $container->autowire(MessageGenerator::class) + ->setAutoconfigured(true) + ->setPublic(false); -When you ask for the ``MessageGenerator`` service, the container constructs a new -``MessageGenerator`` object and returns it. But if you never ask for the service, -it's *never* constructed: saving memory and speed. + Thanks to this configuration, you can automatically use any classes from the + ``src/AppBundle`` directory as a service, without needing to manually configure + it. Later, you'll learn more about this later in :ref:`service-psr4-loader`. + + If you'd prefer to manually wire your service, that's totally possible: see + :ref:`services-explicitly-configure-wire-services`. -As a bonus, the ``MessageGenerator`` service is only created *once*: the same -instance is returned each time you ask for it. + .. versionadded:: 3.3 + The ``_defaults`` key *and* ability to load services from a directory were added + in Symfony 3.3. You can also fetch a service directly from the container via its "id", which will be its class name in this case:: @@ -228,6 +239,7 @@ be its class name in this case:: { public function newAction() { + // only works if your service is public $messageGenerator = $this->get(MessageGenerator::class); $message = $messageGenerator->getHappyMessage(); @@ -254,7 +266,8 @@ Your service does *not* have access to the container directly, so you can't fetc it via ``$this->container->get()``. No problem! Instead, create a ``__construct()`` method with a ``$logger`` argument -that has the ``LoggerInterface`` type-hint:: +that has the ``LoggerInterface`` type-hint. Set this on a new ``$logger`` property +and use it later:: // src/AppBundle/Service/MessageGenerator.php // ... @@ -279,22 +292,34 @@ that has the ``LoggerInterface`` type-hint:: That's it! The container will *automatically* know to pass the ``logger`` service when instantiating the ``MessageGenerator``. How does it know to do this? -:doc:`Autowiring `. The key is the ``LoggerInterface`` +:ref:`Autowiring `. The key is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the ``autowire: true`` config in ``services.yml``. When you type-hint an argument, the container will automatically find the matching service. If it can't, you'll see a clear exception with a helpful suggestion. -Be sure to read more about :doc:`autowiring `. - .. _services-debug-container-types: -.. tip:: +How should you know to use ``LoggerInterface`` for the type-hint? You can either +read the docs for whatever feature you're using, or get a list of autowireable +type-hints by running: + +.. code-block:: terminal + + $ php bin/console debug:container --types + +This is just a small subset of the output: - How should you know to use ``LoggerInterface`` for the type-hint? The best way - is by reading the docs for whatever feature you're using. You can also use the - ``php bin/console debug:container --types`` console command to get a list of - available type-hints. +=============================================================== ======================================================================= +Service ID Class name +=============================================================== ======================================================================= +``Psr\Cache\CacheItemPoolInterface`` alias for "cache.app.recorder" +``Psr\Log\LoggerInterface`` alias for "monolog.logger" +``Symfony\Component\EventDispatcher\EventDispatcherInterface`` alias for "debug.event_dispatcher" +``Symfony\Component\HttpFoundation\RequestStack`` alias for "request_stack" +``Symfony\Component\HttpFoundation\Session\SessionInterface`` alias for "session" +``Symfony\Component\Routing\RouterInterface`` alias for "router.default" +=============================== ======================================================================= Handling Multiple Services ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -335,40 +360,9 @@ made. To do that, you create a new class:: } } -This uses the ``MessageGenerator`` *and* the ``Swift_Mailer`` service. To register -this as a new service in the container, simply tell your configuration to load from -the new ``Updates`` sub-directory: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - # ... - - # registers all classes in Services & Updates directories - AppBundle\: - resource: '../../src/AppBundle/{Service,Updates,Command,Form,EventSubscriber,Twig,Security}' - - .. code-block:: xml - - - - - - - - - - - - - -Now, you can use the service immediately:: +This uses the ``MessageGenerator`` *and* the ``Swift_Mailer`` service. As long as +you're :ref:`loading all services from src/AppBundle `, +you can use the service immediately:: use AppBundle\Updates\SiteUpdateManager; @@ -385,6 +379,8 @@ Thanks to autowiring and your type-hints in ``__construct()``, the container cre the ``SiteUpdateManager`` object and passes it the correct argument. In most cases, this works perfectly. +.. _services-manually-wire-args: + Manually Wiring Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -440,7 +436,8 @@ pass here. No problem! In your configuration, you can explicitly set this argume # same as before AppBundle\: - resource: '../../src/AppBundle/{Service,Updates,Command,Form,EventSubscriber,Twig,Security}' + resource: '../../src/AppBundle/*' + exclude: '../../src/AppBundle/{Entity,Repository}' # explicitly configure the service AppBundle\Updates\SiteUpdateManager: @@ -460,11 +457,11 @@ pass here. No problem! In your configuration, you can explicitly set this argume - + - %admin_email% + manager@example.com @@ -484,7 +481,7 @@ pass here. No problem! In your configuration, you can explicitly set this argume .. versionadded:: 3.3 The ability to configure an argument by its name (``$adminEmail``) was added in Symfony 3.3. Previously, you could configure it only by its index (``2`` in - this case). + this case) or by using empty quotes for the other arguments. Thanks to this, the container will pass ``manager@example.com`` as the third argument to ``__construct`` when creating the ``SiteUpdateManager`` service. The other arguments @@ -563,23 +560,10 @@ You can also fetch parameters directly from the container:: // $adminEmail = $this->getParameter('admin_email'); } -.. note:: - - If you use a string that starts with ``@`` or ``%``, you need to escape it by - adding another ``@`` or ``%``: - - .. code-block:: yaml - - # app/config/parameters.yml - parameters: - # This will be parsed as string '@securepass' - mailer_password: '@@securepass' - - # Parsed as http://symfony.com/?foo=%s&bar=%d - url_pattern: 'http://symfony.com/?foo=%%s&bar=%%d' - For more info about parameters, see :doc:`/service_container/parameters`. +.. _services-wire-specific-service: + Choose a Specific Service ------------------------- @@ -662,6 +646,19 @@ service whose id is ``monolog.logger.request``. the *service* whose id is ``monolog.logger.request``, and not just the *string* ``monolog.logger.request``. +.. _services-autowire: + +The autowire Option +------------------- + +Above, the ``services.yml`` file has ``autowire: true`` in the ``_defaults`` section +so that it applies to all services defined in that file. With this setting, you're +able to type-hint arguments in the ``__construct()`` method of your services and +the container will automatically pass you the correct arguments. This entire entry +has been written around autowiring. + +For more details about autowiring, check out :doc:`/service_container/autowiring`. + .. _services-autoconfigure: The autoconfigure Option @@ -670,10 +667,10 @@ The autoconfigure Option .. versionadded:: 3.3 The ``autoconfigure`` option was added in Symfony 3.3. -Above, we've set ``autoconfigure: true`` in the ``_defaults`` section so that it -applies to all services defined in that file. With this setting, the container will -automatically apply certain configuration to your services, based on your service's -*class*. This is mostly used to *auto-tag* your services. +Above, the ``services.yml`` file has ``autoconfigure: true`` in the ``_defaults`` +section so that it applies to all services defined in that file. With this setting, +the container will automatically apply certain configuration to your services, based +on your service's *class*. This is mostly used to *auto-tag* your services. For example, to create a Twig Extension, you need to create a class, register it as a service, and :doc:`tag ` it with ``twig.extension``: @@ -715,53 +712,15 @@ as a service, and :doc:`tag ` it with ``twig.extension` $container->autowire(MyTwigExtension::class) ->addTag('twig.extension'); -But, with ``autoconfigure: true``, you don't need the tag. In fact, all you need -to do is load your service from the ``Twig`` directory, which is already loaded -by default in a fresh Symfony install: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - _defaults: - # ... - autoconfigure: true - - # load your services from the Twig directory - AppBundle\: - resource: '../../src/AppBundle/{Service,Updates,Command,Form,EventSubscriber,Twig,Security}' - - .. code-block:: xml +But, with ``autoconfigure: true``, you don't need the tag. In fact, if you're using +the :ref:`Symfony Standard Edition services.yml config `, +you don't need to do *anything*: the service will be automatically loaded. Then, +``autoconfigure`` will add the ``twig.extension`` tag *for* you, because your class +implements ``Twig_ExtensionInterface``. And thanks to ``autowire``, you can even add +constructor arguments without any configuration. - - - - - - - - - - - - - .. code-block:: php - - // app/config/services.php - use AppBundle\Twig\MyTwigExtension; - - $container->autowire(MyTwigExtension::class) - ->setAutoconfigure(true); - -That's it! The container will find your class in the ``Twig/`` directory and register -it as a service. Then ``autoconfigure`` will add the ``twig.extension`` tag *for* -you, because your class implements ``Twig_ExtensionInterface``. And thanks to ``autowire``, -you can even add constructor arguments without any configuration. +Of course, you can still :ref:`manually configure the service ` +if you need to. .. _container-public: @@ -842,6 +801,182 @@ need to make your service public, just override this setting: +.. _service-psr4-loader: + +Importing Many Services at once with resource +--------------------------------------------- + +You've already seen that you can import many services at once by using the ``resource`` +key. For example, the default Symfony configuration contains this: + + .. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... + + # the namespace prefix for classes (must end in \) + AppBundle\: + # accepts a glob pattern + resource: '../../src/AppBundle/*' + # exclude some paths + exclude: '../../src/AppBundle/{Entity,Repository}' + + # these were imported above, but we want to add some extra config + AppBundle\Controller\: + resource: '../../src/AppBundle/Controller' + # apply some configuration to these services + public: true + tags: ['controller.service_arguments'] + + .. code-block:: xml + + + + + + + + + + + + + + + + +This can be used to quickly make many classes available as services and apply some +default configuration. The ``id`` of each service is its fully-qualified class name. +You can override any service that's imported by using its id (class name) below +(e.g. see :ref:`services-manually-wire-args`). + +.. note:: + + Wait, does this mean that *every* class in ``src/AppBundle`` is registered as + a service? Even model or entity classes? Actually, no. As long as you have + ``public: false`` under your ``_defaults`` key (or you can add it under the + specific import), all the imported services are *private*. Thanks to this, all + classes in ``src/AppBundle`` that are *not* explicitly used as services are + automatically removed from the final container. In reality, the import simply + means that all classes are "available to be *used* as services" without needing + to be manually configured. + +.. _services-explicitly-configure-wire-services: + +Explicitly Configuring Services and Arguments +--------------------------------------------- + +Prior to Symfony 3.3, all services and (typically) arguments were explicitly configured: +it was not possible to :ref:`load services automatically ` +and :ref:`autowiring ` was much less common. + +Both of these features are optional. And even if you use them, there may be some +cases where you want to manually wire a service. For example, suppose that you want +to register *2* services for the ``SiteUpdateManager`` class - each with a different +admin email. In this case, each needs to have a unique service id: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... + + # this is the service's id + site_update_manager.superadmin: + class: AppBundle\Updates\SiteUpdateManager + # you CAN still use autowiring: we just want to show what it looks like without + autowire: false + # manually wire all arguments + arguments: + - '@AppBundle\Service\MessageGenerator' + - '@mailer' + - 'superadmin@example.com' + + site_update_manager.normal_users: + class: AppBundle\Updates\SiteUpdateManager + autowire: false + arguments: + - '@AppBundle\Service\MessageGenerator' + - '@mailer' + - 'contact@example.com' + + # Create an alias, so that - by default - if you type-hint SiteUpdateManager, + # the site_update_manager.superadmin will be used + AppBundle\Updates\SiteUpdateManager: '@site_update_manager.superadmin' + + .. code-block:: xml + + + + + + + + + + + + superadmin@example.com + + + + + + contact@example.com + + + + + + + .. code-block:: php + + // app/config/services.php + use AppBundle\Updates\SiteUpdateManager; + use AppBundle\Service\MessageGenerator; + use Symfony\Component\DependencyInjection\Reference; + + $container->register('site_update_manager.superadmin', SiteUpdateManager::class) + ->setAutowire(false) + ->setArguments(array( + new Reference(MessageGenerator::class), + new Reference('mailer'), + 'superadmin@example.com' + )); + + $container->register('site_update_manager.normal_users', SiteUpdateManager::class) + ->setAutowire(false) + ->setArguments(array( + new Reference(MessageGenerator::class), + new Reference('mailer'), + 'contact@example.com' + )); + + $container->setAlias(SiteUpdateManager::class, 'site_update_manager.superadmin') + +In this case, *two* services are registered: ``site_update_manager.superadmin`` +and ``site_update_manager.normal_users``. Thanks to the alias, if you type-hint +``SiteUpdateManager`` the first (``site_update_manager.superadmin``) will be passed. +If you want to pass the second, you'll need to :ref:`manually wire the service `. + +.. caution:: + + If you do *not* create the alias and are :ref:`loading all services from src/AppBundle `, + then *three* services have been created (the automatic service + your two services) + and the automatically loaded service will be passed - by defaut - when you type-hint + ``SiteUpdateManager``. That's why creating the alias is a good idea. + Learn more ---------- @@ -852,3 +987,4 @@ Learn more /service_container/* .. _`service-oriented architecture`: https://en.wikipedia.org/wiki/Service-oriented_architecture +.. _`Symfony Standard Edition (version 3.3) services.yml`: https://github.com/symfony/symfony-standard/blob/master/app/config/services.yml \ No newline at end of file diff --git a/service_container/parameters.rst b/service_container/parameters.rst index fa19c19f544..90d6da0c9b5 100644 --- a/service_container/parameters.rst +++ b/service_container/parameters.rst @@ -109,22 +109,42 @@ and hidden with the service definition: .. note:: - The percent sign inside a parameter or argument, as part of the string, - must be escaped with another percent sign: + If you use a string that starts with ``@`` or has ``%`` anywhere in it, you + need to escape it by adding another ``@`` or ``%``: .. configuration-block:: .. code-block:: yaml - arguments: ['http://symfony.com/?foo=%%s&bar=%%d'] + # app/config/parameters.yml + parameters: + # This will be parsed as string '@securepass' + mailer_password: '@@securepass' + + # Parsed as http://symfony.com/?foo=%s&bar=%d + url_pattern: 'http://symfony.com/?foo=%%s&bar=%%d' .. code-block:: xml - http://symfony.com/?foo=%%s&bar=%%d + + + @securepass + + + http://symfony.com/?foo=%%s&bar=%%d + .. code-block:: php - ->addArgument('http://symfony.com/?foo=%%s&bar=%%d'); + // the @ symbol does NOT need to be escaped in XML + $container->setParameter('mailer_password', '@securepass'); + + // But % does need to be escaped + $container->setParameter('url_pattern', 'http://symfony.com/?foo=%%s&bar=%%d'); + + .. code-block:: yaml + + Getting and Setting Container Parameters in PHP ----------------------------------------------- From f7f9178d6a0610c4a45774caf295b2428ceee322 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 14 May 2017 15:44:37 -0400 Subject: [PATCH 04/10] More changes --- controller.rst | 2 +- profiler/data_collector.rst | 104 ++++++------------------------------ profiler/matchers.rst | 44 +++------------ service_container.rst | 4 +- 4 files changed, 29 insertions(+), 125 deletions(-) diff --git a/controller.rst b/controller.rst index 40ea2519e84..a5599432cfa 100644 --- a/controller.rst +++ b/controller.rst @@ -288,7 +288,7 @@ controller's service config: AppBundle\Controller\LuckyController: public: true tags: - # add multiple tags to controller multiple args + # add multiple tags to control multiple args - name: controller.service_arguments action: numberAction argument: logger diff --git a/profiler/data_collector.rst b/profiler/data_collector.rst index c93239469e7..e76dfc28f51 100644 --- a/profiler/data_collector.rst +++ b/profiler/data_collector.rst @@ -82,49 +82,14 @@ The getters are added to give the template access to the collected information. Enabling Custom Data Collectors ------------------------------- -To enable a data collector, define it as a regular service and tag it as -``data_collector``: +If you're using the :ref:`default services.yml configuration ` +with ``autoconfigure``, then Symfony will automatically see your new data collector! +Your ``collect()`` method should be called next time your refresh. -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - app.request_collector: - class: AppBundle\DataCollector\RequestCollector - public: false - tags: [data_collector] +.. note:: - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/services.php - use AppBundle\DataCollector\RequestCollector; - - $container - ->register('app.request_collector', RequestCollector::class) - ->setPublic(false) - ->addTag('data_collector') - ; + If you're not using ``autoconfigure``, you can also :ref:`manually wire your service ` + and :doc:`tag - + /> @@ -288,50 +253,15 @@ the ``data_collector`` tag in your service configuration: use AppBundle\DataCollector\RequestCollector; $container - ->register('app.request_collector', RequestCollector::class) + ->autowire(RequestCollector::class) ->setPublic(false) ->addTag('data_collector', array( 'template' => 'data_collector/template.html.twig', 'id' => 'app.request_collector', + // 'priority' => 300, )) ; -.. caution:: - - The ``id`` attribute must match the value returned by the ``getName()`` method. - The position of each panel in the toolbar is determined by the priority defined by each collector. Most built-in collectors use ``255`` as their priority. If you -want your collector to be displayed before them, use a higher value: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - app.request_collector: - class: AppBundle\DataCollector\RequestCollector - tags: - - { name: data_collector, template: '...', id: '...', priority: 300 } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/services.php - use AppBundle\DataCollector\RequestCollector; - - $container - ->register('app.request_collector', RequestCollector::class) - ->addTag('data_collector', array( - 'template' => '...', - 'id' => '...', - 'priority' => 300, - )) - ; +want your collector to be displayed before them, use a higher value (like 300). diff --git a/profiler/matchers.rst b/profiler/matchers.rst index b0b8dae47f3..5d348aefcf1 100644 --- a/profiler/matchers.rst +++ b/profiler/matchers.rst @@ -106,39 +106,9 @@ matcher:: } } -Then, configure a new service and set it as ``private`` because the application -won't use it directly: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - app.super_admin_matcher: - class: AppBundle\Profiler\SuperAdminMatcher - arguments: ['@security.authorization_checker'] - public: false - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/services.php - use AppBundle\Profiler\SuperAdminMatcher; - use Symfony\Component\DependencyInjection\Reference; - - $container->register('app.super_admin_matcher', SuperAdminMatcher::class) - ->addArgument(new Reference('security.authorization_checker')) - ->setPublic(false); +Then, you'll need to make sure your class is defined as as service. If you're using +the :ref:`default services.yml configuration `, +you don't need to do anything! Once the service is registered, the only thing left to do is configure the profiler to use this service as the matcher: @@ -152,7 +122,7 @@ profiler to use this service as the matcher: # ... profiler: matcher: - service: app.super_admin_matcher + service: AppBundle\Profiler\SuperAdminMatcher .. code-block:: xml @@ -170,7 +140,7 @@ profiler to use this service as the matcher: - + @@ -178,11 +148,13 @@ profiler to use this service as the matcher: .. code-block:: php // app/config/config.php + use AppBundle\Profiler\SuperAdminMatcher; + $container->loadFromExtension('framework', array( // ... 'profiler' => array( 'matcher' => array( - 'service' => 'app.super_admin_matcher', + 'service' => SuperAdminMatcher::class, ) ), )); diff --git a/service_container.rst b/service_container.rst index 7a3287e35ee..f9f82277273 100644 --- a/service_container.rst +++ b/service_container.rst @@ -854,7 +854,9 @@ key. For example, the default Symfony configuration contains this: This can be used to quickly make many classes available as services and apply some default configuration. The ``id`` of each service is its fully-qualified class name. You can override any service that's imported by using its id (class name) below -(e.g. see :ref:`services-manually-wire-args`). +(e.g. see :ref:`services-manually-wire-args`). If you override a service, none of +the options (e.g. ``public``) are inherited from the import (but the overridden +service *does* still inherit from ``_defaults``). .. note:: From 1b82e39e1ab3b78d26d0f5fed831e3de7d26ba00 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 16 May 2017 06:28:56 -0400 Subject: [PATCH 05/10] Tweaks --- doctrine.rst | 17 +++++++++-------- event_dispatcher/before_after_filters.rst | 2 +- form/create_custom_field_type.rst | 2 +- form/form_dependencies.rst | 8 ++++---- service_container/parameters.rst | 4 ---- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 7a616d926e2..8e4a186569a 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -551,7 +551,6 @@ a controller, this is pretty easy. Add the following method to the use Doctrine\ORM\EntityManagerInterface; use Doctrine\Common\Persistence\ManagerRegistry; - // ... public function createAction(EntityManagerInterface $em) { // or fetch the em via the container @@ -585,17 +584,19 @@ a controller, this is pretty easy. Add the following method to the Take a look at the previous example in more detail: -* **lines 10-13** In this section, you instantiate and work with the ``$product`` - object like any other normal PHP object. +.. _doctrine-entity-manager: -* **line 15** This line fetches Doctrine's *entity manager* object, which is - responsible for the process of persisting objects to, and fetching objects - from, the database. +* **line 10** The ``EntityManagerInterface`` type-hint tells Symfony to pass you Doctrine's + *entity manager* object, which is the most important object in Doctrine. It's + responsible for saving objects to, and fetching objects from, the database. + +* **lines 15-18** In this section, you instantiate and work with the ``$product`` + object like any other normal PHP object. -* **line 18** The ``persist($product)`` call tells Doctrine to "manage" the +* **line 21** The ``persist($product)`` call tells Doctrine to "manage" the ``$product`` object. This does **not** cause a query to be made to the database. -* **line 21** When the ``flush()`` method is called, Doctrine looks through +* **line 24** When the ``flush()`` method is called, Doctrine looks through all of the objects that it's managing to see if they need to be persisted to the database. In this example, the ``$product`` object's data doesn't exist in the database, so the entity manager executes an ``INSERT`` query, diff --git a/event_dispatcher/before_after_filters.rst b/event_dispatcher/before_after_filters.rst index 18100a97d1f..c2b53e71d88 100644 --- a/event_dispatcher/before_after_filters.rst +++ b/event_dispatcher/before_after_filters.rst @@ -160,7 +160,7 @@ you want. .. tip:: - If your subscriber id *not* called on each request, double-check that + If your subscriber is *not* called on each request, double-check that you're :ref:`loading services ` from the ``EventSubscriber`` directory and have :ref:`autoconfigure ` enabled. You can also manually add the ``kernel.event_subscriber`` tag. diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst index 7b75441b27e..59189dd8a6a 100644 --- a/form/create_custom_field_type.rst +++ b/form/create_custom_field_type.rst @@ -268,7 +268,7 @@ Accessing Services and Config ----------------------------- If you need to access :doc:`services ` from your form class, -just a ``__construct()`` method like normal:: +add a ``__construct()`` method like normal:: // src/AppBundle/Form/Type/GenderType.php namespace AppBundle\Form\Type; diff --git a/form/form_dependencies.rst b/form/form_dependencies.rst index 5ba9e2b4a6a..a883f6a91e7 100644 --- a/form/form_dependencies.rst +++ b/form/form_dependencies.rst @@ -71,8 +71,8 @@ Alternatively, you can define your form class as a service. This is a good idea you want to re-use the form in several places - registering it as a service makes this easier. -Suppose you need to access the ``EntityManager`` service so that you -can make a query. First, add this as an argument to your form class:: +Suppose you need to access the :ref:`EntityManager ` object +so that you can make a query. First, add this as an argument to your form class:: // src/AppBundle/Form/TaskType.php @@ -93,8 +93,8 @@ can make a query. First, add this as an argument to your form class:: If you're using :ref:`autowire ` and :ref:`autoconfigure `, then you don't need to do *anything* -else: Symfony will automatically know to pass the entity manager service to your -form type. +else: Symfony will automatically know to pass the correct ``EntityManager`` object +to your ``__construct()`` method. If you are **not using autowire and autoconfigure**, register your form as a service manually and tag it with ``form.type``: diff --git a/service_container/parameters.rst b/service_container/parameters.rst index 90d6da0c9b5..cefbe92dd21 100644 --- a/service_container/parameters.rst +++ b/service_container/parameters.rst @@ -142,10 +142,6 @@ and hidden with the service definition: // But % does need to be escaped $container->setParameter('url_pattern', 'http://symfony.com/?foo=%%s&bar=%%d'); - .. code-block:: yaml - - - Getting and Setting Container Parameters in PHP ----------------------------------------------- From b82f4afc5eb60c6d4ab406798db3b64793f64f96 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 16 May 2017 06:29:58 -0400 Subject: [PATCH 06/10] fixing bad table --- service_container.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service_container.rst b/service_container.rst index f9f82277273..493503f30c5 100644 --- a/service_container.rst +++ b/service_container.rst @@ -310,16 +310,16 @@ type-hints by running: This is just a small subset of the output: -=============================================================== ======================================================================= +=============================================================== ===================================== Service ID Class name -=============================================================== ======================================================================= +=============================================================== ===================================== ``Psr\Cache\CacheItemPoolInterface`` alias for "cache.app.recorder" ``Psr\Log\LoggerInterface`` alias for "monolog.logger" ``Symfony\Component\EventDispatcher\EventDispatcherInterface`` alias for "debug.event_dispatcher" ``Symfony\Component\HttpFoundation\RequestStack`` alias for "request_stack" ``Symfony\Component\HttpFoundation\Session\SessionInterface`` alias for "session" ``Symfony\Component\Routing\RouterInterface`` alias for "router.default" -=============================== ======================================================================= +=============================================================== ===================================== Handling Multiple Services ~~~~~~~~~~~~~~~~~~~~~~~~~~ From 11b9627f7cad0fe8073a4619bb093544d1bd3020 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 16 May 2017 06:33:27 -0400 Subject: [PATCH 07/10] fixing bad link --- form/data_transformers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/form/data_transformers.rst b/form/data_transformers.rst index 5c278eed2e0..c69a379c1f8 100644 --- a/form/data_transformers.rst +++ b/form/data_transformers.rst @@ -397,7 +397,7 @@ As long as you're using :ref:`autowire ` and .. tip:: If you're not using ``autowire`` and ``autoconfigure``, see - :doc:`` for how to configure your new ``IssueSelectorType``. + :doc:`/form/create_custom_field_type` for how to configure your new ``IssueSelectorType``. .. _model-and-view-transformers: From 220f41950d7c4b0f2ade0badef2893479898e439 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 16 May 2017 06:37:12 -0400 Subject: [PATCH 08/10] more bad links --- form/dynamic_form_modification.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/form/dynamic_form_modification.rst b/form/dynamic_form_modification.rst index a618d97ed1c..e074ab08872 100644 --- a/form/dynamic_form_modification.rst +++ b/form/dynamic_form_modification.rst @@ -335,7 +335,7 @@ If you're using :ref:`autowire ` and .. tip:: - If you're not using autowire and autoconfigure, see :doc:`` + If you're not using autowire and autoconfigure, see :doc:`/form/form_dependencies` for how to register your form type as a service. In a controller, create the form like normal:: From 1de941ca1784265b603ef4e9ab083990dcfc9a50 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 16 May 2017 06:40:05 -0400 Subject: [PATCH 09/10] bad link --- profiler/data_collector.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiler/data_collector.rst b/profiler/data_collector.rst index e76dfc28f51..0d8b930b5f4 100644 --- a/profiler/data_collector.rst +++ b/profiler/data_collector.rst @@ -89,7 +89,7 @@ Your ``collect()`` method should be called next time your refresh. .. note:: If you're not using ``autoconfigure``, you can also :ref:`manually wire your service ` - and :doc:`tag ` it with ``data_collector``. Adding Web Profiler Templates ----------------------------- From 228b0de0abaa718b3067561cd22658741dd5ce94 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 16 May 2017 06:44:28 -0400 Subject: [PATCH 10/10] Bad link --- service_container.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service_container.rst b/service_container.rst index 493503f30c5..b8a4f7db5e4 100644 --- a/service_container.rst +++ b/service_container.rst @@ -292,7 +292,7 @@ and use it later:: That's it! The container will *automatically* know to pass the ``logger`` service when instantiating the ``MessageGenerator``. How does it know to do this? -:ref:`Autowiring `. The key is the ``LoggerInterface`` +:ref:`Autowiring `. The key is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the ``autowire: true`` config in ``services.yml``. When you type-hint an argument, the container will automatically find the matching service. If it can't, you'll see a clear exception with a helpful