diff --git a/_build/redirection_map b/_build/redirection_map index 983c68f78ab..fdb85af6438 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -77,14 +77,15 @@ /book/configuration /configuration /book/propel /propel/propel /book/performance /performance +/bundles/installation /bundles /cookbook/assetic/apply_to_option /frontend/assetic/apply_to_option /cookbook/assetic/asset_management /frontend/assetic/asset_management -/cookbook/assetic/index /frontend/assetic +/cookbook/assetic/index /frontend/assetic/index /cookbook/assetic/jpeg_optimize /frontend/assetic/jpeg_optimize /cookbook/assetic/php /frontend/assetic/php /cookbook/assetic/uglifyjs /frontend/assetic/uglifyjs /cookbook/assetic/yuicompressor /frontend/assetic/yuicompressor -/assetic /frontend/assetic +/assetic /frontend/assetic/index /assetic/apply_to_option /frontend/assetic/apply_to_option /assetic/asset_management /frontend/assetic/asset_management /assetic/jpeg_optimize /frontend/assetic/jpeg_optimize @@ -96,10 +97,11 @@ /cookbook/bundles/extension /bundles/extension /cookbook/bundles/index /bundles /cookbook/bundles/inheritance /bundles/inheritance -/cookbook/bundles/installation /bundles/installation +/cookbook/bundles/installation /bundles /cookbook/bundles/override /bundles/override /cookbook/bundles/prepend_extension /bundles/prepend_extension -/cookbook/bundles/remove /bundles/remove +/cookbook/bundles/remove /bundles +/bundles/remove /bundles /cookbook/cache/form_csrf_caching /http_cache/form_csrf_caching /cookbook/cache/varnish /http_cache/varnish /cookbook/composer /setup/composer @@ -120,14 +122,16 @@ /cookbook/console/logging /console /cookbook/console/request_context /console/request_context /cookbook/console/style /console/style -/cookbook/console/usage /console/usage -/cookbook/controller/csrf_token_validation /controller/csrf_token_validation +/cookbook/console/usage /console +/console/usage /console +/cookbook/controller/csrf_token_validation /security/csrf /cookbook/controller/error_pages /controller/error_pages /cookbook/controller/forwarding /controller/forwarding /cookbook/controller/index /controller /cookbook/controller/service /controller/service /cookbook/controller/upload_file /controller/upload_file -/cookbook/debugging /debug/debugging +/cookbook/debugging / +/debug/debugging / /cookbook/deployment/azure-website /cookbook/azure-website /cookbook/deployment/fortrabbit /deployment/fortrabbit /cookbook/deployment/heroku /deployment/heroku @@ -135,22 +139,25 @@ /cookbook/deployment/platformsh /deployment/platformsh /cookbook/deployment/tools /deployment/tools /cookbook/doctrine/common_extensions /doctrine/common_extensions -/cookbook/doctrine/console /doctrine/console +/cookbook/doctrine/console /doctrine /cookbook/doctrine/custom_dql_functions /doctrine/custom_dql_functions /cookbook/doctrine/dbal /doctrine/dbal /cookbook/doctrine/event_listeners_subscribers /doctrine/event_listeners_subscribers /cookbook/doctrine/index /doctrine -/cookbook/doctrine/mapping_model_classes /doctrine/mapping_model_classes +/cookbook/doctrine/mapping_model_classes /doctrine +/doctrine/mapping_model_classes /doctrine /cookbook/doctrine/mongodb_session_storage /doctrine/mongodb_session_storage /cookbook/doctrine/multiple_entity_managers /doctrine/multiple_entity_managers /cookbook/doctrine/pdo_session_storage /doctrine/pdo_session_storage /cookbook/doctrine/registration_form /doctrine/registration_form /cookbook/doctrine/resolve_target_entity /doctrine/resolve_target_entity /cookbook/doctrine/reverse_engineering /doctrine/reverse_engineering -/cookbook/email/cloud /email/cloud +/doctrine/repository /doctrine +/doctrine/console /doctrine +/cookbook/email/cloud /email /cookbook/email/dev_environment /email/dev_environment /cookbook/email/email /email -/cookbook/email/gmail /email/gmail +/cookbook/email/gmail /email /cookbook/email/index /email /cookbook/email/spool /email/spool /cookbook/email/testing /email/testing @@ -214,7 +221,7 @@ /cookbook/security/acl /security/acl /cookbook/security/acl_advanced /security/acl_advanced /cookbook/security/api_key_authentication /security/api_key_authentication -/cookbook/security/csrf_in_login_form /security/csrf_in_login_form +/cookbook/security/csrf_in_login_form /security/csrf /cookbook/security/custom_authentication_provider /security/custom_authentication_provider /cookbook/security/custom_password_authenticator /security/custom_password_authenticator /cookbook/security/custom_provider /security/custom_provider @@ -280,18 +287,28 @@ /cookbook/web_services/php_soap_extension /controller/soap_web_service /cookbook/workflow/homestead /setup/homestead /cookbook/workflow/index /setup -/cookbook/workflow/new_project_git /setup/new_project_git -/cookbook/workflow/new_project_svn /setup/new_project_svn +/cookbook/workflow/new_project_git /setup +/cookbook/workflow/new_project_svn /setup +/setup/new_project_git /setup +/setup/new_project_svn /setup /components/asset/index /components/asset /components/asset/introduction /components/asset /components/browser_kit/index /components/browser_kit /components/browser_kit/introduction /components/browser_kit /components/class_loader/introduction /components/class_loader /components/class_loader/index /components/class_loader +/components/class_loader/cache_class_loader /components/class_loader +/components/class_loader/class_loader /components/class_loader +/components/class_loader/class_map_generator /components/class_loader +/components/class_loader/debug_class_loader /components/class_loader +/components/class_loader/map_class_loader /components/class_loader +/components/class_loader/map_class_loader /components/class_loader +/components/class_loader/psr4_class_loader /components/class_loader /components/config/introduction /components/config /components/config/index /components/config /components/console/helpers/tablehelper /components/console/helpers/table /components/console/helpers/progresshelper /components/console/helpers/progressbar +/components/console/helpers/dialoghelper /components/console/helpers/questionhelper /components/console/introduction /components/console /components/console/index /components/console /components/debug/class_loader /components/debug @@ -343,21 +360,56 @@ /components/var_dumper/index /components/var_dumper /components/yaml/introduction /components/yaml /components/yaml/index /components/yaml +/console/logging /console +/controller/csrf_token_validation /security/csrf /deployment/tools /deployment +/form/csrf_protection /security/csrf /install/bundles /setup/bundles +/email/gmail /email +/email/cloud /email /event_dispatcher/class_extension /event_dispatcher /form /forms /form/use_virtual_forms /form/inherit_data_option +/frontend/assetic /frontend/assetic/index +/frontend/assetic/apply_to_option /frontend/assetic/index +/frontend/assetic/asset_management /frontend/assetic/index +/frontend/assetic/jpeg_optimize /frontend/assetic/index +/frontend/assetic/php /frontend/assetic/index +/frontend/assetic/uglifyjs /frontend/assetic/index +/frontend/assetic/yuicompressor /frontend/assetic/index +/reference/configuration/assetic /frontend/assetic/index /security/target_path /security +/security/csrf_in_login_form /security/csrf /service_container/service_locators /service_container/service_subscribers_locators /service_container/third_party /service_container /templating/templating_service /templates /testing/simulating_authentication /testing/http_authentication /validation/group_service_resolver /form/validation_group_service_resolver /request/load_balancer_reverse_proxy /deployment/proxies +/quick_tour/the_controller /quick_tour/the_big_picture +/quick_tour/the_view /quick_tour/flex_recipes /service_container/service_locators /service_container/service_subscribers_locators +/templating/overriding /bundles/override +/security/custom_provider /security/user_provider +/security/multiple_user_providers /security/user_provider +/security/custom_password_authenticator /security/guard_authentication +/security/api_key_authentication /security/guard_authentication +/security/pre_authenticated /security/auth_providers +/security/host_restriction /security/firewall_restriction +/security/acl_advanced /security/acl +/security/password_encoding /security /weblink /web_link /components/weblink /components/web_link /frontend/encore/installation-no-flex /frontend/encore/installation +/http_cache/form_csrf_caching /security/csrf /console/logging /console +/reference/forms/twig_reference /form/form_customization +/form/rendering /form/form_customization +/profiler/matchers /profiler +/profiler/profiling_data /profiler +/profiler/wdt_follow_ajax /profiler +/security/entity_provider /security/user_provider +/session/avoid_session_start /session +/session/sessions_directory /session /frontend/encore/legacy-apps /frontend/encore/legacy-applications +/configuration/external_parameters /configuration/environment_variables diff --git a/_images/components/messenger/overview.svg b/_images/components/messenger/overview.svg new file mode 100644 index 00000000000..94737e7a6da --- /dev/null +++ b/_images/components/messenger/overview.svg @@ -0,0 +1 @@ + diff --git a/_images/components/serializer/serializer_workflow.png b/_images/components/serializer/serializer_workflow.png deleted file mode 100644 index 3e1944e6cec..00000000000 Binary files a/_images/components/serializer/serializer_workflow.png and /dev/null differ diff --git a/_images/components/serializer/serializer_workflow.svg b/_images/components/serializer/serializer_workflow.svg new file mode 100644 index 00000000000..f3906506878 --- /dev/null +++ b/_images/components/serializer/serializer_workflow.svg @@ -0,0 +1 @@ + diff --git a/_images/components/workflow/blogpost_puml.png b/_images/components/workflow/blogpost_puml.png new file mode 100644 index 00000000000..14d45c8b40f Binary files /dev/null and b/_images/components/workflow/blogpost_puml.png differ diff --git a/_images/form/form-field-parts.svg b/_images/form/form-field-parts.svg new file mode 100644 index 00000000000..c9856c89a99 --- /dev/null +++ b/_images/form/form-field-parts.svg @@ -0,0 +1 @@ + diff --git a/_images/http/request-flow.png b/_images/http/request-flow.png deleted file mode 100644 index cbf4019307b..00000000000 Binary files a/_images/http/request-flow.png and /dev/null differ diff --git a/_images/http/request-flow.svg b/_images/http/request-flow.svg new file mode 100644 index 00000000000..97061ada0d5 --- /dev/null +++ b/_images/http/request-flow.svg @@ -0,0 +1 @@ + diff --git a/_images/mercure/chrome.png b/_images/mercure/chrome.png new file mode 100644 index 00000000000..8ccc55a0a88 Binary files /dev/null and b/_images/mercure/chrome.png differ diff --git a/_images/mercure/discovery.png b/_images/mercure/discovery.png new file mode 100644 index 00000000000..0ef38271de6 Binary files /dev/null and b/_images/mercure/discovery.png differ diff --git a/_images/mercure/schema.png b/_images/mercure/schema.png new file mode 100644 index 00000000000..4616046e5cc Binary files /dev/null and b/_images/mercure/schema.png differ diff --git a/_images/profiler/web-interface.png b/_images/profiler/web-interface.png new file mode 100644 index 00000000000..2e6c6061892 Binary files /dev/null and b/_images/profiler/web-interface.png differ diff --git a/_images/quick_tour/no_routes_page.png b/_images/quick_tour/no_routes_page.png new file mode 100644 index 00000000000..8c8c4d508d1 Binary files /dev/null and b/_images/quick_tour/no_routes_page.png differ diff --git a/_images/quick_tour/profiler.png b/_images/quick_tour/profiler.png deleted file mode 100644 index 3b55b75f3af..00000000000 Binary files a/_images/quick_tour/profiler.png and /dev/null differ diff --git a/_images/quick_tour/web_debug_toolbar.png b/_images/quick_tour/web_debug_toolbar.png index 72cd7483f2f..465020380cb 100644 Binary files a/_images/quick_tour/web_debug_toolbar.png and b/_images/quick_tour/web_debug_toolbar.png differ diff --git a/_images/quick_tour/welcome.png b/_images/quick_tour/welcome.png deleted file mode 100644 index 738105f715d..00000000000 Binary files a/_images/quick_tour/welcome.png and /dev/null differ diff --git a/_images/security/authentication-guard-methods.svg b/_images/security/authentication-guard-methods.svg index a18da9e66dc..cc042656212 100644 --- a/_images/security/authentication-guard-methods.svg +++ b/_images/security/authentication-guard-methods.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/_images/security/http_basic_popup.png b/_images/security/http_basic_popup.png deleted file mode 100644 index fcd9a4ed836..00000000000 Binary files a/_images/security/http_basic_popup.png and /dev/null differ diff --git a/_images/security/symfony_loggedin_wdt.png b/_images/security/symfony_loggedin_wdt.png index ca182192c94..b51e1cafba1 100644 Binary files a/_images/security/symfony_loggedin_wdt.png and b/_images/security/symfony_loggedin_wdt.png differ diff --git a/_images/sources/components/messenger/overview.dia b/_images/sources/components/messenger/overview.dia new file mode 100644 index 00000000000..55ee153439e Binary files /dev/null and b/_images/sources/components/messenger/overview.dia differ diff --git a/_images/sources/components/serializer/serializer_workflow.dia b/_images/sources/components/serializer/serializer_workflow.dia new file mode 100644 index 00000000000..6cb44280d0d Binary files /dev/null and b/_images/sources/components/serializer/serializer_workflow.dia differ diff --git a/_images/sources/form/form-field-parts.dia b/_images/sources/form/form-field-parts.dia new file mode 100644 index 00000000000..d6ed2dfc3fe Binary files /dev/null and b/_images/sources/form/form-field-parts.dia differ diff --git a/_images/sources/http/request-flow.dia b/_images/sources/http/request-flow.dia new file mode 100644 index 00000000000..ca09a05504e Binary files /dev/null and b/_images/sources/http/request-flow.dia differ diff --git a/_images/sources/security/authentication-guard-methods.dia b/_images/sources/security/authentication-guard-methods.dia index 283e74b2e05..d655be780fe 100644 Binary files a/_images/sources/security/authentication-guard-methods.dia and b/_images/sources/security/authentication-guard-methods.dia differ diff --git a/_includes/service_container/_my_mailer.rst.inc b/_includes/service_container/_my_mailer.rst.inc index a944097f917..01eafdfe87a 100644 --- a/_includes/service_container/_my_mailer.rst.inc +++ b/_includes/service_container/_my_mailer.rst.inc @@ -2,15 +2,15 @@ .. code-block:: yaml - # app/config/services.yml + # config/services.yaml services: app.mailer: - class: AppBundle\Mailer + class: App\Mailer arguments: [sendmail] .. code-block:: xml - + - + sendmail @@ -26,8 +26,8 @@ .. code-block:: php - // app/config/services.php - use AppBundle\Mailer; + // config/services.php + use App\Mailer; $container->register('app.mailer', Mailer::class) ->addArgument('sendmail'); diff --git a/assetic/_standard_edition_warning.rst.inc b/assetic/_standard_edition_warning.rst.inc deleted file mode 100644 index 2a111cd2291..00000000000 --- a/assetic/_standard_edition_warning.rst.inc +++ /dev/null @@ -1,5 +0,0 @@ -.. caution:: - - Starting from Symfony 2.8, Assetic is no longer included by default in the - Symfony Standard Edition. Refer to :doc:`this article ` - to learn how to install and enable Assetic in your Symfony application. diff --git a/best_practices/business-logic.rst b/best_practices/business-logic.rst index d4da19721a8..8feb8163bf7 100644 --- a/best_practices/business-logic.rst +++ b/best_practices/business-logic.rst @@ -10,92 +10,55 @@ your app that's not specific to the framework (e.g. routing and controllers). Domain classes, Doctrine entities and regular PHP classes that are used as services are good examples of business logic. -For most projects, you should store everything inside the AppBundle. +For most projects, you should store all your code inside the ``src/`` directory. Inside here, you can create whatever directories you want to organize things: .. code-block:: text symfony-project/ - ├─ app/ + ├─ config/ + ├─ public/ ├─ src/ - │ └─ AppBundle/ - │ └─ Utils/ - │ └─ MyClass.php + │ └─ Utils/ + │ └─ MyClass.php ├─ tests/ ├─ var/ - ├─ vendor/ - └─ web/ + └─ vendor/ -Storing Classes Outside of the Bundle? --------------------------------------- +.. _services-naming-and-format: -But there's no technical reason for putting business logic inside of a bundle. -If you like, you can create your own namespace inside the ``src/`` directory -and put things there: +Services: Naming and Configuration +---------------------------------- -.. code-block:: text - - symfony-project/ - ├─ app/ - ├─ src/ - │ ├─ Acme/ - │ │ └─ Utils/ - │ │ └─ MyClass.php - │ └─ AppBundle/ - ├─ tests/ - ├─ var/ - ├─ vendor/ - └─ web/ - -.. tip:: +.. best-practice:: - The recommended approach of using the ``AppBundle/`` directory is for - simplicity. If you're advanced enough to know what needs to live in - a bundle and what can live outside of one, then feel free to do that. + Use autowiring to automate the configuration of application services. -Services: Naming and Format ---------------------------- +:doc:`Service autowiring ` is a feature provided +by Symfony's Service Container to manage services with minimal configuration. It +reads the type-hints on your constructor (or other methods) and automatically +passes the correct services to each method. It can also add +:doc:`service tags ` to the services needing them, such +as Twig extensions, event subscribers, etc. The blog application needs a utility that can transform a post title (e.g. -"Hello World") into a slug (e.g. "hello-world"). The slug will be used as -part of the post URL. +"Hello World") into a slug (e.g. "hello-world") to include it as part of the +post URL. Let's create a new ``Slugger`` class inside ``src/Utils/``:: -Let's create a new ``Slugger`` class inside ``src/AppBundle/Utils/`` and -add the following ``slugify()`` method:: - - // src/AppBundle/Utils/Slugger.php - namespace AppBundle\Utils; + // src/Utils/Slugger.php + namespace App\Utils; class Slugger { - public function slugify($string) + public function slugify(string $value): string { - return preg_replace( - '/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string))) - ); + // ... } } -Next, define a new service for that class. - -.. code-block:: yaml - - # app/config/services.yml - services: - # ... - - # use the fully-qualified class name as the service id - AppBundle\Utils\Slugger: - public: false - -.. note:: - - If you're using the :ref:`default services.yml configuration `, - the class is auto-registered as a service. - -Traditionally, the naming convention for a service was a short, but unique -snake case key - e.g. ``app.utils.slugger``. But for most services, you should now -use the class name. +If you're using the :ref:`default services.yaml configuration `, +this class is auto-registered as a service with the ID ``App\Utils\Slugger`` (to +prevent against typos, import the class and write ``Slugger::class`` in your code). .. best-practice:: @@ -103,19 +66,15 @@ use the class name. except when you have multiple services configured for the same class (in that case, use a snake case id). -Now you can use the custom slugger in any controller class, such as the -``AdminController``:: +Now you can use the custom slugger in any other service or controller class, +such as the ``AdminController``:: - use AppBundle\Utils\Slugger; + use App\Utils\Slugger; - public function createAction(Request $request, Slugger $slugger) + public function create(Request $request, Slugger $slugger) { // ... - // you can also fetch a public service like this - // but fetching services in this way is not considered a best practice - // $slugger = $this->get('app.slugger'); - if ($form->isSubmitted() && $form->isValid()) { $slug = $slugger->slugify($post->getTitle()); $post->setSlug($slug); @@ -125,7 +84,7 @@ Now you can use the custom slugger in any controller class, such as the } Services can also be :ref:`public or private `. If you use the -:ref:`default services.yml configuration `, +:ref:`default services.yaml configuration `, all services are private by default. .. best-practice:: @@ -137,11 +96,13 @@ all services are private by default. Service Format: YAML -------------------- -In the previous section, YAML was used to define the service. +If you use the :ref:`default services.yaml configuration `, +most services will be configured automatically. However, in some edge cases +you'll need to configure services (or parts of them) manually. .. best-practice:: - Use the YAML format to define your own services. + Use the YAML format to configure your own services. This is controversial, and in our experience, YAML and XML usage is evenly distributed among developers, with a slight preference towards YAML. @@ -149,37 +110,7 @@ Both formats have the same performance, so this is ultimately a matter of personal taste. We recommend YAML because it's friendly to newcomers and concise. You can -of course use whatever format you like. - -Service: No Class Parameter ---------------------------- - -You may have noticed that the previous service definition doesn't configure -the class namespace as a parameter: - -.. code-block:: yaml - - # app/config/services.yml - - # service definition with class namespace as parameter - parameters: - slugger.class: AppBundle\Utils\Slugger - - services: - app.slugger: - class: '%slugger.class%' - -This practice is cumbersome and completely unnecessary for your own services. - -.. best-practice:: - - Don't define parameters for the classes of your services. - -This practice was wrongly adopted from third-party bundles. When Symfony -introduced its service container, some developers used this technique to -allow overriding services. However, overriding a service by just changing its -class name is a very rare use case because, frequently, the new service has -different constructor arguments. +use any of the other formats if you prefer another format. Using a Persistence Layer ------------------------- @@ -192,7 +123,7 @@ library or strategy you want for this. In practice, many Symfony applications rely on the independent `Doctrine project`_ to define their model using entities and repositories. Just like with business logic, we recommend storing Doctrine entities in the -AppBundle. +``src/Entity/`` directory. The three entities defined by our sample blog application are a good example: @@ -201,16 +132,10 @@ The three entities defined by our sample blog application are a good example: symfony-project/ ├─ ... └─ src/ - └─ AppBundle/ - └─ Entity/ - ├─ Comment.php - ├─ Post.php - └─ User.php - -.. tip:: - - If you're more advanced, you can of course store them under your own - namespace in ``src/``. + └─ Entity/ + ├─ Comment.php + ├─ Post.php + └─ User.php Doctrine Mapping Information ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -227,7 +152,7 @@ PHP and annotations. Annotations are by far the most convenient and agile way of setting up and looking for mapping information:: - namespace AppBundle\Entity; + namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; @@ -303,29 +228,14 @@ the following command to install the Doctrine fixtures bundle: $ composer require "doctrine/doctrine-fixtures-bundle" -Then, enable the bundle in ``AppKernel.php``, but only for the ``dev`` and +Then, this bundle is enabled automatically, but only for the ``dev`` and ``test`` environments:: - use Symfony\Component\HttpKernel\Kernel; - - class AppKernel extends Kernel - { - public function registerBundles() - { - $bundles = [ - // ... - ]; - - if (in_array($this->getEnvironment(), ['dev', 'test'])) { - // ... - $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(); - } - - return $bundles; - } - + // config/bundles.php + return [ // ... - } + Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], + ]; We recommend creating just *one* `fixture class`_ for simplicity, though you're welcome to have more if that class gets quite large. @@ -340,7 +250,7 @@ command: Careful, database will be purged. Do you want to continue Y/N ? Y > purging database - > loading AppBundle\DataFixtures\ORM\LoadFixtures + > loading App\DataFixtures\ORM\LoadFixtures Coding Standards ---------------- diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst index 87e8a5f7ef5..ab2c38e98a5 100644 --- a/best_practices/configuration.rst +++ b/best_practices/configuration.rst @@ -6,62 +6,68 @@ and security credentials) and different environments (development, production). That's why Symfony recommends that you split the application configuration into three parts. -.. _config-parameters.yml: - Infrastructure-Related Configuration ------------------------------------ +These are the options that change from one machine to another (e.g. from your +development machine to the production server) but which don't change the +application behavior. + .. best-practice:: - Define the infrastructure-related configuration options in the - ``app/config/parameters.yml`` file. + Define the infrastructure-related configuration options as + :doc:`environment variables `. During + development, use the ``.env`` and ``.env.local`` files at the root of your + project to set these. -The default ``parameters.yml`` file follows this recommendation and defines the -options related to the database and mail server infrastructure: +By default, Symfony adds these types of options to the ``.env`` file when +installing new dependencies in the app: -.. code-block:: yaml +.. code-block:: bash - # app/config/parameters.yml - parameters: - database_driver: pdo_mysql - database_host: 127.0.0.1 - database_port: ~ - database_name: symfony - database_user: root - database_password: ~ + # .env + ###> doctrine/doctrine-bundle ### + DATABASE_URL=sqlite:///%kernel.project_dir%/var/data/blog.sqlite + ###< doctrine/doctrine-bundle ### - mailer_transport: smtp - mailer_host: 127.0.0.1 - mailer_user: ~ - mailer_password: ~ + ###> symfony/swiftmailer-bundle ### + MAILER_URL=smtp://localhost?encryption=ssl&auth_mode=login&username=&password= + ###< symfony/swiftmailer-bundle ### - # ... + # ... -These options aren't defined inside the ``app/config/config.yml`` file because +These options aren't defined inside the ``config/services.yaml`` file because they have nothing to do with the application's behavior. In other words, your application doesn't care about the location of your database or the credentials to access to it, as long as the database is correctly configured. -.. _best-practices-canonical-parameters: +To override these variables with machine-specific or sensitive values, create a +``.env.local`` file. This file should not be added to version control. + +.. caution:: + + Beware that dumping the contents of the ``$_SERVER`` and ``$_ENV`` variables + or outputting the ``phpinfo()`` contents will display the values of the + environment variables, exposing sensitive information such as the database + credentials. Canonical Parameters ~~~~~~~~~~~~~~~~~~~~ .. best-practice:: - Define all your application's parameters in the - ``app/config/parameters.yml.dist`` file. + Define all your application's env vars in the ``.env`` file. + +Symfony includes a configuration file called ``.env`` at the project root, which +stores the canonical list of environment variables for the application. This +file should be stored in version control and so should only contain non-sensitive +default values. -Symfony includes a configuration file called ``parameters.yml.dist``, which -stores the canonical list of configuration parameters for the application. +.. caution:: -Whenever a new configuration parameter is defined for the application, you -should also add it to this file and submit the changes to your version control -system. Then, whenever a developer updates the project or deploys it to a server, -Symfony will check if there is any difference between the canonical -``parameters.yml.dist`` file and your local ``parameters.yml`` file. If there -is a difference, Symfony will ask you to provide a value for the new parameter -and it will add it to your local ``parameters.yml`` file. + Applications created before November 2018 had a slightly different system, + involving a ``.env.dist`` file. For information about upgrading, see: + :doc:`/configuration/dot-env-changes`. Application-Related Configuration --------------------------------- @@ -69,17 +75,17 @@ Application-Related Configuration .. best-practice:: Define the application behavior related configuration options in the - ``app/config/config.yml`` file. + ``config/services.yaml`` file. -The ``config.yml`` file contains the options used by the application to modify -its behavior, such as the sender of email notifications, or the enabled -`feature toggles`_. Defining these values in ``parameters.yml`` file would -add an extra layer of configuration that's not needed because you don't need -or want these configuration values to change on each server. +The ``services.yaml`` file contains the options used by the application to +modify its behavior, such as the sender of email notifications, or the enabled +`feature toggles`_. Defining these values in ``.env`` file would add an extra +layer of configuration that's not needed because you don't need or want these +configuration values to change on each server. -The configuration options defined in the ``config.yml`` file usually vary from -one :doc:`environment ` to another. That's -why Symfony already includes ``app/config/config_dev.yml`` and ``app/config/config_prod.yml`` +The configuration options defined in the ``services.yaml`` may vary from one +:doc:`environment ` to another. That's why Symfony +supports defining ``config/services_dev.yaml`` and ``config/services_prod.yaml`` files so that you can override specific values for each environment. Constants vs Configuration Options @@ -99,7 +105,7 @@ used to control the number of posts to display on the blog homepage: .. code-block:: yaml - # app/config/config.yml + # config/services.yaml parameters: homepage.number_of_items: 10 @@ -109,8 +115,8 @@ option for a value that you are never going to configure just isn't necessary. Our recommendation is to define these values as constants in your application. You could, for example, define a ``NUMBER_OF_ITEMS`` constant in the ``Post`` entity:: - // src/AppBundle/Entity/Post.php - namespace AppBundle\Entity; + // src/Entity/Post.php + namespace App\Entity; class Post { @@ -135,10 +141,10 @@ Constants can be used for example in your Twig templates thanks to the And Doctrine entities and repositories can now easily access these values, whereas they cannot access the container parameters:: - namespace AppBundle\Repository; + namespace App\Repository; + use App\Entity\Post; use Doctrine\ORM\EntityRepository; - use AppBundle\Entity\Post; class PostRepository extends EntityRepository { @@ -165,7 +171,7 @@ just one or two words to describe the purpose of the parameter: .. code-block:: yaml - # app/config/config.yml + # config/services.yaml parameters: # don't do this: 'dir' is too generic and it doesn't convey any meaning app.dir: '...' @@ -176,44 +182,6 @@ just one or two words to describe the purpose of the parameter: app.dir.contents: '...' app.contents-dir: '...' -Semantic Configuration: Don't Do It ------------------------------------ - -.. best-practice:: - - Don't define a semantic dependency injection configuration for your bundles. - -As explained in :doc:`/bundles/extension` article, Symfony bundles -have two choices on how to handle configuration: normal service configuration -through the ``services.yml`` file and semantic configuration through a special -``*Extension`` class. - -Although semantic configuration is much more powerful and provides nice features -such as configuration validation, the amount of work needed to define that -configuration isn't worth it for bundles that aren't meant to be shared as -third-party bundles. - -Moving Sensitive Options Outside of Symfony Entirely ----------------------------------------------------- - -When dealing with sensitive options, like database credentials, we also recommend -that you store them outside the Symfony project and make them available -through environment variables: - -.. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - # ... - password: "%env(DB_PASSWORD)%" - -.. versionadded:: 3.2 - - Support for runtime environment variables via the ``%env(...)%`` syntax - was added in Symfony 3.2. Prior to version 3.2, you needed to use the - :doc:`special SYMFONY__ variables `. - ---- Next: :doc:`/best_practices/business-logic` diff --git a/best_practices/controllers.rst b/best_practices/controllers.rst index 22605898357..0ef6fd1d3bc 100644 --- a/best_practices/controllers.rst +++ b/best_practices/controllers.rst @@ -5,16 +5,15 @@ Symfony follows the philosophy of *"thin controllers and fat models"*. This means that controllers should hold just the thin layer of *glue-code* needed to coordinate the different parts of the application. -As a rule of thumb, you should follow the 5-10-20 rule, where controllers should -only define 5 variables or less, contain 10 actions or less and include 20 lines -of code or less in each action. This isn't an exact science, but it should -help you realize when code should be refactored out of the controller and -into a service. +Your controller methods should just call to other services, trigger some events +if needed and then return a response, but they should not contain any actual +business logic. If they do, refactor it out of the controller and into a service. .. best-practice:: - Make your controller extend the FrameworkBundle base controller and use - annotations to configure routing, caching and security whenever possible. + Make your controller extend the ``AbstractController`` base controller + provided by Symfony and use annotations to configure routing, caching and + security whenever possible. Coupling the controllers to the underlying framework allows you to leverage all of its features and increases your productivity. @@ -33,6 +32,18 @@ Overall, this means you should aggressively decouple your business logic from the framework while, at the same time, aggressively coupling your controllers and routing *to* the framework in order to get the most out of it. +Controller Action Naming +------------------------ + +.. best-practice:: + + Don't add the ``Action`` suffix to the methods of the controller actions. + +The first Symfony versions required that controller method names ended in +``Action`` (e.g. ``newAction()``, ``showAction()``). This suffix became optional +when annotations were introduced for controllers. In modern Symfony applications +this suffix is neither required nor recommended, so you can safely remove it. + Routing Configuration --------------------- @@ -41,32 +52,30 @@ configuration to the main routing configuration file: .. code-block:: yaml - # app/config/routing.yml - app: - resource: '@AppBundle/Controller/' + # config/routes.yaml + controllers: + resource: '../src/Controller/' type: annotation This configuration will load annotations from any controller stored inside the -``src/AppBundle/Controller/`` directory and even from its subdirectories. -So if your application defines lots of controllers, it's perfectly ok to -reorganize them into subdirectories: +``src/Controller/`` directory and even from its subdirectories. So if your application +defines lots of controllers, it's perfectly ok to reorganize them into subdirectories: .. code-block:: text / ├─ ... └─ src/ - └─ AppBundle/ + ├─ ... + └─ Controller/ + ├─ DefaultController.php ├─ ... - └─ Controller/ - ├─ DefaultController.php + ├─ Api/ + │ ├─ ... + │ └─ ... + └─ Backend/ ├─ ... - ├─ Api/ - │ ├─ ... - │ └─ ... - └─ Backend/ - ├─ ... - └─ ... + └─ ... Template Configuration ---------------------- @@ -91,18 +100,18 @@ What does the Controller look like Considering all this, here is an example of what the controller should look like for the homepage of our app:: - namespace AppBundle\Controller; + namespace App\Controller; - use AppBundle\Entity\Post; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use App\Entity\Post; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; - class DefaultController extends Controller + class DefaultController extends AbstractController { /** * @Route("/", name="homepage") */ - public function indexAction() + public function index() { $posts = $this->getDoctrine() ->getRepository(Post::class) @@ -117,9 +126,9 @@ for the homepage of our app:: Fetching Services ----------------- -If you extend the base ``Controller`` class, you can access services directly from -the container via ``$this->container->get()`` or ``$this->get()``. But instead, you -should use dependency injection to fetch services by +If you extend the base ``AbstractController`` class, you can't access services +directly from the container via ``$this->container->get()`` or ``$this->get()``. +Instead, you must use dependency injection to fetch services by :ref:`type-hinting action method arguments `: .. best-practice:: @@ -145,40 +154,41 @@ to automatically query for an entity and pass it as an argument to your controll For example:: - use AppBundle\Entity\Post; + use App\Entity\Post; use Symfony\Component\Routing\Annotation\Route; /** * @Route("/{id}", name="admin_post_show") */ - public function showAction(Post $post) + public function show(Post $post) { $deleteForm = $this->createDeleteForm($post); return $this->render('admin/post/show.html.twig', [ - 'post' => $post, + 'post' => $post, 'delete_form' => $deleteForm->createView(), ]); } -Normally, you'd expect a ``$id`` argument to ``showAction()``. Instead, by -creating a new argument (``$post``) and type-hinting it with the ``Post`` -class (which is a Doctrine entity), the ParamConverter automatically queries -for an object whose ``$id`` property matches the ``{id}`` value. It will -also show a 404 page if no ``Post`` can be found. +Normally, you'd expect a ``$id`` argument to ``show()``. Instead, by creating a +new argument (``$post``) and type-hinting it with the ``Post`` class (which is a +Doctrine entity), the ParamConverter automatically queries for an object whose +``$id`` property matches the ``{id}`` value. It will also show a 404 page if no +``Post`` can be found. When Things Get More Advanced ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The above example works without any configuration because the wildcard name ``{id}`` matches -the name of the property on the entity. If this isn't true, or if you have -even more complex logic, the easiest thing to do is just query for the entity -manually. In our application, we have this situation in ``CommentController``:: +The above example works without any configuration because the wildcard name +``{id}`` matches the name of the property on the entity. If this isn't true, or +if you have even more complex logic, your best choice is to query for +the entity manually. In our application, we have this situation in +``CommentController``:: /** * @Route("/comment/{postSlug}/new", name="comment_new") */ - public function newAction(Request $request, $postSlug) + public function new(Request $request, $postSlug) { $post = $this->getDoctrine() ->getRepository(Post::class) @@ -194,7 +204,7 @@ manually. In our application, we have this situation in ``CommentController``:: You can also use the ``@ParamConverter`` configuration, which is infinitely flexible:: - use AppBundle\Entity\Post; + use App\Entity\Post; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; @@ -203,7 +213,7 @@ flexible:: * @Route("/comment/{postSlug}/new", name="comment_new") * @ParamConverter("post", options={"mapping"={"postSlug"="slug"}}) */ - public function newAction(Request $request, Post $post) + public function new(Request $request, Post $post) { // ... } diff --git a/best_practices/creating-the-project.rst b/best_practices/creating-the-project.rst index 6c860552abc..e323d6bcf7a 100644 --- a/best_practices/creating-the-project.rst +++ b/best_practices/creating-the-project.rst @@ -4,16 +4,23 @@ Creating the Project Installing Symfony ------------------ -In the past, Symfony projects were created with `Composer`_, the dependency manager -for PHP applications. However, the current recommendation is to use the **Symfony -Installer**, which has to be installed before creating your first project. +.. best-practice:: + + Use Composer and Symfony Flex to create and manage Symfony applications. + +`Composer`_ is the package manager used by modern PHP applications to manage +their dependencies. `Symfony Flex`_ is a Composer plugin designed to automate +some of the most common tasks performed in Symfony applications. Using Flex is +optional but recommended because it improves your productivity significantly. .. best-practice:: - Use the Symfony Installer to create new Symfony-based projects. + Use the Symfony Skeleton to create new Symfony-based projects. -Read the :doc:`/setup` article learn how to install and use the Symfony -Installer. +The `Symfony Skeleton`_ is a minimal and empty Symfony project which you can +base your new projects on. Unlike past Symfony versions, this skeleton installs +the absolute bare minimum amount of dependencies to make a fully working Symfony +project. Read the :doc:`/setup` article to learn more about installing Symfony. .. _linux-and-mac-os-x-systems: .. _windows-systems: @@ -21,87 +28,55 @@ Installer. Creating the Blog Application ----------------------------- -Now that everything is correctly set up, you can create a new project based on -Symfony. In your command console, browse to a directory where you have permission -to create files and execute the following commands: +In your command console, browse to a directory where you have permission to +create files and execute the following commands: .. code-block:: terminal $ cd projects/ - $ symfony new blog - - # Windows - c:\> cd projects/ - c:\projects\> php symfony new blog - -.. note:: - - If the installer doesn't work for you or doesn't output anything, make sure - that the `Phar extension`_ is installed and enabled on your computer. + $ composer create-project symfony/skeleton blog This command creates a new directory called ``blog`` that contains a fresh new -project based on the most recent stable Symfony version available. In addition, -the installer checks if your system meets the technical requirements to execute -Symfony applications. If not, you'll see the list of changes needed to meet those -requirements. +project based on the most recent stable Symfony version available. .. tip:: - Symfony releases are digitally signed for security reasons. If you want to - verify the integrity of your Symfony installation, take a look at the - `public checksums repository`_ and follow `these steps`_ to verify the - signatures. + The technical requirements to run Symfony are simple. If you want to check + if your system meets those requirements, read :doc:`/reference/requirements`. Structuring the Application --------------------------- After creating the application, enter the ``blog/`` directory and you'll see a -number of files and directories generated automatically: +number of files and directories generated automatically. These are the most +important ones: .. code-block:: text blog/ - ├─ app/ - │ ├─ config/ - │ └─ Resources/ - ├─ bin + ├─ bin/ │ └─ console + ├─ config/ + └─ public/ + │ └─ index.php ├─ src/ - │ └─ AppBundle/ + │ └─ Kernel.php ├─ var/ │ ├─ cache/ - │ ├─ logs/ - │ └─ sessions/ - ├─ tests/ - │ └─ AppBundle/ - ├─ vendor/ - └─ web/ + │ └─ log/ + └─ vendor/ This file and directory hierarchy is the convention proposed by Symfony to -structure your applications. The recommended purpose of each directory is the -following: - -* ``app/config/``, stores all the configuration defined for any environment; -* ``app/Resources/``, stores all the templates and the translation files for the - application; -* ``src/AppBundle/``, stores the Symfony specific code (controllers and routes), - your domain code (e.g. Doctrine classes) and all your business logic; -* ``var/cache/``, stores all the cache files generated by the application; -* ``var/logs/``, stores all the log files generated by the application; -* ``var/sessions/``, stores all the session files generated by the application; -* ``tests/AppBundle/``, stores the automatic tests (e.g. Unit tests) of the - application. -* ``vendor/``, this is the directory where Composer installs the application's - dependencies and you should never modify any of its contents; -* ``web/``, stores all the front controller files and all the web assets, such - as stylesheets, JavaScript files and images. +structure your applications. It's recommended to keep this structure because it's +easy to navigate and most directory names are self-explanatory, but you can +:doc:`override the location of any Symfony directory `: Application Bundles ~~~~~~~~~~~~~~~~~~~ When Symfony 2.0 was released, most developers naturally adopted the symfony 1.x way of dividing applications into logical modules. That's why many Symfony -applications use bundles to divide their code into logical features: UserBundle, +applications used bundles to divide their code into logical features: UserBundle, ProductBundle, InvoiceBundle, etc. But a bundle is *meant* to be something that can be reused as a stand-alone @@ -111,68 +86,16 @@ depends on ProductBundle, then there's no advantage to having two separate bundl .. best-practice:: - Create only one bundle called AppBundle for your application logic. - -Implementing a single AppBundle bundle in your projects will make your code -more concise and easier to understand. - -.. note:: - - There is no need to prefix the AppBundle with your own vendor (e.g. - AcmeAppBundle), because this application bundle is never going to be - shared. - -.. note:: - - Another reason to create a new bundle is when you're overriding something - in a vendor's bundle (e.g. a controller). See :doc:`/bundles/inheritance`. - -All in all, this is the typical directory structure of a Symfony application -that follows these best practices: - -.. code-block:: text - - blog/ - ├─ app/ - │ ├─ config/ - │ └─ Resources/ - ├─ bin/ - │ └─ console - ├─ src/ - │ └─ AppBundle/ - ├─ tests/ - │ └─ AppBundle/ - ├─ var/ - │ ├─ cache/ - │ ├─ logs/ - └─ sessions/ - ├─ vendor/ - └─ web/ - ├─ app.php - └─ app_dev.php - -.. tip:: - - If your Symfony installation doesn't come with a pre-generated AppBundle, - you can generate it by hand executing this command: - - .. code-block:: terminal - - $ php bin/console generate:bundle --namespace=AppBundle --dir=src --format=annotation --no-interaction - -Extending the Directory Structure ---------------------------------- + Don't create any bundle to organize your application logic. -If your project or infrastructure requires some changes to the default directory -structure of Symfony, you can -:doc:`override the location of the main directories `: -``cache/``, ``logs/`` and ``web/``. +Symfony applications can still use third-party bundles (installed in ``vendor/``) +to add features, but you should use PHP namespaces instead of bundles to organize +your own code. ---- Next: :doc:`/best_practices/configuration` .. _`Composer`: https://getcomposer.org/ -.. _`Phar extension`: https://php.net/manual/en/intro.phar.php -.. _`public checksums repository`: https://github.com/sensiolabs/checksums -.. _`these steps`: http://fabien.potencier.org/signing-project-releases.html +.. _`Symfony Flex`: https://github.com/symfony/flex +.. _`Symfony Skeleton`: https://github.com/symfony/skeleton diff --git a/best_practices/forms.rst b/best_practices/forms.rst index 4bfd3bf1221..79a95da0be1 100644 --- a/best_practices/forms.rst +++ b/best_practices/forms.rst @@ -12,14 +12,14 @@ Building Forms Define your forms as PHP classes. -The Form component allows you to build forms right inside your controller -code. This is perfectly fine if you don't need to reuse the form somewhere else. -But for organization and reuse, we recommend that you define each -form in its own PHP class:: +The Form component allows you to build forms right inside your controller code. +This is perfectly fine if you don't need to reuse the form somewhere else. But +for organization and reuse, we recommend that you define each form in its own +PHP class:: - namespace AppBundle\Form; + namespace App\Form; - use AppBundle\Entity\Post; + use App\Entity\Post; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -50,16 +50,16 @@ form in its own PHP class:: .. best-practice:: - Put the form type classes in the ``AppBundle\Form`` namespace, unless you + Put the form type classes in the ``App\Form`` namespace, unless you use other custom form classes like data transformers. To use the class, use ``createForm()`` and pass the fully qualified class name:: // ... - use AppBundle\Form\PostType; + use App\Form\PostType; // ... - public function newAction(Request $request) + public function new(Request $request) { $post = new Post(); $form = $this->createForm(PostType::class, $post); @@ -67,14 +67,6 @@ To use the class, use ``createForm()`` and pass the fully qualified class name:: // ... } -Registering Forms as Services -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also :ref:`register your form type as a service `. -This is only needed if your form type requires some dependencies to be injected -by the container, otherwise it is unnecessary overhead and therefore *not* -recommended to do this for all form type classes. - Form Button Configuration ------------------------- @@ -107,25 +99,25 @@ This form *may* have been designed for creating posts, but if you wanted to reuse it for editing posts, the button label would be wrong. Instead, some developers configure form buttons in the controller:: - namespace AppBundle\Controller\Admin; + namespace App\Controller\Admin; + use App\Entity\Post; + use App\Form\PostType; use Symfony\Component\HttpFoundation\Request; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\SubmitType; - use AppBundle\Entity\Post; - use AppBundle\Form\PostType; - class PostController extends Controller + class PostController extends AbstractController { // ... - public function newAction(Request $request) + public function new(Request $request) { $post = new Post(); $form = $this->createForm(PostType::class, $post); $form->add('submit', SubmitType::class, [ 'label' => 'Create', - 'attr' => ['class' => 'btn btn-default pull-right'], + 'attr' => ['class' => 'btn btn-default pull-right'], ]); // ... @@ -142,8 +134,7 @@ view layer: {{ form_start(form) }} {{ form_widget(form) }} - + {{ form_end(form) }} Validation @@ -188,7 +179,7 @@ all of the fields: .. code-block:: html+twig - {{ form_start(form, {'attr': {'class': 'my-form-class'} }) }} + {{ form_start(form, {attr: {class: 'my-form-class'} }) }} {{ form_widget(form) }} {{ form_end(form) }} @@ -202,7 +193,7 @@ Handling Form Submits Handling a form submit usually follows a similar template:: - public function newAction(Request $request) + public function new(Request $request) { // build the form ... @@ -213,26 +204,18 @@ Handling a form submit usually follows a similar template:: $entityManager->persist($post); $entityManager->flush(); - return $this->redirect($this->generateUrl( - 'admin_post_show', - ['id' => $post->getId()] - )); + return $this->redirectToRoute('admin_post_show', [ + 'id' => $post->getId() + ]); } // render the template } -There are really only two notable things here. First, we recommend that you -use a single action for both rendering the form and handling the form submit. -For example, you *could* have a ``newAction()`` that *only* renders the form -and a ``createAction()`` that *only* processes the form submit. Both those -actions will be almost identical. So it's much simpler to let ``newAction()`` -handle everything. - -Second, is it required to call ``$form->isSubmitted()`` in the ``if`` statement -before calling ``isValid()``. Calling ``isValid()`` with an unsubmitted form -is deprecated since version 3.2 and will throw an exception in 4.0. - ----- +We recommend that you use a single action for both rendering the form and +handling the form submit. For example, you *could* have a ``new()`` action that +*only* renders the form and a ``create()`` action that *only* processes the form +submit. Both those actions will be almost identical. So it's much simpler to let +``new()`` handle everything. Next: :doc:`/best_practices/i18n` diff --git a/best_practices/i18n.rst b/best_practices/i18n.rst index 06bec2b8cbb..ca4dd0c4c4b 100644 --- a/best_practices/i18n.rst +++ b/best_practices/i18n.rst @@ -3,20 +3,18 @@ Internationalization Internationalization and localization adapt the applications and their contents to the specific region or language of the users. In Symfony this is an opt-in -feature that needs to be enabled before using it. To do this, uncomment the -following ``translator`` configuration option and set your application locale: +feature that needs to be installed before using it (``composer require symfony/translation``). -.. code-block:: yaml +Translation Source File Location +-------------------------------- + +.. best-practice:: - # app/config/config.yml - framework: - # ... - translator: { fallbacks: ['%locale%'] } + Store the translation files in the ``translations/`` directory at the root + of your project. - # app/config/parameters.yml - parameters: - # ... - locale: en +Your translators' lives will be much easier if all the application translations +are in one central location. Translation Source File Format ------------------------------ @@ -41,21 +39,6 @@ XLIFF notes allow you to define that context. The `PHP Translation Bundle`_ includes advanced extractors that can read your project and automatically update the XLIFF files. -Translation Source File Location --------------------------------- - -.. best-practice:: - - Store the translation files in the ``app/Resources/translations/`` - directory. - -Traditionally, Symfony developers have created these files in the -``Resources/translations/`` directory of each bundle. But since the -``app/Resources/`` directory is considered the global location for the -application's resources, storing translations in ``app/Resources/translations/`` -centralizes them *and* gives them priority over any other translation file. -This let's you override translations defined in third-party bundles. - Translation Keys ---------------- @@ -63,8 +46,8 @@ Translation Keys Always use keys for translations instead of content strings. -Using keys simplifies the management of the translation files because you -can change the original contents without having to update all of the translation +Using keys simplifies the management of the translation files because you can +change the original contents without having to update all of the translation files. Keys should always describe their *purpose* and *not* their location. For @@ -79,7 +62,7 @@ English in the application would be: .. code-block:: xml - + diff --git a/best_practices/introduction.rst b/best_practices/introduction.rst index 1826cfad3b5..c0a2b0cb44d 100644 --- a/best_practices/introduction.rst +++ b/best_practices/introduction.rst @@ -49,8 +49,8 @@ quality. It's also a moving target that will continue to improve. Keep in mind that these are **optional recommendations** that you and your team may or may not follow to develop Symfony applications. If you want to -continue using your own best practices and methodologies, you can of course -do it. Symfony is flexible enough to adapt to your needs. That will never +continue using your own best practices and methodologies, you can still do +that. Symfony is flexible enough to adapt to your needs. That will never change. Who this Book Is for (Hint: It's not a Tutorial) @@ -58,8 +58,8 @@ Who this Book Is for (Hint: It's not a Tutorial) Any Symfony developer, whether you are an expert or a newcomer, can read this guide. But since this isn't a tutorial, you'll need some basic knowledge of -Symfony to follow everything. If you are totally new to Symfony, welcome! -Start with :doc:`The Quick Tour ` tutorial first. +Symfony to follow everything. If you are totally new to Symfony, welcome! and +read the :doc:`Getting Started guides ` first. We've deliberately kept this guide short. We won't repeat explanations that you can find in the vast Symfony documentation, like discussions about Dependency @@ -69,14 +69,13 @@ what you already know. The Application --------------- -In addition to this guide, a sample application has been developed with all these -best practices in mind. This project, called the Symfony Demo application, can -be obtained through the Symfony Installer. First, `download and install`_ the -installer and then execute this command to download the demo application: +In addition to this guide, a sample application called `Symfony Demo`_ has been +developed with all these best practices in mind. Execute this command to download +the demo application: .. code-block:: terminal - $ symfony demo + $ composer create-project symfony/symfony-demo **The demo application is a simple blog engine**, because that will allow us to focus on the Symfony concepts and features without getting buried in difficult @@ -87,9 +86,10 @@ Don't Update Your Existing Applications --------------------------------------- After reading this handbook, some of you may be considering refactoring your -existing Symfony applications. Our recommendation is sound and clear: **you -should not refactor your existing applications to comply with these best -practices**. The reasons for not doing it are various: +existing Symfony applications. Our recommendation is sound and clear: you may +use these best practices for **new applications** but **you should not refactor +your existing applications to comply with these best practices**. The reasons +for not doing it are various: * Your existing applications are not wrong, they just follow another set of guidelines; @@ -103,4 +103,4 @@ practices**. The reasons for not doing it are various: Next: :doc:`/best_practices/creating-the-project` .. _`Fabien Potencier`: https://connect.symfony.com/profile/fabpot -.. _`download and install`: https://symfony.com/download +.. _`Symfony Demo`: https://github.com/symfony/demo diff --git a/best_practices/security.rst b/best_practices/security.rst index 7eb094ef791..f746303d347 100644 --- a/best_practices/security.rst +++ b/best_practices/security.rst @@ -6,10 +6,9 @@ Authentication and Firewalls (i.e. Getting the User's Credentials) You can configure Symfony to authenticate your users using any method you want and to load user information from any source. This is a complex topic, but -the :doc:`Security guide` has a lot of information about -this. +the :doc:`Security guide ` has a lot of information about this. -Regardless of your needs, authentication is configured in ``security.yml``, +Regardless of your needs, authentication is configured in ``security.yaml``, primarily under the ``firewalls`` key. .. best-practice:: @@ -30,9 +29,9 @@ site (or maybe nearly *all* sections), use the ``access_control`` area. .. best-practice:: - Use the ``bcrypt`` encoder for encoding your users' passwords. + Use the ``bcrypt`` encoder for hashing your users' passwords. -If your users have a password, then we recommend encoding it using the ``bcrypt`` +If your users have a password, then we recommend hashing it using the ``bcrypt`` encoder, instead of the traditional SHA-512 hashing encoder. The main advantages of ``bcrypt`` are the inclusion of a *salt* value to protect against rainbow table attacks, and its adaptive nature, which allows to make it slower to @@ -50,14 +49,14 @@ which uses a login form to load users from the database: .. code-block:: yaml - # app/config/security.yml + # config/packages/security.yaml security: encoders: - AppBundle\Entity\User: bcrypt + App\Entity\User: bcrypt providers: database_users: - entity: { class: AppBundle:User, property: username } + entity: { class: App\Entity\User, property: username } firewalls: secured_area: @@ -81,7 +80,7 @@ Authorization (i.e. Denying Access) ----------------------------------- Symfony gives you several ways to enforce authorization, including the ``access_control`` -configuration in :doc:`security.yml `, the +configuration in :doc:`security.yaml `, the :ref:`@Security annotation ` and using :ref:`isGranted ` on the ``security.authorization_checker`` service directly. @@ -90,17 +89,15 @@ service directly. * For protecting broad URL patterns, use ``access_control``; * Whenever possible, use the ``@Security`` annotation; - * Check security directly on the ``security.authorization_checker`` service whenever - you have a more complex situation. + * Check security directly on the ``security.authorization_checker`` service + whenever you have a more complex situation. There are also different ways to centralize your authorization logic, like -with a custom security voter or with ACL. +with a custom security voter: .. best-practice:: - * For fine-grained restrictions, define a custom security voter; - * For restricting access to *any* object by *any* user via an admin - interface, use the Symfony ACL. + Define a custom security voter to implement fine-grained restrictions. .. _best-practices-security-annotation: @@ -121,9 +118,9 @@ Using ``@Security``, this looks like:: * Displays a form to create a new Post entity. * * @Route("/new", name="admin_post_new") - * @Security("has_role('ROLE_ADMIN')") + * @Security("is_granted('ROLE_ADMIN')") */ - public function newAction() + public function new() { // ... } @@ -136,7 +133,7 @@ inside ``@Security``. In the following example, a user can only access the controller if their email matches the value returned by the ``getAuthorEmail()`` method on the ``Post`` object:: - use AppBundle\Entity\Post; + use App\Entity\Post; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Component\Routing\Annotation\Route; @@ -144,7 +141,7 @@ method on the ``Post`` object:: * @Route("/{id}/edit", name="admin_post_edit") * @Security("user.getEmail() == post.getAuthorEmail()") */ - public function editAction(Post $post) + public function edit(Post $post) { // ... } @@ -167,7 +164,7 @@ need to repeat the expression code using Twig syntax: A good solution - if your logic is simple enough - can be to add a new method to the ``Post`` entity that checks if a given user is its author:: - // src/AppBundle/Entity/Post.php + // src/Entity/Post.php // ... class Post @@ -187,7 +184,7 @@ to the ``Post`` entity that checks if a given user is its author:: Now you can reuse this method both in the template and in the security expression:: - use AppBundle\Entity\Post; + use App\Entity\Post; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Component\Routing\Annotation\Route; @@ -195,7 +192,7 @@ Now you can reuse this method both in the template and in the security expressio * @Route("/{id}/edit", name="admin_post_edit") * @Security("post.isAuthor(user)") */ - public function editAction(Post $post) + public function edit(Post $post) { // ... } @@ -221,7 +218,7 @@ more advanced use-case, you can always do the same security check in PHP:: /** * @Route("/{id}/edit", name="admin_post_edit") */ - public function editAction($id) + public function edit($id) { $post = $this->getDoctrine() ->getRepository(Post::class) @@ -237,42 +234,47 @@ more advanced use-case, you can always do the same security check in PHP:: // equivalent code without using the "denyAccessUnlessGranted()" shortcut: // // use Symfony\Component\Security\Core\Exception\AccessDeniedException; + // use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface + // + // ... + // + // public function __construct(AuthorizationCheckerInterface $authorizationChecker) { + // $this->authorizationChecker = $authorizationChecker; + // } + // // ... // - // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) { + // if (!$this->authorizationChecker->isGranted('edit', $post)) { // throw $this->createAccessDeniedException(); // } - + // // ... } Security Voters --------------- -If your security logic is complex and can't be centralized into a method -like ``isAuthor()``, you should leverage custom voters. These are an order -of magnitude easier than :doc:`ACLs ` and will give -you the flexibility you need in almost all cases. +If your security logic is complex and can't be centralized into a method like +``isAuthor()``, you should leverage custom voters. These are much easier than +:doc:`ACLs ` and will give you the flexibility you need in almost +all cases. First, create a voter class. The following example shows a voter that implements the same ``getAuthorEmail()`` logic you used above:: - namespace AppBundle\Security; + namespace App\Security; + use App\Entity\Post; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; - use AppBundle\Entity\Post; class PostVoter extends Voter { const CREATE = 'create'; const EDIT = 'edit'; - /** - * @var AccessDecisionManagerInterface - */ private $decisionManager; public function __construct(AccessDecisionManagerInterface $decisionManager) @@ -304,15 +306,16 @@ the same ``getAuthorEmail()`` logic you used above:: } switch ($attribute) { + // if the user is an admin, allow them to create new posts case self::CREATE: - // if the user is an admin, allow them to create new posts if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) { return true; } break; + + // if the user is the author of the post, allow them to edit the posts case self::EDIT: - // if the user is the author of the post, allow them to edit the posts if ($user->getEmail() === $post->getAuthorEmail()) { return true; } @@ -324,7 +327,7 @@ the same ``getAuthorEmail()`` logic you used above:: } } -If you're using the :ref:`default services.yml configuration `, +If you're using the :ref:`default services.yaml configuration `, your application will :ref:`autoconfigure ` your security voter and inject an ``AccessDecisionManagerInterface`` instance into it thanks to :doc:`autowiring `. @@ -335,7 +338,7 @@ Now, you can use the voter with the ``@Security`` annotation:: * @Route("/{id}/edit", name="admin_post_edit") * @Security("is_granted('edit', post)") */ - public function editAction(Post $post) + public function edit(Post $post) { // ... } @@ -346,42 +349,30 @@ via the even easier shortcut in a controller:: /** * @Route("/{id}/edit", name="admin_post_edit") */ - public function editAction($id) + public function edit($id) { $post = ...; // query for the post $this->denyAccessUnlessGranted('edit', $post); - // or without the shortcut: - // // use Symfony\Component\Security\Core\Exception\AccessDeniedException; + // use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface + // // ... // - // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) { + // public function __construct(AuthorizationCheckerInterface $authorizationChecker) { + // $this->authorizationChecker = $authorizationChecker; + // } + // + // ... + // + // if (!$this->authorizationChecker->isGranted('edit', $post)) { // throw $this->createAccessDeniedException(); // } + // + // ... } -Learn More ----------- - -The `FOSUserBundle`_, developed by the Symfony community, adds support for a -database-backed user system in Symfony. It also handles common tasks like -user registration and forgotten password functionality. - -Enable the :doc:`Remember Me feature ` to -allow your users to stay logged in for a long period of time. - -When providing customer support, sometimes it's necessary to access the application -as some *other* user so that you can reproduce the problem. Symfony provides -the ability to :doc:`impersonate users `. - -If your company uses a user login method not supported by Symfony, you can -develop :doc:`your own user provider ` and -:doc:`your own authentication provider `. - ----- - Next: :doc:`/best_practices/web-assets` .. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html diff --git a/best_practices/templates.rst b/best_practices/templates.rst index 4bfe1f05899..e9970b9a8d7 100644 --- a/best_practices/templates.rst +++ b/best_practices/templates.rst @@ -9,7 +9,7 @@ languages - like `Twig`_ - were created to make templating even better. Use Twig templating format for your templates. -Generally speaking, PHP templates are much more verbose than Twig templates because +Generally speaking, PHP templates are more verbose than Twig templates because they lack native support for lots of modern features needed by templates, like inheritance, automatic escaping and named arguments for filters and functions. @@ -18,41 +18,27 @@ Twig is the default templating format in Symfony and has the largest community support of all non-PHP template engines (it's used in high profile projects such as Drupal 8). -In addition, Twig is the only template format with guaranteed support in Symfony -3.0. As a matter of fact, PHP may be removed from the officially supported -template engines. - Template Locations ------------------ .. best-practice:: - Store all your application's templates in ``app/Resources/views/`` directory. - -Traditionally, Symfony developers stored the application templates in the -``Resources/views/`` directory of each bundle. Then they used the Twig namespaced -path to refer to them (e.g. ``@AcmeDemo/Default/index.html.twig``). - -But for the templates used in your application, it's much more convenient -to store them in the ``app/Resources/views/`` directory. For starters, this -drastically simplifies their logical names: - -============================================ ================================== -Templates Stored inside Bundles Templates Stored in ``app/`` -============================================ ================================== -``@AcmeDemo/index.html.twig`` ``index.html.twig`` -``@AcmeDemo/Default/index.html.twig`` ``default/index.html.twig`` -``@AcmeDemo/Default/subdir/index.html.twig`` ``default/subdir/index.html.twig`` -============================================ ================================== + Store the application templates in the ``templates/`` directory at the root + of your project. -Another advantage is that centralizing your templates simplifies the work -of your designers. They don't need to look for templates in lots of directories -scattered through lots of bundles. +Centralizing your templates in a single location simplifies the work of your +designers. In addition, using this directory simplifies the notation used when +referring to templates (e.g. ``$this->render('admin/post/show.html.twig')`` +instead of ``$this->render('@SomeTwigNamespace/Admin/Posts/show.html.twig')``). .. best-practice:: Use lowercased snake_case for directory and template names. +This recommendation aligns with Twig best practices, where variables and template +names use lowercased snake_case too (e.g. ``user_profile`` instead of ``userProfile`` +and ``edit_form.html.twig`` instead of ``EditForm.html.twig``). + .. best-practice:: Use a prefixed underscore for partial templates in template names. @@ -67,47 +53,33 @@ Twig Extensions .. best-practice:: - Define your Twig extensions in the ``AppBundle/Twig/`` directory. Your + Define your Twig extensions in the ``src/Twig/`` directory. Your application will automatically detect them and configure them. Our application needs a custom ``md2html`` Twig filter so that we can transform -the Markdown contents of each post into HTML. - -To do this, first, install the excellent `Parsedown`_ Markdown parser as -a new dependency of the project: - -.. code-block:: terminal - - $ composer require erusev/parsedown +the Markdown contents of each post into HTML. To do this, create a new +``Markdown`` class that will be used later by the Twig extension. It needs +to define one single method to transform Markdown content into HTML:: -Then, create a new ``Markdown`` class that will be used later by the Twig -extension. It just needs to define one single method to transform -Markdown content into HTML:: - - namespace AppBundle\Utils; + namespace App\Utils; class Markdown { - private $parser; - - public function __construct() - { - $this->parser = new \Parsedown(); - } + // ... - public function toHtml($text) + public function toHtml(string $text): string { return $this->parser->text($text); } } -Next, create a new Twig extension and define a new filter called ``md2html`` -using the ``Twig\TwigFilter`` class. Inject the newly defined ``Markdown`` -class in the constructor of the Twig extension:: +Next, create a new Twig extension and define a filter called ``md2html`` using +the ``Twig\TwigFilter`` class. Inject the newly defined ``Markdown`` class in the +constructor of the Twig extension:: - namespace AppBundle\Twig; + namespace App\Twig; - use AppBundle\Utils\Markdown; + use App\Utils\Markdown; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -123,11 +95,10 @@ class in the constructor of the Twig extension:: public function getFilters() { return [ - new TwigFilter( - 'md2html', - [$this, 'markdownToHtml'], - ['is_safe' => ['html'], 'pre_escape' => 'html'] - ), + new TwigFilter('md2html', [$this, 'markdownToHtml'], [ + 'is_safe' => ['html'], + 'pre_escape' => 'html', + ]), ]; } @@ -135,16 +106,11 @@ class in the constructor of the Twig extension:: { return $this->parser->toHtml($content); } - - public function getName() - { - return 'app_extension'; - } } And that's it! -If you're using the :ref:`default services.yml configuration `, +If you're using the :ref:`default services.yaml configuration `, you're done! Symfony will automatically know about your new service and tag it to be used as a Twig extension. diff --git a/best_practices/tests.rst b/best_practices/tests.rst index 4a26ee7139c..2c1230cc452 100644 --- a/best_practices/tests.rst +++ b/best_practices/tests.rst @@ -1,10 +1,11 @@ Tests ===== -Roughly speaking, there are two types of test. Unit testing allows you to -test the input and output of specific functions. Functional testing allows -you to command a "browser" where you browse to pages on your site, click -links, fill out forms and assert that you see certain things on the page. +Of all the different types of test available, these best practices focus solely +on unit and functional tests. Unit testing allows you to test the input and +output of specific functions. Functional testing allows you to command a +"browser" where you browse to pages on your site, click links, fill out forms +and assert that you see certain things on the page. Unit Tests ---------- @@ -26,11 +27,11 @@ functional tests, you can quickly spot any big errors before you deploy them: Define a functional test that at least checks if your application pages are successfully loading. -A functional test like this is simple to implement thanks to -:ref:`PHPUnit data providers `:: +:ref:`PHPUnit data providers ` help you implement +functional tests:: - // tests/AppBundle/ApplicationAvailabilityFunctionalTest.php - namespace Tests\AppBundle; + // tests/ApplicationAvailabilityFunctionalTest.php + namespace App\Tests; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; @@ -49,14 +50,12 @@ A functional test like this is simple to implement thanks to public function urlProvider() { - return [ - ['/'], - ['/posts'], - ['/post/fixture-post-1'], - ['/blog/category/fixture-category'], - ['/archives'], - // ... - ]; + yield ['/']; + yield ['/posts']; + yield ['/post/fixture-post-1']; + yield ['/blog/category/fixture-category']; + yield ['/archives']; + // ... } } @@ -83,10 +82,13 @@ generator service: Consider the following functional test that uses the ``router`` service to generate the URL of the tested page:: + // ... + private $router; // consider that this holds the Symfony router service + public function testBlogArchives() { $client = self::createClient(); - $url = $client->getContainer()->get('router')->generate('blog_archives'); + $url = $this->router->generate('blog_archives'); $client->request('GET', $url); // ... @@ -104,7 +106,7 @@ The built-in functional testing client is great, but it can't be used to test any JavaScript behavior on your pages. If you need to test this, consider using the `Mink`_ library from within PHPUnit. -Of course, if you have a heavy JavaScript frontend, you should consider using +Of course, if you have a heavy JavaScript front-end, you should consider using pure JavaScript-based testing tools. Learn More about Functional Tests diff --git a/best_practices/web-assets.rst b/best_practices/web-assets.rst index c567bf36e47..271a1fa3eeb 100644 --- a/best_practices/web-assets.rst +++ b/best_practices/web-assets.rst @@ -2,101 +2,33 @@ Web Assets ========== Web assets are things like CSS, JavaScript and image files that make the -frontend of your site look and work great. Symfony developers have traditionally -stored these assets in the ``Resources/public/`` directory of each bundle. +frontend of your site look and work great. .. best-practice:: - Store your assets in the ``web/`` directory. + Store your assets in the ``assets/`` directory at the root of your project. -Scattering your web assets across tens of different bundles makes it more -difficult to manage them. Your designers' lives will be much easier if all -the application assets are in one location. - -Templates also benefit from centralizing your assets, because the links are -much more concise: - -.. code-block:: html+twig - - - - - {# ... #} - - - - -.. note:: - - Keep in mind that ``web/`` is a public directory and that anything stored - here will be publicly accessible, including all the original asset files - (e.g. Sass, LESS and CoffeeScript files). - -Using Assetic -------------- - -.. include:: /assetic/_standard_edition_warning.rst.inc - -These days, you probably can't create static CSS and JavaScript files and -include them in your template without much effort. Instead, you'll probably -want to combine and minify these to improve client-side performance. You may -also want to use LESS or Sass (for example), which means you'll need some way -to process these into CSS files. - -A lot of tools exist to solve these problems, including pure-frontend (non-PHP) -tools like GruntJS. +Your designers' and front-end developers' lives will be much easier if all the +application assets are in one central location. .. best-practice:: - Use Assetic to compile, combine and minimize web assets, unless you're - comfortable with frontend tools like GruntJS. - -:doc:`Assetic ` is an asset manager capable -of compiling assets developed with a lot of different frontend technologies -like LESS, Sass and CoffeeScript. Combining all your assets with Assetic is a -matter of wrapping all the assets with a single Twig tag: - -.. code-block:: html+twig - - {% stylesheets - 'css/bootstrap.min.css' - 'css/main.css' - filter='cssrewrite' output='css/compiled/app.css' %} - - {% endstylesheets %} - - {# ... #} - - {% javascripts - 'js/jquery.min.js' - 'js/bootstrap.min.js' - output='js/compiled/app.js' %} - - {% endjavascripts %} - -Frontend-Based Applications ---------------------------- - -Recently, frontend technologies like AngularJS have become pretty popular -for developing frontend web applications that talk to an API. - -If you are developing an application like this, you should use the tools -that are recommended by the technology, such as Bower and GruntJS. You should -develop your frontend application separately from your Symfony backend (even -separating the repositories if you want). + Use `Webpack Encore`_ to compile, combine and minimize web assets. -Learn More about Assetic ------------------------- +`Webpack`_ is the leading JavaScript module bundler that compiles, transforms +and packages assets for usage in a browser. Webpack Encore is a JavaScript +library that gets rid of most of Webpack complexity without hiding any of its +features or distorting its usage and philosophy. -Assetic can also minimize CSS and JavaScript assets -:doc:`using UglifyCSS/UglifyJS ` to speed up your -websites. You can even :doc:`compress images ` -with Assetic to reduce their size before serving them to the user. Check out -the `official Assetic documentation`_ to learn more about all the available +Webpack Encore was designed to bridge the gap between Symfony applications and +the JavaScript-based tools used in modern web applications. Check out the +`official Webpack Encore documentation`_ to learn more about all the available features. ---- Next: :doc:`/best_practices/tests` -.. _`official Assetic documentation`: https://github.com/kriswallsmith/assetic +.. _`Webpack Encore`: https://github.com/symfony/webpack-encore +.. _`Webpack`: https://webpack.js.org/ +.. _`official Webpack Encore documentation`: https://symfony.com/doc/current/frontend.html diff --git a/bundles.rst b/bundles.rst index 43ccbb10111..2f0fb608c04 100644 --- a/bundles.rst +++ b/bundles.rst @@ -8,73 +8,43 @@ The Bundle System .. caution:: - In Symfony versions prior to 3.4, it was recommended to organize your own + In Symfony versions prior to 4.0, it was recommended to organize your own application code using bundles. This is no longer recommended and bundles should only be used to share code and features between multiple applications. -A bundle is similar to a plugin in other software, but even better. The key -difference is that *everything* is a bundle in Symfony, including both the -core framework functionality and the code written for your application. -Bundles are first-class citizens in Symfony. This gives you the flexibility -to use pre-built features packaged in `third-party bundles`_ or to distribute -your own bundles. It makes it easy to pick and choose which features to enable -in your application and to optimize them the way you want. - -.. note:: - - While you'll learn the basics here, an entire article is devoted to the - organization and best practices of :doc:`bundles `. - -A bundle is simply a structured set of files within a directory that implement -a single feature. You might create a BlogBundle, a ForumBundle or -a bundle for user management (many of these exist already as open source -bundles). Each directory contains everything related to that feature, including -PHP files, templates, stylesheets, JavaScript files, tests and anything else. -Every aspect of a feature exists in a bundle and every feature lives in a -bundle. - -Bundles used in your applications must be enabled by registering them in -the ``registerBundles()`` method of the ``AppKernel`` class:: - - // app/AppKernel.php - public function registerBundles() - { - $bundles = [ - new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new Symfony\Bundle\SecurityBundle\SecurityBundle(), - new Symfony\Bundle\TwigBundle\TwigBundle(), - new Symfony\Bundle\MonologBundle\MonologBundle(), - new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), - new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), - new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), - new AppBundle\AppBundle(), - ]; - - if (in_array($this->getEnvironment(), ['dev', 'test'])) { - $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); - $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); - $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); - } - - return $bundles; - } - -With the ``registerBundles()`` method, you have total control over which bundles -are used by your application (including the core Symfony bundles). +A bundle is similar to a plugin in other software, but even better. The core +features of Symfony framework are implemented with bundles (FrameworkBundle, +SecurityBundle, DebugBundle, etc.) They are also used to add new features in +your application via `third-party bundles`_. + +Bundles used in your applications must be enabled per +:doc:`environment ` in the ``config/bundles.php`` +file:: + + // config/bundles.php + return [ + // 'all' means that the bundle is enabled for any Symfony environment + Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], + // this bundle is enabled only in 'dev' and 'test', so you can't use it in 'prod' + Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], + ]; .. tip:: - A bundle can live *anywhere* as long as it can be autoloaded (via the - autoloader configured at ``app/autoload.php``). + In a default Symfony application that uses :doc:`Symfony Flex `, + bundles are enabled/disabled automatically for you when installing/removing + them, so you don't need to look at or edit this ``bundles.php`` file. Creating a Bundle ----------------- -`SensioGeneratorBundle`_ is an optional bundle that includes commands to create -different elements of your application, such as bundles. If you create lots of -bundles, consider using it. However, this section creates and enables a new -bundle by hand to show how simple it is to do it. - +This section creates and enables a new bundle to show there are only a few steps required. The new bundle is called AcmeTestBundle, where the ``Acme`` portion is just a dummy name that should be replaced by some "vendor" name that represents you or your organization (e.g. ABCTestBundle for some company named ``ABC``). @@ -83,7 +53,7 @@ Start by creating a ``src/Acme/TestBundle/`` directory and adding a new file called ``AcmeTestBundle.php``:: // src/Acme/TestBundle/AcmeTestBundle.php - namespace Acme\TestBundle; + namespace App\Acme\TestBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -100,49 +70,22 @@ called ``AcmeTestBundle.php``:: This empty class is the only piece you need to create the new bundle. Though commonly empty, this class is powerful and can be used to customize the behavior -of the bundle. - -Now that you've created the bundle, enable it via the ``AppKernel`` class:: - - // app/AppKernel.php - public function registerBundles() - { - $bundles = [ - // ... +of the bundle. Now that you've created the bundle, enable it:: - // register your bundle - new Acme\TestBundle\AcmeTestBundle(), - ]; + // config/bundles.php + return [ // ... - - return $bundles; - } + App\Acme\TestBundle\AcmeTestBundle::class => ['all' => true], + ]; And while it doesn't do anything yet, AcmeTestBundle is now ready to be used. -And as easy as this is, Symfony also provides a command-line interface for -generating a basic bundle skeleton: - -.. code-block:: terminal - - $ php bin/console generate:bundle --namespace=Acme/TestBundle - -The bundle skeleton generates a basic controller, template and routing -resource that can be customized. You'll learn more about Symfony's command-line -tools later. - -.. tip:: - - Whenever creating a new bundle or using a third-party bundle, always make - sure the bundle has been enabled in ``registerBundles()``. When using - the ``generate:bundle`` command, this is done for you. - Bundle Directory Structure -------------------------- -The directory structure of a bundle is simple and flexible. By default, the -bundle system follows a set of conventions that help to keep code consistent -between all Symfony bundles. Take a look at AcmeDemoBundle, as it contains some +The directory structure of a bundle is meant to help to keep code consistent +between all Symfony bundles. It follows a set of conventions, but is flexible +to be adjusted if needed. Take a look at AcmeDemoBundle, as it contains some of the most common elements of a bundle: ``Controller/`` @@ -154,14 +97,14 @@ of the most common elements of a bundle: necessary). ``Resources/config/`` - Houses configuration, including routing configuration (e.g. ``routing.yml``). + Houses configuration, including routing configuration (e.g. ``routing.yaml``). ``Resources/views/`` Holds templates organized by controller name (e.g. ``Random/index.html.twig``). ``Resources/public/`` Contains web assets (images, stylesheets, etc) and is copied or symbolically - linked into the project ``web/`` directory via the ``assets:install`` console + linked into the project ``public/`` directory via the ``assets:install`` console command. ``Tests/`` @@ -178,11 +121,10 @@ the bundle. Learn more ---------- -.. toctree:: - :maxdepth: 1 - :glob: - - bundles/* +* :doc:`/bundles/override` +* :doc:`/bundles/best_practices` +* :doc:`/bundles/configuration` +* :doc:`/bundles/extension` +* :doc:`/bundles/prepend_extension` .. _`third-party bundles`: https://github.com/search?q=topic%3Asymfony-bundle&type=Repositories -.. _`SensioGeneratorBundle`: https://symfony.com/doc/current/bundles/SensioGeneratorBundle/index.html diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst index f9fbda249a9..2091016cecf 100644 --- a/bundles/best_practices.rst +++ b/bundles/best_practices.rst @@ -4,21 +4,10 @@ Best Practices for Reusable Bundles =================================== -There are two types of bundles: - -* Application-specific bundles: only used to build your application; -* Reusable bundles: meant to be shared across many projects. - This article is all about how to structure your **reusable bundles** to be -configurable and extendable. Many of these recommendations do not -apply to application bundles because you'll want to keep those as simple -as possible. For application bundles, just follow the practices shown throughout -the guides. - -.. seealso:: - - The best practices for application-specific bundles are discussed in - :doc:`/best_practices/introduction`. +configurable and extendable. Reusable bundles are those meant to be shared +privately across many company projects or publicly so any Symfony project can +install them. .. index:: pair: Bundle; Naming conventions @@ -28,13 +17,13 @@ the guides. Bundle Name ----------- -A bundle is also a PHP namespace. The namespace must follow the `PSR-0`_ or -`PSR-4`_ interoperability standards for PHP namespaces and class names: it starts -with a vendor segment, followed by zero or more category segments, and it ends -with the namespace short name, which must end with ``Bundle``. +A bundle is also a PHP namespace. The namespace must follow the `PSR-4`_ +interoperability standard for PHP namespaces and class names: it starts with a +vendor segment, followed by zero or more category segments, and it ends with the +namespace short name, which must end with ``Bundle``. A namespace becomes a bundle as soon as you add a bundle class to it. The -bundle class name must follow these simple rules: +bundle class name must follow these rules: * Use only alphanumeric characters and underscores; * Use a StudlyCaps name (i.e. camelCase with the first letter uppercased); @@ -124,8 +113,8 @@ Service Container Extensions ``DependencyInjection/`` Doctrine ORM entities (when not using annotations) ``Entity/`` Doctrine ODM documents (when not using annotations) ``Document/`` Event Listeners ``EventListener/`` -Configuration ``Resources/config/`` -Web Resources (CSS, JS, images) ``Resources/public/`` +Configuration (routes, services, etc.) ``Resources/config/`` +Web Assets (CSS, JS, images) ``Resources/public/`` Translation files ``Resources/translations/`` Validation (when not using annotations) ``Resources/config/validation/`` Serialization (when not using annotations) ``Resources/config/serialization/`` @@ -177,6 +166,92 @@ the ``Tests/`` directory. Tests should follow the following principles: A test suite must not contain ``AllTests.php`` scripts, but must rely on the existence of a ``phpunit.xml.dist`` file. +Continuous Integration +---------------------- + +Testing bundle code continuously, including all its commits and pull requests, +is a good practice called Continuous Integration. There are several services +providing this feature for free for open source projects. The most popular +service for Symfony bundles is called `Travis CI`_. + +Here is the recommended configuration file (``.travis.yml``) for Symfony bundles, +which test the two latest :doc:`LTS versions ` +of Symfony and the latest beta release: + +.. code-block:: yaml + + language: php + sudo: false + cache: + directories: + - $HOME/.composer/cache/files + - $HOME/symfony-bridge/.phpunit + + env: + global: + - PHPUNIT_FLAGS="-v" + - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" + + matrix: + fast_finish: true + include: + # Minimum supported dependencies with the latest and oldest PHP version + - php: 7.2 + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" + - php: 7.0 + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" + + # Test the latest stable release + - php: 7.0 + - php: 7.1 + - php: 7.2 + env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" + + # Test LTS versions. This makes sure we do not use Symfony packages with version greater + # than 2 or 3 respectively. Read more at https://github.com/symfony/lts + - php: 7.2 + env: DEPENDENCIES="symfony/lts:^2" + - php: 7.2 + env: DEPENDENCIES="symfony/lts:^3" + + # Latest commit to master + - php: 7.2 + env: STABILITY="dev" + + allow_failures: + # Dev-master is allowed to fail. + - env: STABILITY="dev" + + before_install: + - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi + - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; + - if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi; + + install: + # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355 + - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi + - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction + - ./vendor/bin/simple-phpunit install + + script: + - composer validate --strict --no-check-lock + # simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and + # it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge) + - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS + +Consider using the `Travis cron`_ tool to make sure your project is built even if +there are no new pull requests or commits. + +Installation +------------ + +Bundles should set ``"type": "symfony-bundle"`` in their ``composer.json`` file. +With this, :doc:`Symfony Flex ` will be able to automatically +enable your bundle when it's installed. + +If your bundle requires any setup (e.g. configuration, new files, changes to +``.gitignore``, etc), then you should create a `Symfony Flex recipe`_. + Documentation ------------- @@ -203,8 +278,19 @@ following standardized instructions in your ``README.md`` file. Installation ============ - Step 1: Download the Bundle - --------------------------- + Applications that use Symfony Flex + ---------------------------------- + + Open a command console, enter your project directory and execute: + + ```console + $ composer require + ``` + + Applications that don't use Symfony Flex + ---------------------------------------- + + ### Step 1: Download the Bundle Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle: @@ -217,8 +303,7 @@ following standardized instructions in your ``README.md`` file. in the [installation chapter](https://getcomposer.org/doc/00-intro.md) of the Composer documentation. - Step 2: Enable the Bundle - ------------------------- + ### Step 2: Enable the Bundle Then, enable the bundle by adding it to the list of registered bundles in the `app/AppKernel.php` file of your project: @@ -248,8 +333,20 @@ following standardized instructions in your ``README.md`` file. Installation ============ + Applications that use Symfony Flex + ---------------------------------- + + Open a command console, enter your project directory and execute: + + .. code-block:: bash + + $ composer require + + Applications that don't use Symfony Flex + ---------------------------------------- + Step 1: Download the Bundle - --------------------------- + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle: @@ -262,7 +359,7 @@ following standardized instructions in your ``README.md`` file. in the `installation chapter`_ of the Composer documentation. Step 2: Enable the Bundle - ------------------------- + ~~~~~~~~~~~~~~~~~~~~~~~~~ Then, enable the bundle by adding it to the list of registered bundles in the ``app/AppKernel.php`` file of your project:: @@ -338,13 +435,13 @@ The end user can provide values in any configuration file: .. code-block:: yaml - # app/config/config.yml + # config/services.yaml parameters: acme_blog.author.email: 'fabien@example.com' .. code-block:: xml - + setParameter('acme_blog.author.email', 'fabien@example.com'); Retrieve the configuration parameters in your code from the container:: $container->getParameter('acme_blog.author.email'); -Even if this mechanism is simple enough, you should consider using the more -advanced :doc:`semantic bundle configuration `. +While this mechanism requires the least effort, you should consider using the +more advanced :doc:`semantic bundle configuration ` to +make your configuration more robust. Versioning ---------- @@ -418,9 +516,8 @@ The ``composer.json`` file should include at least the following metadata: a string (or array of strings) with a `valid license identifier`_, such as ``MIT``. ``autoload`` - This information is used by Symfony to load the classes of the bundle. The - `PSR-4`_ autoload standard is recommended for modern bundles, but `PSR-0`_ - standard is also supported. + This information is used by Symfony to load the classes of the bundle. It's + recommended to use the `PSR-4`_ autoload standard. In order to make it easier for developers to find your bundle, register it on `Packagist`_, the official repository for Composer packages. @@ -430,7 +527,7 @@ Resources If the bundle references any resources (config files, translation files, etc.), don't use physical paths (e.g. ``__DIR__/config/services.xml``) but logical -paths (e.g. ``@AppBundle/Resources/config/services.xml``). +paths (e.g. ``@FooBundle/Resources/config/services.xml``). The logical paths are required because of the bundle overriding mechanism that lets you override any resource/file of any bundle. See :ref:`http-kernel-resource-locator` @@ -438,7 +535,7 @@ for more details about transforming physical paths into logical paths. Beware that templates use a simplified version of the logical path shown above. For example, an ``index.html.twig`` template located in the ``Resources/views/Default/`` -directory of the AppBundle, is referenced as ``@App/Default/index.html.twig``. +directory of the FooBundle, is referenced as ``@Foo/Default/index.html.twig``. Learn more ---------- @@ -446,9 +543,11 @@ Learn more * :doc:`/bundles/extension` * :doc:`/bundles/configuration` -.. _`PSR-0`: https://www.php-fig.org/psr/psr-0/ .. _`PSR-4`: https://www.php-fig.org/psr/psr-4/ +.. _`Symfony Flex recipe`: https://github.com/symfony/recipes .. _`Semantic Versioning Standard`: https://semver.org/ .. _`Packagist`: https://packagist.org/ .. _`choose any license`: https://choosealicense.com/ .. _`valid license identifier`: https://spdx.org/licenses/ +.. _`Travis CI`: https://travis-ci.org/ +.. _`Travis Cron`: https://docs.travis-ci.com/user/cron-jobs/ diff --git a/bundles/configuration.rst b/bundles/configuration.rst index 3f111cb382c..b1cdf6af67d 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -5,11 +5,12 @@ How to Create Friendly Configuration for a Bundle ================================================= -If you open your application configuration file (usually ``app/config/config.yml``), -you'll see a number of different configuration sections, such as ``framework``, -``twig`` and ``doctrine``. Each of these configures a specific bundle, allowing -you to define options at a high level and then let the bundle make all the -low-level, complex changes based on your settings. +If you open your main application configuration directory (usually +``config/packages/``), you'll see a number of different files, such as +``framework.yaml``, ``twig.yaml`` and ``doctrine.yaml``. Each of these +configures a specific bundle, allowing you to define options at a high level and +then let the bundle make all the low-level, complex changes based on your +settings. For example, the following configuration tells the FrameworkBundle to enable the form integration, which involves the definition of quite a few services as well @@ -30,7 +31,7 @@ as integration of other related components: xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony - http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -43,29 +44,18 @@ as integration of other related components: 'form' => true, ]); -.. sidebar:: Using Parameters to Configure your Bundle - - If you don't have plans to share your bundle between projects, it doesn't - make sense to use this more advanced way of configuration. Since you use - the bundle only in one project, you can just change the service - configuration each time. - - If you *do* want to be able to configure something from within - ``config.yml``, you can always create a parameter there and use that - parameter somewhere else. - Using the Bundle Extension -------------------------- Imagine you are creating a new bundle - AcmeSocialBundle - which provides -integration with Twitter, etc. To make your bundle easy to use, you want to -allow users to configure it with some configuration that looks like this: +integration with Twitter. To make your bundle configurable to the user, you +can add some configuration that looks like this: .. configuration-block:: .. code-block:: yaml - # app/config/config.yml + # config/packages/acme_social.yaml acme_social: twitter: client_id: 123 @@ -73,7 +63,7 @@ allow users to configure it with some configuration that looks like this: .. code-block:: xml - + loadFromExtension('acme_social', [ 'client_id' => 123, 'client_secret' => 'your_secret', @@ -149,20 +139,20 @@ For the configuration example in the previous section, the array passed to your ] Notice that this is an *array of arrays*, not just a single flat array of the -configuration values. This is intentional, as it allows Symfony to parse -several configuration resources. For example, if ``acme_social`` appears in -another configuration file - say ``config_dev.yml`` - with different values -beneath it, the incoming array might look like this:: +configuration values. This is intentional, as it allows Symfony to parse several +configuration resources. For example, if ``acme_social`` appears in another +configuration file - say ``config/packages/dev/acme_social.yaml`` - with +different values beneath it, the incoming array might look like this:: [ - // values from config.yml + // values from config/packages/acme_social.yaml [ 'twitter' => [ 'client_id' => 123, 'client_secret' => 'your_secret', ], ], - // values from config_dev.yml + // values from config/packages/dev/acme_social.yaml [ 'twitter' => [ 'client_id' => 456, @@ -190,10 +180,9 @@ The ``Configuration`` class to handle the sample configuration looks like:: { public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('acme_social'); + $treeBuilder = new TreeBuilder('acme_social'); - $rootNode + $treeBuilder->getRootNode() ->children() ->arrayNode('twitter') ->children() @@ -208,6 +197,10 @@ The ``Configuration`` class to handle the sample configuration looks like:: } } +.. deprecated:: 4.2 + + Not passing the root node name to ``TreeBuilder`` was deprecated in Symfony 4.2. + .. seealso:: The ``Configuration`` class can be much more complicated than shown here, @@ -306,7 +299,7 @@ In your extension, you can load this and dynamically set its arguments:: .. sidebar:: Processing the Configuration yourself Using the Config component is fully optional. The ``load()`` method gets an - array of configuration values. You can simply parse these arrays yourself + array of configuration values. You can instead parse these arrays yourself (e.g. by overriding configurations and using :phpfunction:`isset` to check for the existence of a value). Be aware that it'll be very hard to support XML:: @@ -324,12 +317,10 @@ In your extension, you can load this and dynamically set its arguments:: Modifying the Configuration of Another Bundle --------------------------------------------- -If you have multiple bundles that depend on each other, it may be useful -to allow one ``Extension`` class to modify the configuration passed to another -bundle's ``Extension`` class, as if the end-developer has actually placed that -configuration in their ``app/config/config.yml`` file. This can be achieved -using a prepend extension. For more details, see -:doc:`/bundles/prepend_extension`. +If you have multiple bundles that depend on each other, it may be useful to +allow one ``Extension`` class to modify the configuration passed to another +bundle's ``Extension`` class. This can be achieved using a prepend extension. +For more details, see :doc:`/bundles/prepend_extension`. Dump the Configuration ---------------------- @@ -401,7 +392,7 @@ namespace is then replaced with the XSD validation base path returned from method. This namespace is then followed by the rest of the path from the base path to the file itself. -By convention, the XSD file lives in the ``Resources/config/schema``, but you +By convention, the XSD file lives in the ``Resources/config/schema/``, but you can place it anywhere you like. You should return this path as the base path:: // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php @@ -422,7 +413,7 @@ Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be .. code-block:: xml - + ` -to return the correct DI alias. The DI alias is the name used to refer to the -bundle in the container (e.g. in the ``app/config/config.yml`` file). By +method to return the correct DI alias. The DI alias is the name used to refer to +the bundle in the container (e.g. in the ``config/packages/`` files). By default, this is done by removing the ``Extension`` suffix and converting the class name to underscores (e.g. ``AcmeHelloExtension``'s DI alias is ``acme_hello``). @@ -87,11 +83,10 @@ container. In the ``load()`` method, you can use PHP code to register service definitions, but it is more common if you put these definitions in a configuration file -(using the Yaml, XML or PHP format). Luckily, you can use the file loaders in -the extension! +(using the YAML, XML or PHP format). For instance, assume you have a file called ``services.xml`` in the -``Resources/config`` directory of your bundle, your ``load()`` method looks like:: +``Resources/config/`` directory of your bundle, your ``load()`` method looks like:: use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\Config\FileLocator; @@ -106,67 +101,37 @@ For instance, assume you have a file called ``services.xml`` in the $loader->load('services.xml'); } -Other available loaders are the ``YamlFileLoader``, ``PhpFileLoader`` and -``IniFileLoader``. - -.. note:: - - The ``IniFileLoader`` can only be used to load parameters and it can only - load them as strings. - -.. caution:: - - If you removed the default file with service definitions (i.e. - ``app/config/services.yml``), make sure to also remove it from the - ``imports`` key in ``app/config/config.yml``. +The other available loaders are ``YamlFileLoader`` and ``PhpFileLoader``. Using Configuration to Change the Services ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Extension is also the class that handles the configuration for that -particular bundle (e.g. the configuration in ``app/config/config.yml``). To -read more about it, see the ":doc:`/bundles/configuration`" article. +particular bundle (e.g. the configuration in ``config/packages/.yaml``). +To read more about it, see the ":doc:`/bundles/configuration`" article. Adding Classes to Compile ------------------------- -.. deprecated:: 3.3 - - This technique is discouraged and the ``addClassesToCompile()`` method was - deprecated in Symfony 3.3 because modern PHP versions make it unnecessary. - -Symfony creates a big ``classes.php`` file in the cache directory to aggregate -the contents of the PHP classes that are used in every request. This reduces the -I/O operations and increases the application performance. +Bundles can hint Symfony about which of their classes contain annotations so +they are compiled when generating the application cache to improve the overall +performance. Define the list of annotated classes to compile in the +``addAnnotatedClassesToCompile()`` method:: -.. versionadded:: 3.2 - - The ``addAnnotatedClassesToCompile()`` method was added in Symfony 3.2. - -Your bundles can also add their own classes into this file thanks to the -``addClassesToCompile()`` and ``addAnnotatedClassesToCompile()`` methods (both -work in the same way, but the second one is for classes that contain PHP -annotations). Define the classes to compile as an array of their fully qualified -class names:: - - use AppBundle\Manager\UserManager; - use AppBundle\Utils\Slugger; + use App\Manager\UserManager; + use App\Utils\Slugger; // ... public function load(array $configs, ContainerBuilder $container) { // ... - // this method can't compile classes that contain PHP annotations - $this->addClassesToCompile([ - UserManager::class, - Slugger::class, - // ... - ]); - - // add here only classes that contain PHP annotations $this->addAnnotatedClassesToCompile([ - 'AppBundle\\Controller\\DefaultController', + // you can define the fully qualified class names... + 'App\\Controller\\DefaultController', + // ... but glob patterns are also supported: + '**Bundle\\Controller\\', + // ... ]); } @@ -176,28 +141,6 @@ class names:: If some class extends from other classes, all its parents are automatically included in the list of classes to compile. -.. versionadded:: 3.2 - - The option to add classes to compile using patterns was introduced in Symfony 3.2. - -The classes to compile can also be added using file path patterns:: - - // ... - public function load(array $configs, ContainerBuilder $container) - { - // ... - - $this->addClassesToCompile([ - '**Bundle\\Manager\\', - // ... - ]); - - $this->addAnnotatedClassesToCompile([ - '**Bundle\\Controller\\', - // ... - ]); - } - Patterns are transformed into the actual class namespaces using the classmap generated by Composer. Therefore, before using these patterns, you must generate the full classmap executing the ``dump-autoload`` command of Composer. diff --git a/bundles/index.rst b/bundles/index.rst index 87641de5e23..78dd8c6d4fb 100644 --- a/bundles/index.rst +++ b/bundles/index.rst @@ -1,14 +1,14 @@ +:orphan: + Bundles ======= .. toctree:: :maxdepth: 2 - installation - best_practices - inheritance override - remove - extension + inheritance + best_practices configuration + extension prepend_extension diff --git a/bundles/inheritance.rst b/bundles/inheritance.rst index 201b5f18fec..d8ce372adb4 100644 --- a/bundles/inheritance.rst +++ b/bundles/inheritance.rst @@ -6,107 +6,6 @@ How to Use Bundle Inheritance to Override Parts of a Bundle .. caution:: - Bundle inheritance is deprecated since Symfony 3.4 and will be removed in - 4.0. - -When working with third-party bundles, you'll probably come across a situation -where you want to override a file in that third-party bundle with a file -in one of your own bundles. Symfony gives you a very convenient way to override -things like controllers, templates, and other files in a bundle's -``Resources/`` directory. - -For example, suppose that you have installed `FOSUserBundle`_, but you want to -override its base ``layout.html.twig`` template, as well as one of its -controllers. - -First, create a new bundle called UserBundle and enable it in your application. -Then, register the third-party FOSUserBundle as the "parent" of your bundle:: - - // src/UserBundle/UserBundle.php - namespace UserBundle; - - use Symfony\Component\HttpKernel\Bundle\Bundle; - - class UserBundle extends Bundle - { - public function getParent() - { - return 'FOSUserBundle'; - } - } - -By making this small change, you can now override several parts of the FOSUserBundle -by creating a file with the same name. - -.. note:: - - Despite the method name, there is no parent/child relationship between - the bundles, it is just a way to extend and override an existing bundle. - -Overriding Controllers -~~~~~~~~~~~~~~~~~~~~~~ - -Suppose you want to add some functionality to the ``registerAction()`` of a -``RegistrationController`` that lives inside FOSUserBundle. To do so, -just create your own ``RegistrationController.php`` file, override the bundle's -original method, and change its functionality:: - - // src/UserBundle/Controller/RegistrationController.php - namespace UserBundle\Controller; - - use FOS\UserBundle\Controller\RegistrationController as BaseController; - - class RegistrationController extends BaseController - { - public function registerAction() - { - $response = parent::registerAction(); - - // ... do custom stuff - return $response; - } - } - -.. tip:: - - Depending on how severely you need to change the behavior, you might - call ``parent::registerAction()`` or completely replace its logic with - your own. - -.. note:: - - Overriding controllers in this way only works if the bundle refers to - the controller using the standard ``FOSUserBundle:Registration:register`` - syntax in routes and templates. This is the best practice. - -Overriding Resources: Templates, Routing, etc -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Most resources can also be overridden by creating a file in the same location -as your parent bundle. - -For example, it's very common to need to override the FOSUserBundle's -``layout.html.twig`` template so that it uses your application's base layout. -Since the file lives at ``Resources/views/layout.html.twig`` in the FOSUserBundle, -you can create your own file in the same location of UserBundle. Symfony will -ignore the file that lives inside the FOSUserBundle entirely, and use your file -instead. - -The same goes for routing files and some other resources. - -.. note:: - - The overriding of resources only works when you refer to resources with - the ``@FOSUserBundle/Resources/config/routing/security.xml`` method. - You need to use the ``@BundleName`` shortcut when referring to resources - so they can be successfully overridden (except templates, which are - overridden in a different way, as explained in :doc:`/templating/overriding`). - -.. caution:: - - Translation and validation files do not work in the same way as described - above. Read ":ref:`override-translations`" if you want to learn how to - override translations and see ":ref:`override-validation`" for tricks to - override the validation. - -.. _`FOSUserBundle`: https://github.com/friendsofsymfony/fosuserbundle + Bundle inheritance was removed in Symfony 4.0, but you can + :doc:`override any part of a bundle ` without + using bundle inheritance. diff --git a/bundles/installation.rst b/bundles/installation.rst deleted file mode 100644 index c8f3e4ec1d5..00000000000 --- a/bundles/installation.rst +++ /dev/null @@ -1,160 +0,0 @@ -.. index:: - single: Bundle; Installation - -How to Install 3rd Party Bundles -================================ - -Most bundles provide their own installation instructions. However, the -basic steps for installing a bundle are the same: - -* `A) Add Composer Dependencies`_ -* `B) Enable the Bundle`_ -* `C) Configure the Bundle`_ - -A) Add Composer Dependencies ----------------------------- - -Dependencies are managed with Composer, so if Composer is new to you, learn -some basics in `their documentation`_. This involves two steps: - -1) Find out the Name of the Bundle on Packagist -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The README for a bundle (e.g. `FOSUserBundle`_) usually tells you its name -(e.g. ``friendsofsymfony/user-bundle``). If it doesn't, you can search for -the bundle on the `Packagist.org`_ site. - -.. tip:: - - Looking for bundles? Try searching for `symfony-bundle topic on GitHub`_. - -2) Install the Bundle via Composer -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Now that you know the package name, you can install it via Composer: - -.. code-block:: terminal - - $ composer require friendsofsymfony/user-bundle - -This will choose the best version for your project, add it to ``composer.json`` -and download its code into the ``vendor/`` directory. If you need a specific -version, include it as the second argument of the `composer require`_ command: - -.. code-block:: terminal - - $ composer require friendsofsymfony/user-bundle "~2.0" - -B) Enable the Bundle --------------------- - -At this point, the bundle is installed in your Symfony project (e.g. -``vendor/friendsofsymfony/``) and the autoloader recognizes its classes. -The only thing you need to do now is register the bundle in ``AppKernel``:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - // ... - - public function registerBundles() - { - $bundles = [ - // ... - new FOS\UserBundle\FOSUserBundle(), - ]; - - // ... - } - } - -In a few rare cases, you may want a bundle to be *only* enabled in the development -:doc:`environment `. For example, -the DoctrineFixturesBundle helps to load dummy data - something you probably -only want to do while developing. To only load this bundle in the ``dev`` -and ``test`` environments, register the bundle in this way:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - // ... - - public function registerBundles() - { - $bundles = [ - // ... - ]; - - if (in_array($this->getEnvironment(), ['dev', 'test'])) { - $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(); - } - - // ... - } - } - -C) Configure the Bundle ------------------------ - -It's pretty common for a bundle to need some additional setup or configuration -in ``app/config/config.yml``. The bundle's documentation will tell you about -the configuration, but you can also get a reference of the bundle's configuration -via the ``config:dump-reference`` command: - -.. code-block:: terminal - - $ php bin/console config:dump-reference AsseticBundle - -Instead of the full bundle name, you can also pass the short name used as the root -of the bundle's configuration: - -.. code-block:: terminal - - $ php bin/console config:dump-reference assetic - -The output will look like this: - -.. code-block:: yaml - - assetic: - debug: '%kernel.debug%' - use_controller: - enabled: '%kernel.debug%' - profiler: false - read_from: '%kernel.project_dir%/web' - write_to: '%assetic.read_from%' - java: /usr/bin/java - node: /usr/local/bin/node - node_paths: [] - # ... - -.. tip:: - - For complex bundles that define lots of configuration options, you can pass - a second optional argument to the ``config:dump-reference`` command to only - display a section of the entire configuration: - - .. code-block:: terminal - - $ php bin/console config:dump-reference AsseticBundle use_controller - - # Default configuration for "AsseticBundle" at path "use_controller" - use_controller: - enabled: '%kernel.debug%' - profiler: false - -Other Setup ------------ - -At this point, check the ``README`` file of your brand new bundle to see -what to do next. Have fun! - -.. _their documentation: https://getcomposer.org/doc/00-intro.md -.. _Packagist.org: https://packagist.org -.. _FOSUserBundle: https://github.com/FriendsOfSymfony/FOSUserBundle -.. _`composer require`: https://getcomposer.org/doc/03-cli.md#require -.. _`symfony-bundle topic on GitHub`: https://github.com/search?q=topic%3Asymfony-bundle&type=Repositories diff --git a/bundles/override.rst b/bundles/override.rst index a66c277fe2f..b73b245d13b 100644 --- a/bundles/override.rst +++ b/bundles/override.rst @@ -4,33 +4,72 @@ How to Override any Part of a Bundle ==================================== -This document is a quick reference for how to override different parts of -third-party bundles without using :doc:`/bundles/inheritance`, which is -deprecated since Symfony 3.4. +When using a third-party bundle, you might want to customize or override some of +its features. This document describes ways of overriding the most common +features of a bundle. .. tip:: The bundle overriding mechanism means that you cannot use physical paths to refer to bundle's resources (e.g. ``__DIR__/config/services.xml``). Always - use logical paths in your bundles (e.g. ``@AppBundle/Resources/config/services.xml``) + use logical paths in your bundles (e.g. ``@FooBundle/Resources/config/services.xml``) and call the :ref:`locateResource() method ` to turn them into physical paths when needed. +.. _override-templates: + Templates --------- -See :doc:`/templating/overriding`. +Third-party bundle templates can be overridden in the +``/templates/bundles//`` directory. The new templates +must use the same name and path (relative to ``/Resources/views/``) as +the original templates. + +For example, to override the ``Resources/views/Registration/confirmed.html.twig`` +template from the FOSUserBundle, create this template: +``/templates/bundles/FOSUserBundle/Registration/confirmed.html.twig`` + +.. caution:: + + If you add a template in a new location, you *may* need to clear your + cache (``php bin/console cache:clear``), even if you are in debug mode. + +Instead of overriding an entire template, you may just want to override one or +more blocks. However, since you are overriding the template you want to extend +from, you would end up in an infinite loop error. The solution is to use the +special ``!`` prefix in the template name to tell Symfony that you want to +extend from the original template, not from the overridden one: + +.. code-block:: twig + + {# templates/bundles/FOSUserBundle/Registration/confirmed.html.twig #} + {# the special '!' prefix avoids errors when extending from an overridden template #} + {% extends "@!FOSUserBundle/Registration/confirmed.html.twig" %} + + {% block some_block %} + ... + {% endblock %} + +.. _templating-overriding-core-templates: + +.. tip:: + + Symfony internals use some bundles too, so you can apply the same technique + to override the core Symfony templates. For example, you can + :doc:`customize error pages ` overriding TwigBundle + templates. Routing ------- Routing is never automatically imported in Symfony. If you want to include the routes from any bundle, then they must be manually imported from somewhere -in your application (e.g. ``app/config/routing.yml``). +in your application (e.g. ``config/routes.yaml``). The easiest way to "override" a bundle's routing is to never import it at -all. Instead of importing a third-party bundle's routing, copy that -routing file into your application, modify it, and import it instead. +all. Instead of importing a third-party bundle's routing, copy +that routing file into your application, modify it, and import it instead. Controllers ----------- @@ -86,7 +125,7 @@ to a new validation group: .. code-block:: yaml - # src/Acme/UserBundle/Resources/config/validation.yml + # config/validator/validation.yaml FOS\UserBundle\Model\User: properties: plainPassword: @@ -99,7 +138,7 @@ to a new validation group: .. code-block:: xml - + `. +Translations are not related to bundles, but to :ref:`translation domains `. +For this reason, you can override any bundle translation file from the main +``translations/`` directory, as long as the new file uses the same domain. + +For example, to override the translations defined in the +``Resources/translations/FOSUserBundle.es.yml`` file of the FOSUserBundle, +create a``/translations/FOSUserBundle.es.yml`` file. .. _`the Doctrine documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#overrides diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst index b9f2d7adc0b..642da040128 100644 --- a/bundles/prepend_extension.rst +++ b/bundles/prepend_extension.rst @@ -12,11 +12,10 @@ users to choose to remove functionality they are not using. Creating multiple bundles has the drawback that configuration becomes more tedious and settings often need to be repeated for various bundles. -It is possible to remove the disadvantage of the multiple bundle approach -by enabling a single Extension to prepend the settings for any bundle. -It can use the settings defined in the ``app/config/config.yml`` -to prepend settings just as if they had been written explicitly by -the user in the application configuration. +It is possible to remove the disadvantage of the multiple bundle approach by +enabling a single Extension to prepend the settings for any bundle. It can use +the settings defined in the ``config/*`` files to prepend settings just as if +they had been written explicitly by the user in the application configuration. For example, this could be used to configure the entity manager name to use in multiple bundles. Or it can be used to enable an optional feature that depends @@ -50,7 +49,7 @@ prepend settings to a bundle extension developers can use the :method:`Symfony\\Component\\DependencyInjection\\ContainerBuilder::prependExtensionConfig` method on the :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder` instance. As this method only prepends settings, any other settings done explicitly -inside the ``app/config/config.yml`` would override these prepended settings. +inside the ``config/*`` files would override these prepended settings. The following example illustrates how to prepend a configuration setting in multiple bundles as well as disable a flag in multiple bundles @@ -73,7 +72,7 @@ in case a specific other bundle is not registered:: // acme_something and acme_other // // note that if the user manually configured - // use_acme_goodbye to true in app/config/config.yml + // use_acme_goodbye to true in config/services.yaml // then the setting would in the end be true and not false $container->prependExtensionConfig($name, $config); break; @@ -96,14 +95,15 @@ in case a specific other bundle is not registered:: } The above would be the equivalent of writing the following into the -``app/config/config.yml`` in case AcmeGoodbyeBundle is not registered and the -``entity_manager_name`` setting for ``acme_hello`` is set to ``non_default``: +``config/packages/acme_something.yaml`` in case AcmeGoodbyeBundle is not +registered and the ``entity_manager_name`` setting for ``acme_hello`` is set to +``non_default``: .. configuration-block:: .. code-block:: yaml - # app/config/config.yml + # config/packages/acme_something.yaml acme_something: # ... use_acme_goodbye: false @@ -115,7 +115,7 @@ The above would be the equivalent of writing the following into the .. code-block:: xml - + loadFromExtension('acme_something', [ // ... 'use_acme_goodbye' => false, diff --git a/bundles/remove.rst b/bundles/remove.rst deleted file mode 100644 index 0c27eacb68b..00000000000 --- a/bundles/remove.rst +++ /dev/null @@ -1,96 +0,0 @@ -.. index:: - single: Bundle; Removing a bundle - -How to Remove a Bundle -====================== - -1. Unregister the Bundle in the ``AppKernel`` ---------------------------------------------- - -To disconnect the bundle from the framework, you should remove the bundle from -the ``AppKernel::registerBundles()`` method. The bundle will likely be found in -the ``$bundles`` array declaration or added to it in a later statement if the -bundle is only registered in the development environment:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - public function registerBundles() - { - $bundles = [ - new Acme\DemoBundle\AcmeDemoBundle(), - ]; - - if (in_array($this->getEnvironment(), ['dev', 'test'])) { - // comment or remove this line: - // $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); - // ... - } - } - } - -2. Remove Bundle Configuration ------------------------------- - -Now that Symfony doesn't know about the bundle, you need to remove any -configuration and routing configuration inside the ``app/config`` directory -that refers to the bundle. - -2.1 Remove Bundle Routing -~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Some* bundles require you to import routing configuration. Check for references -to the bundle in ``app/config/routing.yml`` and ``app/config/routing_dev.yml``. -If you find any references, remove them completely. - -2.2 Remove Bundle Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some bundles contain configuration in one of the ``app/config/config*.yml`` -files. Be sure to remove the related configuration from these files. You can -quickly spot bundle configuration by looking for an ``acme_demo`` (or whatever -the name of the bundle is, e.g. ``fos_user`` for the FOSUserBundle) string in -the configuration files. - -3. Remove the Bundle from the Filesystem ----------------------------------------- - -Now you have removed every reference to the bundle in your application, you -should remove the bundle from the filesystem. The bundle will be located in -`src/` for example the ``src/Acme/DemoBundle`` directory. You should remove this -directory, and any parent directories that are now empty (e.g. ``src/Acme/``). - -.. tip:: - - If you don't know the location of a bundle, you can use the - :method:`Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface::getPath` method - to get the path of the bundle:: - - dump($this->container->get('kernel')->getBundle('AcmeDemoBundle')->getPath()); - die(); - -3.1 Remove Bundle Assets -~~~~~~~~~~~~~~~~~~~~~~~~ - -Remove the assets of the bundle in the web/ directory (e.g. -``web/bundles/acmedemo`` for the AcmeDemoBundle). - -4. Remove Integration in other Bundles --------------------------------------- - -Some bundles rely on other bundles, if you remove one of the two, the other -will probably not work. Be sure that no other bundles, third party or self-made, -rely on the bundle you are about to remove. - -.. tip:: - - If one bundle relies on another, in most cases it means that it uses - some services from the bundle. Searching for the bundle alias string may - help you spot them (e.g. ``acme_demo`` for bundles depending on AcmeDemoBundle). - -.. tip:: - - If a third party bundle relies on another bundle, you can find that bundle - mentioned in the ``composer.json`` file included in the bundle directory. diff --git a/components/asset.rst b/components/asset.rst index b550f2ed036..474a1d03704 100644 --- a/components/asset.rst +++ b/components/asset.rst @@ -155,7 +155,7 @@ corresponding output file: "...": "..." } -In those cases, use the +In those cases, use the :class:`Symfony\\Component\\Asset\\VersionStrategy\\JsonManifestVersionStrategy`:: use Symfony\Component\Asset\Package; @@ -282,8 +282,8 @@ You can also pass a schema-agnostic URL:: // result: //static.example.com/images/logo.png?v1 This is useful because assets will automatically be requested via HTTPS if -a visitor is viewing your site in https. Just make sure that your CDN host -supports https. +a visitor is viewing your site in https. If you want to use this, make sure +that your CDN host supports HTTPS. In case you serve assets from more than one domain to improve application performance, pass an array of URLs as the first argument to the ``UrlPackage`` @@ -372,6 +372,32 @@ document inside a template:: echo $packages->getUrl('resume.pdf', 'doc'); // result: /somewhere/deep/for/documents/resume.pdf?v1 +Local Files and Other Protocols +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In addition to HTTP this component supports other protocols (such as ``file://`` +and ``ftp://``). This allows for example to serve local files in order to +improve performance:: + + use Symfony\Component\Asset\UrlPackage; + // ... + + $localPackage = new UrlPackage( + 'file:///path/to/images/', + new EmptyVersionStrategy() + ); + + $ftpPackage = new UrlPackage( + 'ftp://example.com/images/', + new EmptyVersionStrategy() + ); + + echo $localPackage->getUrl('/logo.png'); + // result: file:///path/to/images/logo.png + + echo $ftpPackage->getUrl('/logo.png'); + // result: ftp://example.com/images/logo.png + Learn more ---------- diff --git a/components/browser_kit.rst b/components/browser_kit.rst index 81bd04f01b6..ce1cbec9077 100644 --- a/components/browser_kit.rst +++ b/components/browser_kit.rst @@ -81,17 +81,35 @@ The value returned by the ``request()`` method is an instance of the :doc:`DomCrawler component `, which allows accessing and traversing HTML elements programmatically. +The :method:`Symfony\\Component\\BrowserKit\\Client::xmlHttpRequest` method, +which defines the same arguments as the ``request()`` method, is a shortcut to +make AJAX requests:: + + use Acme\Client; + + $client = new Client(); + // the required HTTP_X_REQUESTED_WITH header is added automatically + $crawler = $client->xmlHttpRequest('GET', '/'); + Clicking Links ~~~~~~~~~~~~~~ -The ``Crawler`` object is capable of simulating link clicks. First, pass the -text content of the link to the ``selectLink()`` method, which returns a -``Link`` object. Then, pass this object to the ``click()`` method, which -performs the needed HTTP GET request to simulate the link click:: +The ``Client`` object is capable of simulating link clicks. Pass the text +content of the link and the client will perform the needed HTTP GET request to +simulate the link click:: use Acme\Client; $client = new Client(); + $client->request('GET', '/product/123'); + + $crawler = $client->clickLink('Go elsewhere...'); + +If you need the :class:`Symfony\\Component\\DomCrawler\\Link` object that +provides access to the link properties (e.g. ``$link->getMethod()``, +``$link->getUri()``), use this other method: + + // ... $crawler = $client->request('GET', '/product/123'); $link = $crawler->selectLink('Go elsewhere...')->link(); $client->click($link); @@ -99,27 +117,48 @@ performs the needed HTTP GET request to simulate the link click:: Submitting Forms ~~~~~~~~~~~~~~~~ -The ``Crawler`` object is also capable of selecting forms. First, select any of -the form's buttons with the ``selectButton()`` method. Then, use the ``form()`` -method to select the form which the button belongs to. - -After selecting the form, fill in its data and send it using the ``submit()`` -method (which makes the needed HTTP POST request to submit the form contents):: +The ``Client`` object is also capable of submitting forms. First, select the +form using any of its buttons and then override any of its properties (method, +field values, etc.) before submitting it:: use Acme\Client; - // make a real request to an external site $client = new Client(); $crawler = $client->request('GET', 'https://github.com/login'); + // find the form with the 'Log in' button and submit it + // 'Log in' can be the text content, id, value or name of a - {{ form_end(form) }} - -See :doc:`/form/form_customization` for more details. - -Update your Database Schema ---------------------------- - -If you've updated the ``User`` entity during this tutorial, you have to update -your database schema using this command: - -.. code-block:: terminal - - $ php bin/console doctrine:schema:update --force - -That's it! Head to ``/register`` to try things out! - -.. _registration-form-via-email: - -Having a Registration form with only Email (no Username) --------------------------------------------------------- - -If you want your users to login via email and you don't need a username, then you -can remove it from your ``User`` entity entirely. Instead, make ``getUsername()`` -return the ``email`` property:: - - // src/AppBundle/Entity/User.php - // ... - - class User implements UserInterface - { - // ... - - public function getUsername() - { - return $this->email; - } - - // ... - } - -Next, just update the ``providers`` section of your ``security.yml`` file -so that Symfony knows how to load your users via the ``email`` property on -login. See :ref:`authenticating-someone-with-a-custom-entity-provider`. + + {{ form_end(registrationForm) }} + {% endblock %} Adding a "accept terms" Checkbox -------------------------------- @@ -428,7 +213,7 @@ that you'll never need. To do this, add a ``termsAccepted`` field to your form, but set its :ref:`mapped ` option to ``false``:: - // src/AppBundle/Form/UserType.php + // src/Form/UserType.php // ... use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -452,6 +237,11 @@ To do this, add a ``termsAccepted`` field to your form, but set its The :ref:`constraints ` option is also used, which allows us to add validation, even though there is no ``termsAccepted`` property on ``User``. +Manually Authenticating after Success +------------------------------------- + +If you're using Guard authentication, you can :ref:`automatically authenticate ` +after registration is successful. The generator may have already configured your +controller to take advantage of this. + .. _`CVE-2013-5750`: https://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form -.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle -.. _`bcrypt`: https://en.wikipedia.org/wiki/Bcrypt diff --git a/doctrine/repository.rst b/doctrine/repository.rst deleted file mode 100644 index 38432085719..00000000000 --- a/doctrine/repository.rst +++ /dev/null @@ -1,100 +0,0 @@ -.. index:: - single: Doctrine; Custom Repository Class - -How to Create custom Repository Classes -======================================= - -Constructing and using complex queries inside controllers complicate the -maintenance of your application. In order to isolate, reuse and test these -queries, it's a good practice to create a custom repository class for your -entity. Methods containing your query logic can then be stored in this class. - -To do this, add the repository class name to your entity's mapping definition: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/AppBundle/Entity/Product.php - namespace AppBundle\Entity; - - use Doctrine\ORM\Mapping as ORM; - - /** - * @ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository") - */ - class Product - { - // ... - } - - .. code-block:: yaml - - # src/AppBundle/Resources/config/doctrine/Product.orm.yml - AppBundle\Entity\Product: - type: entity - repositoryClass: AppBundle\Repository\ProductRepository - # ... - - .. code-block:: xml - - - - - - - - - - - -Then, create an empty ``AppBundle\Repository\ProductRepository`` class extending -from ``Doctrine\ORM\EntityRepository``. - -Next, add a new method - ``findAllOrderedByName()`` - to the newly-generated -``ProductRepository`` class. This method will query for all the ``Product`` -entities, ordered alphabetically by name:: - - // src/AppBundle/Repository/ProductRepository.php - namespace AppBundle\Repository; - - use Doctrine\ORM\EntityRepository; - - class ProductRepository extends EntityRepository - { - public function findAllOrderedByName() - { - return $this->getEntityManager() - ->createQuery( - 'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC' - ) - ->getResult(); - } - } - -.. tip:: - - The entity manager can be accessed via ``$this->getEntityManager()`` - from inside the repository. - -You can use this new method just like the default finder methods of the repository:: - - use AppBundle\Entity\Product; - // ... - - public function listAction() - { - $products = $this->getDoctrine() - ->getRepository(Product::class) - ->findAllOrderedByName(); - } - -.. note:: - - When using a custom repository class, you still have access to the default - finder methods such as ``find()`` and ``findAll()``. diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst index cc334d1a9b1..c7e6f7821a9 100644 --- a/doctrine/resolve_target_entity.rst +++ b/doctrine/resolve_target_entity.rst @@ -39,8 +39,8 @@ brevity) to explain how to set up and use the ``ResolveTargetEntityListener``. A Customer entity:: - // src/AppBundle/Entity/Customer.php - namespace AppBundle\Entity; + // src/Entity/Customer.php + namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Acme\CustomerBundle\Entity\Customer as BaseCustomer; @@ -109,17 +109,17 @@ about the replacement: .. code-block:: yaml - # app/config/config.yml + # config/packages/doctrine.yaml doctrine: # ... orm: # ... resolve_target_entities: - Acme\InvoiceBundle\Model\InvoiceSubjectInterface: AppBundle\Entity\Customer + Acme\InvoiceBundle\Model\InvoiceSubjectInterface: App\Entity\Customer .. code-block:: xml - + + https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd"> - AppBundle\Entity\Customer + App\Entity\Customer .. code-block:: php - // app/config/config.php + // config/packages/doctrine.php use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; - use AppBundle\Entity\Customer; + use App\Entity\Customer; $container->loadFromExtension('doctrine', [ 'orm' => [ diff --git a/doctrine/reverse_engineering.rst b/doctrine/reverse_engineering.rst index 25523d00f82..50274e0f5bc 100644 --- a/doctrine/reverse_engineering.rst +++ b/doctrine/reverse_engineering.rst @@ -47,8 +47,7 @@ to a post record thanks to a foreign key constraint. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; Before diving into the recipe, be sure your database connection parameters are -correctly setup in the ``app/config/parameters.yml`` file (or wherever your -database configuration is kept). +correctly setup in the ``.env`` file (or ``.env.local`` override file). The first step towards building entity classes from an existing database is to ask Doctrine to introspect the database and generate the corresponding @@ -57,113 +56,60 @@ table fields. .. code-block:: terminal - $ php bin/console doctrine:mapping:import --force AppBundle xml + $ php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity This command line tool asks Doctrine to introspect the database and generate -the XML metadata files under the ``src/AppBundle/Resources/config/doctrine`` -folder of your bundle. This generates two files: ``BlogPost.orm.xml`` and -``BlogComment.orm.xml``. +new PHP classes with annotation metadata into ``src/Entity``. This generates two +files: ``BlogPost.php`` and ``BlogComment.php``. .. tip:: - It's also possible to generate the metadata files in YAML format by changing - the last argument to ``yml``. + It's also possible to generate the metadata files into XML or YAML: -The generated ``BlogPost.orm.xml`` metadata file looks as follows: + .. code-block:: terminal -.. code-block:: xml + $ php bin/console doctrine:mapping:import "App\Entity" xml --path=config/doctrine - - - - - - - - - - - + In this case, make sure to adapt your mapping configuration accordingly: -Once the metadata files are generated, you can ask Doctrine to build related -entity classes by executing the following command. + .. code-block:: yaml + + # config/packages/doctrine.yaml + doctrine: + # ... + orm: + # ... + mappings: + App: + is_bundle: false + type: yml # Set to xml in case of XML mapping + dir: '%kernel.project_dir%/config/doctrine' + prefix: 'App\Entity' + alias: App + +Generating the Getters & Setters or PHP Classes +----------------------------------------------- + +The generated PHP classes now have properties and annotation metadata, but they +do *not* have any getter or setter methods. If you generated XML or YAML metadata, +you don't even have the PHP classes! + +To generate the missing getter/setter methods (or to *create* the classes if neceesary), +run: .. code-block:: terminal - // generates entity classes with annotation mappings - $ php bin/console doctrine:mapping:convert annotation ./src - -.. caution:: - - If you want to use annotations, you must remove the XML (or YAML) files - after running this command. This is necessary as - :ref:`it is not possible to mix mapping configuration formats ` - -For example, the newly created ``BlogComment`` entity class looks as follow:: - - // src/AppBundle/Entity/BlogComment.php - namespace AppBundle\Entity; - - use Doctrine\ORM\Mapping as ORM; - - /** - * @ORM\Table(name="blog_comment") - * @ORM\Entity - */ - class BlogComment - { - /** - * @var integer $id - * - * @ORM\Column(name="id", type="bigint") - * @ORM\Id - * @ORM\GeneratedValue(strategy="IDENTITY") - */ - private $id; - - /** - * @var string $author - * - * @ORM\Column(name="author", type="string", length=100, nullable=false) - */ - private $author; - - /** - * @var text $content - * - * @ORM\Column(name="content", type="text", nullable=false) - */ - private $content; - - /** - * @var datetime $createdAt - * - * @ORM\Column(name="created_at", type="datetime", nullable=false) - */ - private $createdAt; - - /** - * @var BlogPost - * - * @ORM\ManyToOne(targetEntity="BlogPost") - * @ORM\JoinColumn(name="post_id", referencedColumnName="id") - */ - private $post; - } - -As you can see, Doctrine converts all table fields to pure private and annotated -class properties. The most impressive thing is that it also discovered the -relationship with the ``BlogPost`` entity class based on the foreign key constraint. -Consequently, you can find a private ``$post`` property mapped with a ``BlogPost`` -entity in the ``BlogComment`` entity class. + // generates getter/setter methods + $ php bin/console make:entity --regenerate App .. note:: - If you want to have a one-to-many relationship, you will need to add - it manually into the entity or to the generated XML or YAML files. - Add a section on the specific entities for one-to-many defining the - ``inversedBy`` and the ``mappedBy`` pieces. + If you want to have a OneToMany relationship, you will need to add + it manually into the entity (e.g. add a ``comments`` property to ``BlogPost``) + or to the generated XML or YAML files. Add a section on the specific entities + for one-to-many defining the ``inversedBy`` and the ``mappedBy`` pieces. The generated entities are now ready to be used. Have fun! .. _`Doctrine tools documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html#reverse-engineering +.. _`doctrine/doctrine#729`: https://github.com/doctrine/DoctrineBundle/issues/729 diff --git a/email.rst b/email.rst index f0a1f181c18..29bb7a1d171 100644 --- a/email.rst +++ b/email.rst @@ -4,112 +4,71 @@ How to Send an Email ==================== -Sending emails is a classic task for any web application and one that has -special complications and potential pitfalls. Instead of recreating the wheel, -one solution to send emails is to use the SwiftmailerBundle, which leverages -the power of the `Swift Mailer`_ library. This bundle comes with the Symfony -Standard Edition. +Symfony provides a mailer feature based on the popular `Swift Mailer`_ library +via the `SwiftMailerBundle`_. This mailer supports sending messages with your +own mail servers as well as using popular email providers like `Mandrill`_, +`SendGrid`_, and `Amazon SES`_. + +Installation +------------ + +In applications using :doc:`Symfony Flex `, run this command to +install the Swift Mailer based mailer before using it: + +.. code-block:: terminal + + $ composer require symfony/swiftmailer-bundle + +If your application doesn't use Symfony Flex, follow the installation +instructions on `SwiftMailerBundle`_. .. _swift-mailer-configuration: Configuration ------------- -To use Swift Mailer, you'll need to configure it for your mail server. - -.. tip:: - - Instead of setting up/using your own mail server, you may want to use - a hosted mail provider such as `Mandrill`_, `SendGrid`_, `Amazon SES`_ - or others. These give you an SMTP server, username and password (sometimes - called keys) that can be used with the Swift Mailer configuration. - -In a standard Symfony installation, some ``swiftmailer`` configuration is -already included: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - swiftmailer: - transport: '%mailer_transport%' - host: '%mailer_host%' - username: '%mailer_user%' - password: '%mailer_password%' - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('swiftmailer', [ - 'transport' => "%mailer_transport%", - 'host' => "%mailer_host%", - 'username' => "%mailer_user%", - 'password' => "%mailer_password%", - ]); - -These values (e.g. ``%mailer_transport%``), are reading from the parameters -that are set in the :ref:`parameters.yml ` file. You -can modify the values in that file, or set the values directly here. - -The following configuration attributes are available: - -* ``transport`` (``smtp``, ``mail``, ``sendmail``, or ``gmail``) -* ``username`` -* ``password`` -* ``host`` -* ``port`` -* ``encryption`` (``tls``, or ``ssl``) -* ``auth_mode`` (``plain``, ``login``, or ``cram-md5``) -* ``spool`` - - * ``type`` (how to queue the messages, ``file`` or ``memory`` is supported, see :doc:`/email/spool`) - * ``path`` (where to store the messages) -* ``delivery_addresses`` (an array of email addresses where to send ALL emails) -* ``disable_delivery`` (set to true to disable delivery completely) +The ``config/packages/swiftmailer.yaml`` file that's created when installing the +mailer provides all the initial config needed to send emails, except your mail +server connection details. Those parameters are defined in the ``MAILER_URL`` +environment variable in the ``.env`` file: + +.. code-block:: bash + + # .env (or override MAILER_URL in .env.local to avoid committing your changes) + + # use this to disable email delivery + MAILER_URL=null://localhost + + # use this to configure a traditional SMTP server + MAILER_URL=smtp://localhost:25?encryption=ssl&auth_mode=login&username=&password= .. caution:: - Starting from SwiftMailer 5.4.5, the ``mail`` transport is deprecated - and will be removed in version 6. Consider using another transport like - ``smtp``, ``sendmail`` or ``gmail``. + If the username, password or host contain any character considered special in a + URI (such as ``+``, ``@``, ``$``, ``#``, ``/``, ``:``, ``*``, ``!``), you must + encode them. See `RFC 3986`_ for the full list of reserved characters or use the + :phpfunction:`urlencode` function to encode them. + +Refer to the :doc:`SwiftMailer configuration reference ` +for the detailed explanation of all the available config options. Sending Emails -------------- The Swift Mailer library works by creating, configuring and then sending ``Swift_Message`` objects. The "mailer" is responsible for the actual delivery -of the message and is accessible via the ``mailer`` service. Overall, sending -an email is pretty straightforward:: +of the message and is accessible via the ``Swift_Mailer`` service. Overall, +sending an email is pretty straightforward:: - public function indexAction($name, \Swift_Mailer $mailer) + public function index($name, \Swift_Mailer $mailer) { $message = (new \Swift_Message('Hello Email')) ->setFrom('send@example.com') ->setTo('recipient@example.com') ->setBody( $this->renderView( - // app/Resources/views/Emails/registration.html.twig - 'Emails/registration.html.twig', + // templates/emails/registration.html.twig + 'emails/registration.html.twig', ['name' => $name] ), 'text/html' @@ -118,7 +77,7 @@ an email is pretty straightforward:: * If you also want to include a plaintext version of the message ->addPart( $this->renderView( - 'Emails/registration.txt.twig', + 'emails/registration.txt.twig', ['name' => $name] ), 'text/plain' @@ -128,9 +87,6 @@ an email is pretty straightforward:: $mailer->send($message); - // or, you can also fetch the mailer service this way - // $this->get('mailer')->send($message); - return $this->render(...); } @@ -140,7 +96,7 @@ template might look something like this: .. code-block:: html+twig - {# app/Resources/views/Emails/registration.html.twig #} + {# templates/emails/registration.html.twig #}

You did it! You registered!

Hi {{ name }}! You're successfully registered. @@ -154,20 +110,70 @@ template might look something like this: The ``$message`` object supports many more options, such as including attachments, -adding HTML content, and much more. Fortunately, Swift Mailer covers the topic -of `Creating Messages`_ in great detail in its documentation. +adding HTML content, and much more. Refer to the `Creating Messages`_ section +of the Swift Mailer documentation for more details. + +.. _email-using-gmail: + +Using Gmail to Send Emails +-------------------------- + +During development, you might prefer to send emails using Gmail instead of +setting up a regular SMTP server. To do that, update the ``MAILER_URL`` of your +``.env`` file to this: + +.. code-block:: bash + + # username is your full Gmail or Google Apps email address + MAILER_URL=gmail://username:password@localhost + +The ``gmail`` transport is a shortcut that uses the ``smtp`` transport, ``ssl`` +encryption, ``login`` auth mode and ``smtp.gmail.com`` host. If your app uses +other encryption or auth mode, you must override those values +(:doc:`see mailer config reference `): + +.. code-block:: bash + + # username is your full Gmail or Google Apps email address + MAILER_URL=gmail://username:password@localhost?encryption=tls&auth_mode=oauth + +If your Gmail account uses 2-Step-Verification, you must `generate an App password`_ +and use it as the value of the mailer password. You must also ensure that you +`allow less secure applications to access your Gmail account`_. + +Using Cloud Services to Send Emails +----------------------------------- + +Cloud mailing services are a popular option for companies that don't want to set +up and maintain their own reliable mail servers. To use these services in a +Symfony app, update the value of ``MAILER_URL`` in the ``.env`` +file. For example, for `Amazon SES`_ (Simple Email Service): + +.. code-block:: bash + + # The host will be different depending on your AWS zone + # The username/password credentials are obtained from the Amazon SES console + MAILER_URL=smtp://email-smtp.us-east-1.amazonaws.com:587?encryption=tls&username=YOUR_SES_USERNAME&password=YOUR_SES_PASSWORD + +Use the same technique for other mail services, as most of the time there is +nothing more to it than configuring an SMTP endpoint. Learn more ---------- .. toctree:: :maxdepth: 1 - :glob: - email/* + email/dev_environment + email/spool + email/testing .. _`Swift Mailer`: http://swiftmailer.org/ +.. _`SwiftMailerBundle`: https://github.com/symfony/swiftmailer-bundle .. _`Creating Messages`: https://swiftmailer.symfony.com/docs/messages.html .. _`Mandrill`: https://mandrill.com/ .. _`SendGrid`: https://sendgrid.com/ .. _`Amazon SES`: http://aws.amazon.com/ses/ +.. _`generate an App password`: https://support.google.com/accounts/answer/185833 +.. _`allow less secure applications to access your Gmail account`: https://support.google.com/accounts/answer/6010255 +.. _`RFC 3986`: https://www.ietf.org/rfc/rfc3986.txt diff --git a/email/cloud.rst b/email/cloud.rst deleted file mode 100644 index 51103f1190d..00000000000 --- a/email/cloud.rst +++ /dev/null @@ -1,120 +0,0 @@ -.. index:: - single: Emails; Using the cloud - -How to Use the Cloud to Send Emails -=================================== - -Requirements for sending emails from a production system differ from your -development setup as you don't want to be limited in the number of emails, -the sending rate or the sender address. Thus, -:doc:`using Gmail ` or similar services is not an -option. If setting up and maintaining your own reliable mail server causes -you a headache there's a simple solution: Leverage the cloud to send your -emails. - -This article shows how to integrate `Amazon's Simple Email Service (SES)`_ -into Symfony. - -.. note:: - - You can use the same technique for other mail services, as most of the - time there is nothing more to it than configuring an SMTP endpoint for - Swift Mailer. - -In the Symfony configuration, change the Swift Mailer settings ``transport``, -``host``, ``port`` and ``encryption`` according to the information provided in -the `SES console`_. Create your individual SMTP credentials in the SES console -and complete the configuration with the provided ``username`` and ``password``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - swiftmailer: - transport: smtp - host: email-smtp.us-east-1.amazonaws.com - port: 587 # different ports are available, see SES console - encryption: tls # TLS encryption is required - username: AWS_SES_SMTP_USERNAME # to be created in the SES console - password: AWS_SES_SMTP_PASSWORD # to be created in the SES console - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('swiftmailer', [ - 'transport' => 'smtp', - 'host' => 'email-smtp.us-east-1.amazonaws.com', - 'port' => 587, - 'encryption' => 'tls', - 'username' => 'AWS_SES_SMTP_USERNAME', - 'password' => 'AWS_SES_SMTP_PASSWORD', - ]); - -The ``port`` and ``encryption`` keys are not present in the Symfony Standard -Edition configuration by default, but you can add them if needed. - -And that's it, you're ready to start sending emails through the cloud! - -.. tip:: - - If you are using the Symfony Standard Edition, configure the parameters in - ``parameters.yml`` and use them in your configuration files. This allows - for different Swift Mailer configurations for each installation of your - application. For instance, use Gmail during development and the cloud in - production. - - .. code-block:: yaml - - # app/config/parameters.yml - parameters: - # ... - mailer_transport: smtp - mailer_host: email-smtp.us-east-1.amazonaws.com - mailer_port: 587 # different ports are available, see SES console - mailer_encryption: tls # TLS encryption is required - mailer_user: AWS_SES_SMTP_USERNAME # to be created in the SES console - mailer_password: AWS_SES_SMTP_PASSWORD # to be created in the SES console - -.. note:: - - If you intend to use Amazon SES, please note the following: - - * You have to sign up to `Amazon Web Services (AWS)`_; - - * Every sender address used in the ``From`` or ``Return-Path`` (bounce - address) header needs to be confirmed by the owner. You can also - confirm an entire domain; - - * Initially you are in a restricted sandbox mode. You need to request - production access before being allowed to send to arbitrary - recipients; - - * SES may be subject to a charge. - -.. _`Amazon's Simple Email Service (SES)`: http://aws.amazon.com/ses -.. _`SES console`: https://console.aws.amazon.com/ses -.. _`Amazon Web Services (AWS)`: http://aws.amazon.com diff --git a/email/dev_environment.rst b/email/dev_environment.rst index d28fbbdbb3c..c3f62dc340a 100644 --- a/email/dev_environment.rst +++ b/email/dev_environment.rst @@ -16,23 +16,21 @@ address (with optional exceptions). Disabling Sending ----------------- -You can disable sending email by setting the ``disable_delivery`` option -to ``true``. This is the default in the ``test`` environment in the Standard -distribution. If you do this in the ``test`` specific config then email -will not be sent when you run tests, but will continue to be sent in the -``prod`` and ``dev`` environments: +You can disable sending email by setting the ``disable_delivery`` option to +``true``, which is the default value used by Symfony in the ``test`` environment +(email messages will continue to be sent in the other environments): .. configuration-block:: .. code-block:: yaml - # app/config/config_test.yml + # config/packages/test/swiftmailer.yaml swiftmailer: disable_delivery: true .. code-block:: xml - + loadFromExtension('swiftmailer', [ 'disable_delivery' => "true", ]); -If you'd also like to disable deliver in the ``dev`` environment, -add this same configuration to the ``config_dev.yml`` file. - .. _sending-to-a-specified-address: Sending to a Specified Address(es) @@ -67,13 +62,13 @@ via the ``delivery_addresses`` option: .. code-block:: yaml - # app/config/config_dev.yml + # config/packages/dev/swiftmailer.yaml swiftmailer: delivery_addresses: ['dev@example.com'] .. code-block:: xml - + loadFromExtension('swiftmailer', [ - 'delivery_addresses' => ["dev@example.com"], + 'delivery_addresses' => ['dev@example.com'], ]); -Now, suppose you're sending an email to ``recipient@example.com``:: +Now, suppose you're sending an email to ``recipient@example.com`` in a controller:: - public function indexAction($name, \Swift_Mailer $mailer) + public function index($name, \Swift_Mailer $mailer) { $message = (new \Swift_Message('Hello Email')) ->setFrom('send@example.com') @@ -141,7 +136,7 @@ by adding the ``delivery_whitelist`` option: .. code-block:: yaml - # app/config/config_dev.yml + # config/packages/dev/swiftmailer.yaml swiftmailer: delivery_addresses: ['dev@example.com'] delivery_whitelist: @@ -152,7 +147,7 @@ by adding the ``delivery_whitelist`` option: .. code-block:: xml - + loadFromExtension('swiftmailer', [ 'delivery_addresses' => ["dev@example.com"], 'delivery_whitelist' => [ @@ -205,20 +200,20 @@ the web debug toolbar will not display an email icon or a report on the next page. Instead, you can set the ``intercept_redirects`` option to ``true`` in the -``config_dev.yml`` file, which will cause the redirect to stop and allow -you to open the report with details of the sent emails. +``dev`` environment, which will cause the redirect to stop and allow you to open +the report with details of the sent emails. .. configuration-block:: .. code-block:: yaml - # app/config/config_dev.yml + # config/packages/dev/web_profiler.yaml web_profiler: intercept_redirects: true .. code-block:: xml - + + https://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd"> loadFromExtension('web_profiler', [ 'intercept_redirects' => 'true', ]); diff --git a/email/gmail.rst b/email/gmail.rst deleted file mode 100644 index 291bafd5956..00000000000 --- a/email/gmail.rst +++ /dev/null @@ -1,133 +0,0 @@ -.. index:: - single: Emails; Gmail - -How to Use Gmail to Send Emails -=============================== - -During development, instead of using a regular SMTP server to send emails, you -might find using Gmail easier and more practical. You can achieve this with the -SwiftmailerBundle without much effort. - -In the development configuration file, change the ``transport`` setting to -``gmail`` and set the ``username`` and ``password`` to the Google credentials: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - swiftmailer: - transport: gmail - username: your_gmail_username - password: your_gmail_password - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config_dev.php - $container->loadFromExtension('swiftmailer', [ - 'transport' => 'gmail', - 'username' => 'your_gmail_username', - 'password' => 'your_gmail_password', - ]); - -.. tip:: - - It's more convenient to configure these options in the ``parameters.yml`` - file: - - .. code-block:: yaml - - # app/config/parameters.yml - parameters: - # ... - mailer_user: your_gmail_username - mailer_password: your_gmail_password - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - swiftmailer: - transport: gmail - username: '%mailer_user%' - password: '%mailer_password%' - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config_dev.php - $container->loadFromExtension('swiftmailer', [ - 'transport' => 'gmail', - 'username' => '%mailer_user%', - 'password' => '%mailer_password%', - ]); - -Redefining the Default Configuration Parameters ------------------------------------------------ - -The ``gmail`` transport is a shortcut that uses the ``smtp`` transport -and sets these options: - -============== ================== -Option Value -============== ================== -``encryption`` ``ssl`` -``auth_mode`` ``login`` -``host`` ``smtp.gmail.com`` -============== ================== - -If your application uses ``tls`` encryption or ``oauth`` authentication, you -must override the default options by defining the ``encryption`` and ``auth_mode`` -parameters. - -If your Gmail account uses 2-Step-Verification, you must `generate an App password`_ -and use it as the value of the ``mailer_password`` parameter. You must also ensure -that you `allow less secure applications to access your Gmail account`_. - -.. seealso:: - - See the :doc:`Swiftmailer configuration reference ` - for more details. - -.. _`generate an App password`: https://support.google.com/accounts/answer/185833 -.. _`allow less secure applications to access your Gmail account`: https://support.google.com/accounts/answer/6010255 diff --git a/email/spool.rst b/email/spool.rst index 744240382fc..de6b5adbd26 100644 --- a/email/spool.rst +++ b/email/spool.rst @@ -4,16 +4,18 @@ How to Spool Emails =================== -When you are using the SwiftmailerBundle to send an email from a Symfony -application, it will default to sending the email immediately. You may, however, -want to avoid the performance hit of the communication between Swift Mailer -and the email transport, which could cause the user to wait for the next -page to load while the email is sending. This can be avoided by choosing -to "spool" the emails instead of sending them directly. This means that Swift Mailer -does not attempt to send the email but instead saves the message to somewhere -such as a file. Another process can then read from the spool and take care -of sending the emails in the spool. Currently only spooling to file or memory is supported -by Swift Mailer. +The default behavior of the Symfony mailer is to send the email messages +immediately. You may, however, want to avoid the performance hit of the +communication to the email server, which could cause the user to wait for the +next page to load while the email is sending. This can be avoided by choosing to +"spool" the emails instead of sending them directly. + +This makes the mailer to not attempt to send the email message but instead save +it somewhere such as a file. Another process can then read from the spool and +take care of sending the emails in the spool. Currently only spooling to file or +memory is supported. + +.. _email-spool-memory: Spool Using Memory ------------------ @@ -21,20 +23,20 @@ Spool Using Memory When you use spooling to store the emails to memory, they will get sent right before the kernel terminates. This means the email only gets sent if the whole request got executed without any unhandled exception or any errors. To configure -swiftmailer with the memory option, use the following configuration: +this spool, use the following configuration: .. configuration-block:: .. code-block:: yaml - # app/config/config.yml + # config/packages/swiftmailer.yaml swiftmailer: # ... spool: { type: memory } .. code-block:: xml - + loadFromExtension('swiftmailer', [ // ... 'spool' => ['type' => 'memory'], @@ -71,7 +73,7 @@ In order to use the spool with files, use the following configuration: .. code-block:: yaml - # app/config/config.yml + # config/packages/swiftmailer.yaml swiftmailer: # ... spool: @@ -80,7 +82,7 @@ In order to use the spool with files, use the following configuration: .. code-block:: xml - + loadFromExtension('swiftmailer', [ // ... @@ -117,7 +119,7 @@ In order to use the spool with files, use the following configuration: .. code-block:: yaml - path: '%kernel.project_dir%/app/spool' + path: '%kernel.project_dir%/var/spool' Now, when your app sends an email, it will not actually be sent but instead added to the spool. Sending the messages from the spool is done separately. @@ -125,21 +127,21 @@ There is a console command to send the messages in the spool: .. code-block:: terminal - $ php bin/console swiftmailer:spool:send --env=prod + $ APP_ENV=prod php bin/console swiftmailer:spool:send It has an option to limit the number of messages to be sent: .. code-block:: terminal - $ php bin/console swiftmailer:spool:send --message-limit=10 --env=prod + $ APP_ENV=prod php bin/console swiftmailer:spool:send --message-limit=10 You can also set the time limit in seconds: .. code-block:: terminal - $ php bin/console swiftmailer:spool:send --time-limit=10 --env=prod + $ APP_ENV=prod php bin/console swiftmailer:spool:send --time-limit=10 -Of course you will not want to run this manually in reality. Instead, the +You will most likely not want to run this command manually in reality. Instead, the console command should be triggered by a cron job or scheduled task and run at a regular interval. diff --git a/email/testing.rst b/email/testing.rst index 58ba1cebd13..8ca65047d9e 100644 --- a/email/testing.rst +++ b/email/testing.rst @@ -12,7 +12,7 @@ content or any other headers, you can use :doc:`the Symfony Profiler Start with a controller action that sends an email:: - public function sendEmailAction($name, \Swift_Mailer $mailer) + public function sendEmail($name, \Swift_Mailer $mailer) { $message = (new \Swift_Message('Hello Email')) ->setFrom('send@example.com') @@ -22,14 +22,14 @@ Start with a controller action that sends an email:: $mailer->send($message); - return $this->render(...); + // ... } In your functional test, use the ``swiftmailer`` collector on the profiler to get information about the messages sent on the previous request:: - // tests/AppBundle/Controller/MailControllerTest.php - namespace Tests\AppBundle\Controller; + // tests/Controller/MailControllerTest.php + namespace App\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; @@ -73,8 +73,8 @@ Problem: The Collector Object Is ``null`` The email collector is only available when the profiler is enabled and collects information, as explained in :doc:`/testing/profiling`. -Problem: The Collector Doesn't Contain the E-Mail -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Problem: The Collector Doesn't Contain the Email +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If a redirection is performed after sending the email (for example when you send an email after a form is processed and before redirecting to another page), make diff --git a/event_dispatcher.rst b/event_dispatcher.rst index 8d0f540136f..f86f1d476ad 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -23,8 +23,8 @@ Creating an Event Listener The most common way to listen to an event is to register an **event listener**:: - // src/AppBundle/EventListener/ExceptionListener.php - namespace AppBundle\EventListener; + // src/EventListener/ExceptionListener.php + namespace App\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpFoundation\Response; @@ -67,7 +67,7 @@ The most common way to listen to an event is to register an **event listener**:: Check out the :doc:`Symfony events reference ` to see what type of object each event provides. -Now that the class is created, you just need to register it as a service and +Now that the class is created, you need to register it as a service and notify Symfony that it is a "listener" on the ``kernel.exception`` event by using a special "tag": @@ -75,15 +75,15 @@ using a special "tag": .. code-block:: yaml - # app/config/services.yml + # config/services.yaml services: - AppBundle\EventListener\ExceptionListener: + App\EventListener\ExceptionListener: tags: - { name: kernel.event_listener, event: kernel.exception } .. code-block:: xml - + - + @@ -99,27 +99,35 @@ using a special "tag": .. code-block:: php - // app/config/services.php - use AppBundle\EventListener\ExceptionListener; + // config/services.php + use App\EventListener\ExceptionListener; $container ->autowire(ExceptionListener::class) ->addTag('kernel.event_listener', ['event' => 'kernel.exception']) ; -.. note:: +Symfony follows this logic to decide which method to execute inside the event +listener class: + +#. If the ``kernel.event_listener`` tag defines the ``method`` attribute, that's + the name of the method to be executed; +#. If no ``method`` attribute is defined, try to execute the method whose name + is ``on`` + "camel-cased event name" (e.g. ``onKernelException()`` method for + the ``kernel.exception`` event); +#. If that method is not defined either, try to execute the ``__invoke()`` magic + method (which makes event listeners invokable); +#. If the ``_invoke()`` method is not defined either, throw an exception. - There is an optional tag attribute called ``method`` which defines which method - to execute when the event is triggered. By default the name of the method is - ``on`` + "camel-cased event name". If the event is ``kernel.exception`` the - method executed by default is ``onKernelException()``. +.. note:: - The other optional tag attribute is called ``priority``, which defaults to - ``0`` and it controls the order in which listeners are executed (the higher - the number the earlier a listener is executed). This is useful when you - need to guarantee that one listener is executed before another. The priorities - of the internal Symfony listeners usually range from ``-255`` to ``255`` but - your own listeners can use any positive or negative integer. + There is an optional attribute for the ``kernel.event_listener`` tag called + ``priority``, which is a positive or negative integer that defaults to ``0`` + and it controls the order in which listeners are executed (the higher the + number, the earlier a listener is executed). This is useful when you need to + guarantee that one listener is executed before another. The priorities of the + internal Symfony listeners usually range from ``-255`` to ``255`` but your + own listeners can use any positive or negative integer. .. _events-subscriber: @@ -139,8 +147,8 @@ about event subscribers, read :doc:`/components/event_dispatcher`. The following example shows an event subscriber that defines several methods which listen to the same ``kernel.exception`` event:: - // src/AppBundle/EventSubscriber/ExceptionSubscriber.php - namespace AppBundle\EventSubscriber; + // src/EventSubscriber/ExceptionSubscriber.php + namespace App\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; @@ -176,7 +184,7 @@ listen to the same ``kernel.exception`` event:: } } -That's it! Your ``services.yml`` file should already be setup to load services from +That's it! Your ``services.yaml`` file should already be setup to load services from the ``EventSubscriber`` directory. Symfony takes care of the rest. .. _ref-event-subscriber-configuration: @@ -196,8 +204,8 @@ sub-requests - typically by :doc:`/templating/embedding_controllers`). For the c Symfony events, you might need to check to see if the event is for a "master" request or a "sub request":: - // src/AppBundle/EventListener/RequestListener.php - namespace AppBundle\EventListener; + // src/EventListener/RequestListener.php + namespace App\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -254,6 +262,6 @@ Learn more .. toctree:: :maxdepth: 1 - :glob: - event_dispatcher/* + event_dispatcher/before_after_filters + event_dispatcher/method_behavior diff --git a/event_dispatcher/before_after_filters.rst b/event_dispatcher/before_after_filters.rst index 9d07e915022..ee3bef8c77b 100644 --- a/event_dispatcher/before_after_filters.rst +++ b/event_dispatcher/before_after_filters.rst @@ -33,14 +33,13 @@ token. Before Filters with the ``kernel.controller`` Event --------------------------------------------------- -First, store some basic token configuration using ``config.yml`` and the -parameters key: +First, define some token configuration as parameters: .. configuration-block:: .. code-block:: yaml - # app/config/config.yml + # config/services.yaml parameters: tokens: client1: pass1 @@ -48,7 +47,7 @@ parameters key: .. code-block:: xml - + setParameter('tokens', [ 'client1' => 'pass1', 'client2' => 'pass2', @@ -81,24 +80,24 @@ some way to identify if the controller that matches the request needs token vali A clean and easy way is to create an empty interface and make the controllers implement it:: - namespace AppBundle\Controller; + namespace App\Controller; interface TokenAuthenticatedController { // ... } -A controller that implements this interface simply looks like this:: +A controller that implements this interface looks like this:: - namespace AppBundle\Controller; + namespace App\Controller; - use AppBundle\Controller\TokenAuthenticatedController; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use App\Controller\TokenAuthenticatedController; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - class FooController extends Controller implements TokenAuthenticatedController + class FooController extends AbstractController implements TokenAuthenticatedController { // An action that needs authentication - public function barAction() + public function bar() { // ... } @@ -111,10 +110,10 @@ Next, you'll need to create an event subscriber, which will hold the logic that you want to be executed before your controllers. If you're not familiar with event subscribers, you can learn more about them at :doc:`/event_dispatcher`:: - // src/AppBundle/EventSubscriber/TokenSubscriber.php - namespace AppBundle\EventSubscriber; + // src/EventSubscriber/TokenSubscriber.php + namespace App\EventSubscriber; - use AppBundle\Controller\TokenAuthenticatedController; + use App\Controller\TokenAuthenticatedController; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -158,7 +157,7 @@ event subscribers, you can learn more about them at :doc:`/event_dispatcher`:: } } -That's it! Your ``services.yml`` file should already be setup to load services from +That's it! Your ``services.yaml`` 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``, @@ -182,7 +181,7 @@ all responses that have passed this token authentication. 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 +To create an "after" listener, create a listener class and register it as a service on this event. For example, take the ``TokenSubscriber`` from the previous example and first diff --git a/event_dispatcher/method_behavior.rst b/event_dispatcher/method_behavior.rst index 2b179039c29..b7d16962bd9 100644 --- a/event_dispatcher/method_behavior.rst +++ b/event_dispatcher/method_behavior.rst @@ -41,8 +41,8 @@ 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. For example, ``BeforeSendMailEvent`` might look like this:: - // src/AppBundle/Event/BeforeSendMailEvent.php - namespace AppBundle\Event; + // src/Event/BeforeSendMailEvent.php + namespace App\Event; use Symfony\Component\EventDispatcher\Event; @@ -80,8 +80,8 @@ events. For example, ``BeforeSendMailEvent`` might look like this:: And the ``AfterSendMailEvent`` even like this:: - // src/AppBundle/Event/AfterSendMailEvent.php - namespace AppBundle\Event; + // src/Event/AfterSendMailEvent.php + namespace App\Event; use Symfony\Component\EventDispatcher\Event; @@ -111,11 +111,11 @@ 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; + // src/EventSubscriber/MailPostSendSubscriber.php + namespace App\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use AppBundle\Event\AfterSendMailEvent; + use App\Event\AfterSendMailEvent; class MailPostSendSubscriber implements EventSubscriberInterface { diff --git a/form/action_method.rst b/form/action_method.rst index dbb47685341..f0b40db24e7 100644 --- a/form/action_method.rst +++ b/form/action_method.rst @@ -15,17 +15,17 @@ form, you can use ``setAction()`` and ``setMethod()``: .. code-block:: php-symfony - // AppBundle/Controller/DefaultController.php - namespace AppBundle\Controller; + // src/Controller/DefaultController.php + namespace App\Controller; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; - class DefaultController extends Controller + class DefaultController extends AbstractController { - public function newAction() + public function new() { // ... @@ -77,15 +77,15 @@ options: .. code-block:: php-symfony - // AppBundle/Controller/DefaultController.php - namespace AppBundle\Controller; + // src/Controller/DefaultController.php + namespace App\Controller; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use AppBundle\Form\TaskType; + use App\Form\TaskType; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - class DefaultController extends Controller + class DefaultController extends AbstractController { - public function newAction() + public function new() { // ... @@ -100,8 +100,8 @@ options: .. code-block:: php-standalone + use App\Form\TaskType; use Symfony\Component\Form\Forms; - use AppBundle\Form\TaskType; $formFactoryBuilder = Forms::createFormFactoryBuilder(); @@ -117,9 +117,9 @@ options: Finally, you can override the action and method in the template by passing them to the ``form()`` or the ``form_start()`` helper functions: -.. code-block:: html+twig +.. code-block:: twig - {# app/Resources/views/default/new.html.twig #} + {# templates/default/new.html.twig #} {{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }} .. note:: diff --git a/form/bootstrap4.rst b/form/bootstrap4.rst index 8d1b071dbe3..e40184f4f6a 100644 --- a/form/bootstrap4.rst +++ b/form/bootstrap4.rst @@ -2,7 +2,7 @@ Bootstrap 4 Form Theme ====================== Symfony provides several ways of integrating Bootstrap into your application. The -most straightforward way is to just add the required ```` and `` - {% endjavascripts %} - -This is all that's needed to compile this CoffeeScript file and serve it -as the compiled JavaScript. - -Filter multiple Files ---------------------- - -You can also combine multiple CoffeeScript files into a single output file: - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/example.coffee' - '@AppBundle/Resources/public/js/another.coffee' - filter='coffee' %} - - {% endjavascripts %} - -Both files will now be served up as a single file compiled into regular JavaScript. - -.. _assetic-apply-to: - -Filtering Based on a File Extension ------------------------------------ - -One of the great advantages of using Assetic is reducing the number of asset -files to lower HTTP requests. In order to make full use of this, it would -be good to combine *all* your JavaScript and CoffeeScript files together -since they will ultimately all be served as JavaScript. Unfortunately just -adding the JavaScript files to the files to be combined as above will not -work as the regular JavaScript files will not survive the CoffeeScript compilation. - -This problem can be avoided by using the ``apply_to`` option, which allows you -to specify which filter should always be applied to particular file extensions. -In this case you can specify that the ``coffee`` filter is applied to all -``.coffee`` files: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - coffee: - bin: /usr/bin/coffee - node: /usr/bin/node - node_paths: [/usr/lib/node_modules/] - apply_to: '\.coffee$' - - .. code-block:: xml - - - - - - - - /usr/lib/node_modules/ - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'coffee' => [ - 'bin' => '/usr/bin/coffee', - 'node' => '/usr/bin/node', - 'node_paths' => ['/usr/lib/node_modules/'], - 'apply_to' => '\.coffee$', - ], - ], - ]); - -With this option, you no longer need to specify the ``coffee`` filter in the -template. You can also list regular JavaScript files, all of which will be -combined and rendered as a single JavaScript file (with only the ``.coffee`` -files being run through the CoffeeScript filter): - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/example.coffee' - '@AppBundle/Resources/public/js/another.coffee' - '@AppBundle/Resources/public/js/regular.js' %} - - {% endjavascripts %} - - diff --git a/frontend/assetic/asset_management.rst b/frontend/assetic/asset_management.rst deleted file mode 100644 index 3a3125f8c92..00000000000 --- a/frontend/assetic/asset_management.rst +++ /dev/null @@ -1,579 +0,0 @@ -.. index:: - single: Assetic; Introduction - -How to Use Assetic for Asset Management -======================================= - -Installing and Enabling Assetic -------------------------------- - -Starting from Symfony 2.8, Assetic is no longer included by default in the -Symfony Standard Edition. Before using any of its features, install the -AsseticBundle executing this console command in your project: - -.. code-block:: terminal - - $ composer require symfony/assetic-bundle - -Then, enable the bundle in the ``AppKernel.php`` file of your Symfony application:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - // ... - - public function registerBundles() - { - $bundles = [ - // ... - new Symfony\Bundle\AsseticBundle\AsseticBundle(), - ]; - - // ... - } - } - -Finally, add the following minimal configuration to enable Assetic support in -your application: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - debug: '%kernel.debug%' - use_controller: '%kernel.debug%' - filters: - cssrewrite: ~ - - # ... - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'debug' => '%kernel.debug%', - 'use_controller' => '%kernel.debug%', - 'filters' => [ - 'cssrewrite' => null, - ], - // ... - ]); - - // ... - -Introducing Assetic -------------------- - -Assetic combines two major ideas: :ref:`assets ` and -:ref:`filters `. The assets are files such as CSS, -JavaScript and image files. The filters are things that can be applied to -these files before they are served to the browser. This allows a separation -between the asset files stored in the application and the files actually presented -to the user. - -Without Assetic, you just serve the files that are stored in the application -directly: - -.. code-block:: html+twig - - - -But *with* Assetic, you can manipulate these assets however you want (or -load them from anywhere) before serving them. This means you can: - -* Minify and combine all of your CSS and JS files - -* Run all (or just some) of your CSS or JS files through some sort of compiler, - such as LESS, SASS or CoffeeScript - -* Run image optimizations on your images - -.. _assetic-assets: - -Assets ------- - -Using Assetic provides many advantages over directly serving the files. -The files do not need to be stored where they are served from and can be -drawn from various sources such as from within a bundle. - -You can use Assetic to process :ref:`CSS stylesheets `, -:ref:`JavaScript files ` and -:ref:`images `. The philosophy -behind adding either is basically the same, but with a slightly different syntax. - -.. _assetic-including-javascript: - -Including JavaScript Files -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To include JavaScript files, use the ``javascripts`` tag in any template: - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/*' %} - - {% endjavascripts %} - -.. note:: - - If your application templates use the default block names from the Symfony - Standard Edition, the ``javascripts`` tag will most commonly live in the - ``javascripts`` block: - - .. code-block:: html+twig - - {# ... #} - {% block javascripts %} - {% javascripts '@AppBundle/Resources/public/js/*' %} - - {% endjavascripts %} - {% endblock %} - {# ... #} - -.. tip:: - - You can also include CSS stylesheets: see :ref:`assetic-including-css`. - -In this example, all files in the ``Resources/public/js/`` directory of the -AppBundle will be loaded and served from a different location. The actual -rendered tag might look like: - -.. code-block:: html - - - -This is a key point: once you let Assetic handle your assets, the files are -served from a different location. This *will* cause problems with CSS files -that reference images by their relative path. See :ref:`assetic-cssrewrite`. - -.. _assetic-including-css: - -Including CSS Stylesheets -~~~~~~~~~~~~~~~~~~~~~~~~~ - -To bring in CSS stylesheets, you can use the same technique explained above, -except with the ``stylesheets`` tag: - -.. code-block:: html+twig - - {% stylesheets 'bundles/app/css/*' filter='cssrewrite' %} - - {% endstylesheets %} - -.. note:: - - If your application templates use the default block names from the Symfony - Standard Edition, the ``stylesheets`` tag will most commonly live in the - ``stylesheets`` block: - - .. code-block:: html+twig - - {# ... #} - {% block stylesheets %} - {% stylesheets 'bundles/app/css/*' filter='cssrewrite' %} - - {% endstylesheets %} - {% endblock %} - {# ... #} - -But because Assetic changes the paths to your assets, this *will* break any -background images (or other paths) that uses relative paths, unless you use -the :ref:`cssrewrite ` filter. - -.. note:: - - Notice that in the original example that included JavaScript files, you - referred to the files using a path like ``@AppBundle/Resources/public/file.js``, - but that in this example, you referred to the CSS files using their actual, - publicly-accessible path: ``bundles/app/css``. You can use either, except - that there is a known issue that causes the ``cssrewrite`` filter to fail - when using the ``@AppBundle`` syntax for CSS stylesheets. - -.. _assetic-including-image: - -Including Images -~~~~~~~~~~~~~~~~ - -To include an image you can use the ``image`` tag. - -.. code-block:: html+twig - - {% image '@AppBundle/Resources/public/images/example.jpg' %} - Example - {% endimage %} - -You can also use Assetic for image optimization. More information in -:doc:`/frontend/assetic/jpeg_optimize`. - -.. tip:: - - Instead of using Assetic to include images, you may consider using the - `LiipImagineBundle`_ community bundle, which allows to compress and - manipulate images (rotate, resize, watermark, etc.) before serving them. - -.. _assetic-cssrewrite: - -Fixing CSS Paths with the ``cssrewrite`` Filter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Since Assetic generates new URLs for your assets, any relative paths inside -your CSS files will break. To fix this, make sure to use the ``cssrewrite`` -filter with your ``stylesheets`` tag. This parses your CSS files and corrects -the paths internally to reflect the new location. - -You can see an example in the previous section. - -.. caution:: - - When using the ``cssrewrite`` filter, don't refer to your CSS files using - the ``@AppBundle`` syntax. See the note in the above section for details. - -Combining Assets -~~~~~~~~~~~~~~~~ - -One feature of Assetic is that it will combine many files into one. This helps -to reduce the number of HTTP requests, which is great for front-end performance. -It also allows you to maintain the files by splitting them into smaller, more -manageable parts. This can help with re-usability as you can split project-specific -files from those which can be used in other applications, but still serve them as a -single file: - -.. code-block:: html+twig - - {% javascripts - '@AppBundle/Resources/public/js/*' - '@AcmeBarBundle/Resources/public/js/form.js' - '@AcmeBarBundle/Resources/public/js/calendar.js' %} - - {% endjavascripts %} - -In the ``dev`` environment, each file is still served individually, so that -you can debug problems more easily. However, in the ``prod`` environment -(or more specifically, when the ``debug`` flag is ``false``), this will be -rendered as a single ``script`` tag, which contains the contents of all of -the JavaScript files. - -.. tip:: - - If you're new to Assetic and try to use your application in the ``prod`` - environment (by using the ``app.php`` controller), you'll likely see - that all of your CSS and JS breaks. Don't worry! This is on purpose. - For details on using Assetic in the ``prod`` environment, see :ref:`assetic-dumping`. - -And combining files doesn't only apply to *your* files. You can also use Assetic to -combine third party assets, such as jQuery, with your own into a single file: - -.. code-block:: html+twig - - {% javascripts - '@AppBundle/Resources/public/js/thirdparty/jquery.js' - '@AppBundle/Resources/public/js/*' %} - - {% endjavascripts %} - -Using Named Assets -~~~~~~~~~~~~~~~~~~ - -AsseticBundle configuration directives allow you to define named asset sets. -You can do so by defining the input files, filters and output files in your -configuration under the ``assetic`` section. Read more in the -:doc:`assetic config reference `. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - assets: - jquery_and_ui: - inputs: - - '@AppBundle/Resources/public/js/thirdparty/jquery.js' - - '@AppBundle/Resources/public/js/thirdparty/jquery.ui.js' - - .. code-block:: xml - - - - - - - - @AppBundle/Resources/public/js/thirdparty/jquery.js - @AppBundle/Resources/public/js/thirdparty/jquery.ui.js - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'assets' => [ - 'jquery_and_ui' => [ - 'inputs' => [ - '@AppBundle/Resources/public/js/thirdparty/jquery.js', - '@AppBundle/Resources/public/js/thirdparty/jquery.ui.js', - ], - ], - ], - ]); - -After you have defined the named assets, you can reference them in your templates -with the ``@named_asset`` notation: - -.. code-block:: html+twig - - {% javascripts - '@jquery_and_ui' - '@AppBundle/Resources/public/js/*' %} - - {% endjavascripts %} - -.. _assetic-filters: - -Filters -------- - -Once they're managed by Assetic, you can apply filters to your assets before -they are served. This includes filters that compress the output of your assets -for smaller file sizes (and better frontend optimization). Other filters -can compile CoffeeScript files to JavaScript and process SASS into CSS. -In fact, Assetic has a long list of available filters. - -Many of the filters do not do the work directly, but use existing third-party -libraries to do the heavy-lifting. This means that you'll often need to install -a third-party library to use a filter. The great advantage of using Assetic -to invoke these libraries (as opposed to using them directly) is that instead -of having to run them manually after you work on the files, Assetic will -take care of this for you and remove this step altogether from your development -and deployment processes. - -To use a filter, you first need to specify it in the Assetic configuration. -Adding a filter here doesn't mean it's being used - it just means that it's -available to use (you'll use the filter below). - -For example to use the UglifyJS JavaScript minifier the following configuration -should be defined: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - uglifyjs2: - bin: /usr/local/bin/uglifyjs - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'uglifyjs2' => [ - 'bin' => '/usr/local/bin/uglifyjs', - ], - ], - ]); - -Now, to actually *use* the filter on a group of JavaScript files, add it -into your template: - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/*' filter='uglifyjs2' %} - - {% endjavascripts %} - -A more detailed guide about configuring and using Assetic filters as well as -details of Assetic's debug mode can be found in :doc:`/frontend/assetic/uglifyjs`. - -Controlling the URL Used ------------------------- - -If you wish to, you can control the URLs that Assetic produces. This is -done from the template and is relative to the public document root: - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/*' output='js/compiled/main.js' %} - - {% endjavascripts %} - -.. note:: - - Symfony provides various cache busting implementations via the - :ref:`version `, - :ref:`version_format `, and - :ref:`json_manifest_path ` - configuration options. - -.. _assetic-dumping: - -Dumping Asset Files -------------------- - -In the ``dev`` environment, Assetic generates paths to CSS and JavaScript -files that don't physically exist on your computer. But they render nonetheless -because an internal Symfony controller opens the files and serves back the -content (after running any filters). - -This kind of dynamic serving of processed assets is great because it means -that you can immediately see the new state of any asset files you change. -It's also bad, because it can be quite slow. If you're using a lot of filters, -it might be downright frustrating. - -Fortunately, Assetic provides a way to dump your assets to real files, instead -of being generated dynamically. - -Dumping Asset Files in the ``prod`` Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In the ``prod`` environment, your JS and CSS files are represented by a single -tag each. In other words, instead of seeing each JavaScript file you're including -in your source, you'll likely just see something like this: - -.. code-block:: html - - - -Moreover, that file does **not** actually exist, nor is it dynamically rendered -by Symfony (as the asset files are in the ``dev`` environment). This is on -purpose - letting Symfony generate these files dynamically in a production -environment is just too slow. - -.. _assetic-dump-prod: - -Instead, each time you use your application in the ``prod`` environment (and therefore, -each time you deploy), you should run the following command: - -.. code-block:: terminal - - $ php bin/console assetic:dump --env=prod --no-debug - -This will physically generate and write each file that you need (e.g. ``/js/abcd123.js``). -If you update any of your assets, you'll need to run this again to regenerate -the file. - -Dumping Asset Files in the ``dev`` Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default, each asset path generated in the ``dev`` environment is handled -dynamically by Symfony. This has no disadvantage (you can see your changes -immediately), except that assets can load noticeably slow. If you feel like -your assets are loading too slowly, follow this guide. - -First, tell Symfony to stop trying to process these files dynamically. Make -the following change in your ``config_dev.yml`` file: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - assetic: - use_controller: false - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/config_dev.php - $container->loadFromExtension('assetic', [ - 'use_controller' => false, - ]); - -Next, since Symfony is no longer generating these assets for you, you'll -need to dump them manually. To do so, run the following command: - -.. code-block:: terminal - - $ php bin/console assetic:dump - -This physically writes all of the asset files you need for your ``dev`` -environment. The big disadvantage is that you need to run this each time -you update an asset. Fortunately, by using the ``assetic:watch`` command, -assets will be regenerated automatically *as they change*: - -.. code-block:: terminal - - $ php bin/console assetic:watch - -The ``assetic:watch`` command was introduced in AsseticBundle 2.4. In prior -versions, you had to use the ``--watch`` option of the ``assetic:dump`` -command for the same behavior. - -Since running this command in the ``dev`` environment may generate a bunch -of files, it's usually a good idea to point your generated asset files to -some isolated directory (e.g. ``/js/compiled``), to keep things organized: - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/*' output='js/compiled/main.js' %} - - {% endjavascripts %} - -.. _`LiipImagineBundle`: https://github.com/liip/LiipImagineBundle diff --git a/frontend/assetic/index.rst b/frontend/assetic/index.rst new file mode 100644 index 00000000000..63955c9f8dd --- /dev/null +++ b/frontend/assetic/index.rst @@ -0,0 +1,8 @@ +Assetic +======= + +.. caution:: + + Using Assetic to manage web assets in Symfony applications is no longer + recommended. Instead, use :doc:`Webpack Encore `, which bridges + Symfony applications with modern JavaScript-based tools to manage web assets. diff --git a/frontend/assetic/jpeg_optimize.rst b/frontend/assetic/jpeg_optimize.rst deleted file mode 100644 index 670943ee930..00000000000 --- a/frontend/assetic/jpeg_optimize.rst +++ /dev/null @@ -1,299 +0,0 @@ -.. index:: - single: Assetic; Image optimization - -How to Use Assetic for Image Optimization with Twig Functions -============================================================= - -.. include:: /assetic/_standard_edition_warning.rst.inc - -Among its many filters, Assetic has four filters which can be used for on-the-fly -image optimization. This allows you to get the benefits of smaller file sizes -without having to use an image editor to process each image. The results -are cached and can be dumped for production so there is no performance hit -for your end users. - -Using Jpegoptim ---------------- - -`Jpegoptim`_ is a utility for optimizing JPEG files. To use it with Assetic, make -sure to have it already installed on your system and then, configure its location -using the ``bin`` option of the ``jpegoptim`` filter: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: path/to/jpegoptim - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'jpegoptim' => [ - 'bin' => 'path/to/jpegoptim', - ], - ], - ]); - -It can now be used from a template: - -.. code-block:: html+twig - - {% image '@AppBundle/Resources/public/images/example.jpg' - filter='jpegoptim' output='/images/example.jpg' %} - Example - {% endimage %} - -Removing all EXIF Data -~~~~~~~~~~~~~~~~~~~~~~ - -By default, the ``jpegoptim`` filter removes some meta information stored -in the image. To remove all EXIF data and comments, set the ``strip_all`` option -to ``true``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: path/to/jpegoptim - strip_all: true - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'jpegoptim' => [ - 'bin' => 'path/to/jpegoptim', - 'strip_all' => 'true', - ], - ], - ]); - -Lowering Maximum Quality -~~~~~~~~~~~~~~~~~~~~~~~~ - -By default, the ``jpegoptim`` filter doesn't alter the quality level of the JPEG -image. Use the ``max`` option to configure the maximum quality setting (in a -scale of ``0`` to ``100``). The reduction in the image file size will of course -be at the expense of its quality: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: path/to/jpegoptim - max: 70 - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'jpegoptim' => [ - 'bin' => 'path/to/jpegoptim', - 'max' => '70', - ], - ], - ]); - -Shorter Syntax: Twig Function ------------------------------ - -If you're using Twig, it's possible to achieve all of this with a shorter -syntax by enabling and using a special Twig function. Start by adding the -following configuration: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: path/to/jpegoptim - twig: - functions: - jpegoptim: ~ - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'jpegoptim' => [ - 'bin' => 'path/to/jpegoptim', - ], - ], - 'twig' => [ - 'functions' => ['jpegoptim'], - ], - ]); - -The Twig template can now be changed to the following: - -.. code-block:: html+twig - - Example - -You can also specify the output directory for images in the Assetic configuration -file: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: path/to/jpegoptim - twig: - functions: - jpegoptim: { output: images/*.jpg } - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'jpegoptim' => [ - 'bin' => 'path/to/jpegoptim', - ], - ], - 'twig' => [ - 'functions' => [ - 'jpegoptim' => [ - 'output' => 'images/*.jpg', - ], - ], - ], - ]); - -.. tip:: - - For uploaded images, you can compress and manipulate them using the - `LiipImagineBundle`_ community bundle. - -.. _`Jpegoptim`: http://www.kokkonen.net/tjko/projects.html -.. _`LiipImagineBundle`: https://github.com/liip/LiipImagineBundle diff --git a/frontend/assetic/php.rst b/frontend/assetic/php.rst deleted file mode 100644 index 7e165e16407..00000000000 --- a/frontend/assetic/php.rst +++ /dev/null @@ -1,213 +0,0 @@ -.. index:: - single: Front-end; Assetic, Bootstrap - -Combining, Compiling and Minimizing Web Assets with PHP Libraries -================================================================= - -.. include:: /assetic/_standard_edition_warning.rst.inc - -The official Symfony Best Practices recommend to use Assetic to -:doc:`manage web assets `, unless you are -comfortable with JavaScript-based front-end tools. - -Even if those JavaScript-based solutions are the most suitable ones from a -technical point of view, using pure PHP alternative libraries can be useful in -some scenarios: - -* If you can't install or use ``npm`` and the other JavaScript solutions; -* If you prefer to limit the amount of different technologies used in your - applications; -* If you want to simplify application deployment. - -In this article, you'll learn how to combine and minimize CSS and JavaScript files -and how to compile Sass files using PHP-only libraries with Assetic. - -Installing the Third-Party Compression Libraries ------------------------------------------------- - -Assetic includes a lot of ready-to-use filters, but it doesn't include their -associated libraries. Therefore, before enabling the filters used in this article, -you must install two libraries. Open a command console, browse to your project -directory and execute the following commands: - -.. code-block:: terminal - - $ composer require leafo/scssphp - $ composer require patchwork/jsqueeze - -Organizing your Web Asset Files -------------------------------- - -This example will include a setup using the Bootstrap CSS framework, jQuery, FontAwesome -and some regular CSS and JavaScript application files (called ``main.css`` and -``main.js``). The recommended directory structure for this set-up looks like this: - -.. code-block:: text - - web/assets/ - ├── css - │   ├── main.css - │   └── code-highlight.css - ├── js - │   ├── bootstrap.js - │   ├── jquery.js - │   └── main.js - └── scss - ├── bootstrap - │   ├── _alerts.scss - │   ├── ... - │   ├── _variables.scss - │   ├── _wells.scss - │   └── mixins - │   ├── _alerts.scss - │   ├── ... - │   └── _vendor-prefixes.scss - ├── bootstrap.scss - ├── font-awesome - │   ├── _animated.scss - │   ├── ... - │   └── _variables.scss - └── font-awesome.scss - -Combining and Minimizing CSS Files and Compiling SCSS Files ------------------------------------------------------------ - -First, configure a new ``scssphp`` Assetic filter: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - scssphp: - formatter: 'Leafo\ScssPhp\Formatter\Compressed' - # ... - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'scssphp' => [ - 'formatter' => 'Leafo\ScssPhp\Formatter\Compressed', - ], - // ... - ], - ]); - -The value of the ``formatter`` option is the fully qualified class name of the -formatter used by the filter to produce the compiled CSS file. Using the -compressed formatter will minimize the resulting file, regardless of whether -the original files are regular CSS files or SCSS files. - -Next, update your Twig template to add the ``{% stylesheets %}`` tag defined -by Assetic: - -.. code-block:: html+twig - - {# app/Resources/views/base.html.twig #} - - - - - - {% stylesheets filter="scssphp" output="css/app.css" - "assets/scss/bootstrap.scss" - "assets/scss/font-awesome.scss" - "assets/css/*.css" - %} - - {% endstylesheets %} - -This simple configuration compiles, combines and minifies the SCSS files into a -regular CSS file that's put in ``web/css/app.css``. This is the only CSS file -which will be served to your visitors. - -Combining and Minimizing JavaScript Files ------------------------------------------ - -First, configure a new ``jsqueeze`` Assetic filter as follows: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jsqueeze: ~ - # ... - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'jsqueeze' => null, - // ... - ], - ]); - -Next, update the code of your Twig template to add the ``{% javascripts %}`` tag -defined by Assetic: - -.. code-block:: html+twig - - - - {% javascripts filter="?jsqueeze" output="js/app.js" - "assets/js/jquery.js" - "assets/js/bootstrap.js" - "assets/js/main.js" - %} - - {% endjavascripts %} - - - - -This simple configuration combines all the JavaScript files, minimizes the contents -and saves the output in the ``web/js/app.js`` file, which is the one that is -served to your visitors. - -The leading ``?`` character in the ``jsqueeze`` filter name tells Assetic to only -apply the filter when *not* in ``debug`` mode. In practice, this means that you'll -see unminified files while developing and minimized files in the ``prod`` environment. diff --git a/frontend/assetic/uglifyjs.rst b/frontend/assetic/uglifyjs.rst deleted file mode 100644 index 3098520fac7..00000000000 --- a/frontend/assetic/uglifyjs.rst +++ /dev/null @@ -1,310 +0,0 @@ -.. index:: - single: Assetic; UglifyJS - -How to Minify CSS/JS Files (Using UglifyJS and UglifyCSS) -========================================================= - -.. include:: /assetic/_standard_edition_warning.rst.inc - -`UglifyJS`_ is a JavaScript parser/compressor/beautifier toolkit. It can be used -to combine and minify JavaScript assets so that they require less HTTP requests -and make your site load faster. `UglifyCSS`_ is a CSS compressor/beautifier -that is very similar to UglifyJS. - -In this article, the installation, configuration and usage of UglifyJS is -shown in detail. UglifyCSS works pretty much the same way and is only -talked about briefly. - -Install UglifyJS ----------------- - -UglifyJS is available as a `Node.js`_ module. First, you need to `install Node.js`_ -and then, decide the installation method: global or local. - -.. caution:: - - Some Linux distributions rename the Node.js binary from ``node`` to ``nodejs``. - This may result in errors like *"/usr/bin/env: node: No such file or - directory"*. You can solve this problem with a symlink: - - .. code-block:: terminal - - $ ln -s /usr/bin/nodejs /usr/bin/node - -Global Installation -~~~~~~~~~~~~~~~~~~~ - -The global installation method makes all your projects use the very same UglifyJS -version, which simplifies its maintenance. Open your command console and execute -the following command (you may need to run it as a root user): - -.. code-block:: terminal - - $ npm install -g uglify-js - -Now you can execute the global ``uglifyjs`` command anywhere on your system: - -.. code-block:: terminal - - $ uglifyjs --help - -Local Installation -~~~~~~~~~~~~~~~~~~ - -It's also possible to install UglifyJS inside your project only, which is useful -when your project requires a specific UglifyJS version. To do this, install it -without the ``-g`` option and specify the path where to put the module: - -.. code-block:: terminal - - $ cd /path/to/your/symfony/project - $ npm install uglify-js --prefix app/Resources - -It is recommended that you install UglifyJS in your ``app/Resources`` folder and -add the ``node_modules`` folder to version control. Alternatively, you can create -an npm `package.json`_ file and specify your dependencies there. - -Now you can execute the ``uglifyjs`` command that lives in the ``node_modules`` -directory: - -.. code-block:: terminal - - $ "./app/Resources/node_modules/.bin/uglifyjs" --help - -Configure the ``uglifyjs2`` Filter ----------------------------------- - -Now we need to configure Symfony to use the ``uglifyjs2`` filter when processing -your JavaScripts: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - uglifyjs2: - # the path to the uglifyjs executable - bin: /usr/local/bin/uglifyjs - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'uglifyjs2' => [ - // the path to the uglifyjs executable - 'bin' => '/usr/local/bin/uglifyjs', - ], - ], - ]); - -.. note:: - - The path where UglifyJS is installed may vary depending on your system. - To find out where npm stores the ``bin`` folder, execute the following command: - - .. code-block:: terminal - - $ npm bin -g - - It should output a folder on your system, inside which you should find - the UglifyJS executable. - - If you installed UglifyJS locally, you can find the ``bin`` folder inside - the ``node_modules`` folder. It's called ``.bin`` in this case. - -You now have access to the ``uglifyjs2`` filter in your application. - -Configure the ``node`` Binary ------------------------------ - -Assetic tries to find the node binary automatically. If it cannot be found, you -can configure its location using the ``node`` key: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - # the path to the node executable - node: /usr/bin/nodejs - filters: - uglifyjs2: - # the path to the uglifyjs executable - bin: /usr/local/bin/uglifyjs - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'node' => '/usr/bin/nodejs', - 'uglifyjs2' => [ - // the path to the uglifyjs executable - 'bin' => '/usr/local/bin/uglifyjs', - ], - ]); - -Minify your Assets ------------------- - -In order to apply UglifyJS on your assets, add the ``filter`` option in the -asset tags of your templates to tell Assetic to use the ``uglifyjs2`` filter: - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/*' filter='uglifyjs2' %} - - {% endjavascripts %} - -.. note:: - - The above example assumes that you have a bundle called AppBundle and your - JavaScript files are in the ``Resources/public/js`` directory under your - bundle. However you can include your JavaScript files no matter where they are. - -With the addition of the ``uglifyjs2`` filter to the asset tags above, you -should now see minified JavaScripts coming over the wire much faster. - -Disable Minification in Debug Mode -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Minified JavaScripts are very difficult to read, let alone debug. Because of -this, Assetic lets you disable a certain filter when your application is in -debug (e.g. ``app_dev.php``) mode. You can do this by prefixing the filter name -in your template with a question mark: ``?``. This tells Assetic to only -apply this filter when debug mode is off (e.g. ``app.php``): - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/*' filter='?uglifyjs2' %} - - {% endjavascripts %} - -To try this out, switch to your ``prod`` environment (``app.php``). But before -you do, don't forget to :ref:`clear your cache ` -and :ref:`dump your assetic assets `. - -.. tip:: - - Instead of adding the filters to the asset tags, you can also configure which - filters to apply for each file in your application configuration file. - See :ref:`assetic-apply-to` for more details. - -Install, Configure and Use UglifyCSS ------------------------------------- - -The usage of UglifyCSS works the same way as UglifyJS. First, make sure -the node package is installed: - -.. code-block:: terminal - - # global installation - $ npm install -g uglifycss - - # local installation - $ cd /path/to/your/symfony/project - $ npm install uglifycss --prefix app/Resources - -Next, add the configuration for this filter: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - uglifycss: - bin: /usr/local/bin/uglifycss - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - 'filters' => [ - 'uglifycss' => [ - 'bin' => '/usr/local/bin/uglifycss', - ], - ], - ]); - -To use the filter for your CSS files, add the filter to the Assetic ``stylesheets`` -helper: - -.. code-block:: html+twig - - {% stylesheets 'bundles/App/css/*' filter='uglifycss' filter='cssrewrite' %} - - {% endstylesheets %} - -Just like with the ``uglifyjs2`` filter, if you prefix the filter name with -``?`` (i.e. ``?uglifycss``), the minification will only happen when you're -not in debug mode. - -.. _`UglifyJS`: https://github.com/mishoo/UglifyJS -.. _`UglifyCSS`: https://github.com/fmarcia/UglifyCSS -.. _`Node.js`: https://nodejs.org/ -.. _`install Node.js`: https://nodejs.org/ -.. _`package.json`: http://browsenpm.org/package.json diff --git a/frontend/assetic/yuicompressor.rst b/frontend/assetic/yuicompressor.rst deleted file mode 100644 index 438586bd3b1..00000000000 --- a/frontend/assetic/yuicompressor.rst +++ /dev/null @@ -1,147 +0,0 @@ -.. index:: - single: Assetic; YUI Compressor - -How to Minify JavaScripts and Stylesheets with YUI Compressor -============================================================= - -.. caution:: - - The YUI Compressor is `no longer maintained by Yahoo`_. That's why you are - **strongly advised to avoid using YUI utilities** unless strictly necessary. - Read :doc:`/frontend/assetic/uglifyjs` for a modern and up-to-date alternative. - -.. include:: /assetic/_standard_edition_warning.rst.inc - -Yahoo! provides an excellent utility for minifying JavaScripts and stylesheets -so they travel over the wire faster, the `YUI Compressor`_. Thanks to Assetic, -you can take advantage of this tool. - -Download the YUI Compressor JAR -------------------------------- - -The YUI Compressor is written in Java and distributed as a JAR. `Download the JAR`_ -from the Yahoo! website and save it to ``app/Resources/java/yuicompressor.jar``. - -Configure the YUI Filters -------------------------- - -Now you need to configure two Assetic filters in your application, one for -minifying JavaScripts with the YUI Compressor and one for minifying -stylesheets: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - # java: '/usr/bin/java' - filters: - yui_css: - jar: '%kernel.project_dir%/app/Resources/java/yuicompressor.jar' - yui_js: - jar: '%kernel.project_dir%/app/Resources/java/yuicompressor.jar' - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', [ - // 'java' => '/usr/bin/java', - 'filters' => [ - 'yui_css' => [ - 'jar' => '%kernel.project_dir%/app/Resources/java/yuicompressor.jar', - ], - 'yui_js' => [ - 'jar' => '%kernel.project_dir%/app/Resources/java/yuicompressor.jar', - ], - ], - ]); - -.. note:: - - Windows users need to remember to update config to proper Java location. - In Windows7 x64 bit by default it's ``C:\Program Files (x86)\Java\jre6\bin\java.exe``. - -You now have access to two new Assetic filters in your application: -``yui_css`` and ``yui_js``. These will use the YUI Compressor to minify -stylesheets and JavaScripts, respectively. - -Minify your Assets ------------------- - -You have YUI Compressor configured now, but nothing is going to happen until -you apply one of these filters to an asset. Since your assets are a part of -the view layer, this work is done in your templates: - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/*' filter='yui_js' %} - - {% endjavascripts %} - -.. note:: - - The above example assumes that you have a bundle called AppBundle and your - JavaScript files are in the ``Resources/public/js`` directory under your - bundle. This isn't important however - you can include your JavaScript - files no matter where they are. - -With the addition of the ``yui_js`` filter to the asset tags above, you should -now see minified JavaScripts coming over the wire much faster. The same process -can be repeated to minify your stylesheets. - -.. code-block:: html+twig - - {% stylesheets '@AppBundle/Resources/public/css/*' filter='yui_css' %} - - {% endstylesheets %} - -Disable Minification in Debug Mode ----------------------------------- - -Minified JavaScripts and stylesheets are very difficult to read, let alone -debug. Because of this, Assetic lets you disable a certain filter when your -application is in debug mode. You can do this by prefixing the filter name -in your template with a question mark: ``?``. This tells Assetic to only -apply this filter when debug mode is off. - -.. code-block:: html+twig - - {% javascripts '@AppBundle/Resources/public/js/*' filter='?yui_js' %} - - {% endjavascripts %} - -.. tip:: - - Instead of adding the filter to the asset tags, you can also globally - enable it by adding the ``apply_to`` attribute to the filter configuration, for - example in the ``yui_js`` filter ``apply_to: "\.js$"``. To only have the filter - applied in production, add this to the ``config_prod`` file rather than the - common config file. For details on applying filters by file extension, - see :ref:`assetic-apply-to`. - -.. _`YUI Compressor`: http://yui.github.io/yuicompressor/ -.. _`Download the JAR`: https://github.com/yui/yuicompressor/releases -.. _`no longer maintained by Yahoo`: http://yuiblog.com/blog/2013/01/24/yui-compressor-has-a-new-owner/ diff --git a/frontend/custom_version_strategy.rst b/frontend/custom_version_strategy.rst index a8008ea9e75..1999dc40c39 100644 --- a/frontend/custom_version_strategy.rst +++ b/frontend/custom_version_strategy.rst @@ -45,8 +45,8 @@ In this example, the constructor of the class takes as arguments the path to the manifest file generated by `gulp-buster`_ and the format of the generated version string:: - // src/AppBundle/Asset/VersionStrategy/GulpBusterVersionStrategy.php - namespace AppBundle\Asset\VersionStrategy; + // src/Asset/VersionStrategy/GulpBusterVersionStrategy.php + namespace App\Asset\VersionStrategy; use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface; @@ -118,9 +118,9 @@ After creating the strategy PHP class, register it as a Symfony service. .. code-block:: yaml - # app/config/services.yml + # config/services.yaml services: - AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy: + App\Asset\VersionStrategy\GulpBusterVersionStrategy: arguments: - "%kernel.project_dir%/busters.json" - "%%s?version=%%s" @@ -128,7 +128,7 @@ After creating the strategy PHP class, register it as a Symfony service. .. code-block:: xml - + - + %kernel.project_dir%/busters.json %%s?version=%%s @@ -145,9 +145,9 @@ After creating the strategy PHP class, register it as a Symfony service. .. code-block:: php - // app/config/services.php + // config/services.php use Symfony\Component\DependencyInjection\Definition; - use AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy; + use App\Asset\VersionStrategy\GulpBusterVersionStrategy; $container->autowire(GulpBusterVersionStrategy::class) ->setArguments( @@ -165,31 +165,31 @@ the :ref:`version_strategy ` option: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: # ... assets: - version_strategy: 'AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy' + version_strategy: 'App\Asset\VersionStrategy\GulpBusterVersionStrategy' .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + .. code-block:: php - // app/config/config.php - use AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy; + // config/packages/framework.php + use App\Asset\VersionStrategy\GulpBusterVersionStrategy; $container->loadFromExtension('framework', [ // ... diff --git a/frontend/encore/advanced-config.rst b/frontend/encore/advanced-config.rst index 8dbc5b9fcab..d5930beabd8 100644 --- a/frontend/encore/advanced-config.rst +++ b/frontend/encore/advanced-config.rst @@ -56,7 +56,7 @@ state of the current configuration to build a new one: // define the first configuration Encore - .setOutputPath('web/build/') + .setOutputPath('public/build/') .setPublicPath('/build') .addEntry('app', './assets/js/app.js') .addStyleEntry('global', './assets/css/global.scss') @@ -76,7 +76,7 @@ state of the current configuration to build a new one: // define the second configuration Encore - .setOutputPath('web/build/') + .setOutputPath('public/build/') .setPublicPath('/build') .addEntry('mobile', './assets/js/mobile.js') .addStyleEntry('mobile', './assets/css/mobile.less') @@ -106,21 +106,21 @@ Next, define the output directories of each build: # config/packages/webpack_encore.yaml webpack_encore: - output_path: '%kernel.public_dir%/web/default_build' + output_path: '%kernel.public_dir%/public/default_build' builds: - firstConfig: '%kernel.public_dir%/web/first_build' - secondConfig: '%kernel.public_dir%/web/second_build' + firstConfig: '%kernel.public_dir%/public/first_build' + secondConfig: '%kernel.public_dir%/public/second_build' Finally, use the third optional parameter of the ``encore_entry_*_tags()`` functions to specify which build to use: .. code-block:: twig - {# Using the entrypoints.json file located in ./web/first_build #} + {# Using the entrypoints.json file located in ./public/first_build #} {{ encore_entry_script_tags('app', null, 'firstConfig') }} {{ encore_entry_link_tags('global', null, 'firstConfig') }} - {# Using the entrypoints.json file located in ./web/second_build #} + {# Using the entrypoints.json file located in ./public/second_build #} {{ encore_entry_script_tags('mobile', null, 'secondConfig') }} {{ encore_entry_link_tags('mobile', null, 'secondConfig') }} diff --git a/frontend/encore/cdn.rst b/frontend/encore/cdn.rst index d03753d6417..b48ef007dca 100644 --- a/frontend/encore/cdn.rst +++ b/frontend/encore/cdn.rst @@ -10,7 +10,7 @@ built files are uploaded to the CDN, configure it in Encore: // ... Encore - .setOutputPath('web/build/') + .setOutputPath('public/build/') // in dev mode, don't use the CDN .setPublicPath('/build'); // ... diff --git a/frontend/encore/copy-files.rst b/frontend/encore/copy-files.rst index c8d3edbb243..f1c51fb5a9d 100644 --- a/frontend/encore/copy-files.rst +++ b/frontend/encore/copy-files.rst @@ -36,7 +36,7 @@ files into your final output directory. Encore // ... - .setOutputPath('web/build/') + .setOutputPath('public/build/') + .copyFiles({ + from: './assets/images', @@ -51,7 +51,7 @@ files into your final output directory. + //pattern: /\.(png|jpg|jpeg)$/ + }) -This will copy all files from ``assets/images`` into ``web/build`` (the output +This will copy all files from ``assets/images`` into ``public/build`` (the output path). If you have :doc:`versioning enabled `, the copied files will include a hash based on their content. @@ -59,10 +59,10 @@ To render inside Twig, use the ``asset()`` function: .. code-block:: html+twig - {# assets/images/logo.png was copied to web/build/logo.png #} + {# assets/images/logo.png was copied to public/build/logo.png #} - {# assets/images/subdir/logo.png was copied to web/build/subdir/logo.png #} + {# assets/images/subdir/logo.png was copied to public/build/subdir/logo.png #} Make sure you've enabled the :ref:`json_manifest_path ` option, diff --git a/frontend/encore/faq.rst b/frontend/encore/faq.rst index 719b19a2960..37f76d09f3b 100644 --- a/frontend/encore/faq.rst +++ b/frontend/encore/faq.rst @@ -29,7 +29,7 @@ server. **2) Only Deploy the Built Assets** The *only* files that need to be deployed to your production servers are the -final, built assets (e.g. the ``web/build`` directory). You do *not* need to install +final, built assets (e.g. the ``public/build`` directory). You do *not* need to install Node.js, deploy ``webpack.config.js``, the ``node_modules`` directory or even your source asset files, **unless** you plan on running ``encore production`` on your production machine. Once your assets are built, these are the *only* thing that need to live @@ -51,7 +51,7 @@ and the built files. Your ``.gitignore`` file should include: /node_modules/ # whatever path you're passing to Encore.setOutputPath() - /web/build + /public/build You *should* commit all of your source asset files, ``package.json`` and ``yarn.lock``. @@ -59,7 +59,7 @@ My App Lives under a Subdirectory --------------------------------- If your app does not live at the root of your web server (i.e. it lives under a subdirectory, -like ``/myAppSubdir``), you just need to configure that when calling ``Encore.setPublicPrefix()``: +like ``/myAppSubdir``), you need to configure that when calling ``Encore.setPublicPrefix()``: .. code-block:: diff @@ -67,7 +67,7 @@ like ``/myAppSubdir``), you just need to configure that when calling ``Encore.se Encore // ... - .setOutputPath('web/build/') + .setOutputPath('public/build/') - .setPublicPath('/build') + // this is your *true* public path @@ -116,7 +116,7 @@ But, instead of working, you see an error: This dependency was not found: - * respond.js in ./app/Resources/assets/js/app.js + * respond.js in ./assets/js/app.js Typically, a package will "advertise" its "main" file by adding a ``main`` key to its ``package.json``. But sometimes, old libraries won't have this. Instead, you'll diff --git a/frontend/encore/installation.rst b/frontend/encore/installation.rst index dba811ec232..3d5d3f0ee3f 100644 --- a/frontend/encore/installation.rst +++ b/frontend/encore/installation.rst @@ -57,7 +57,7 @@ is the main config file for both Webpack and Webpack Encore: Encore // directory where compiled assets will be stored - .setOutputPath('web/build/') + .setOutputPath('public/build/') // public path used by the web server to access the output path .setPublicPath('/build') // only needed for CDN's or sub-directory deploy diff --git a/frontend/encore/reactjs.rst b/frontend/encore/reactjs.rst index f5e216cb26e..36f6a64a845 100644 --- a/frontend/encore/reactjs.rst +++ b/frontend/encore/reactjs.rst @@ -3,6 +3,13 @@ Enabling React.js Using React? First enable support for it in ``webpack.config.js``: +.. code-block:: terminal + + $ yarn add @babel/preset-react --dev + $ yarn add react react-dom prop-types + +Enable react in your ``webpack.config.js``: + .. code-block:: diff // webpack.config.js diff --git a/frontend/encore/simple-example.rst b/frontend/encore/simple-example.rst index cfb208b65a1..ac915328c07 100644 --- a/frontend/encore/simple-example.rst +++ b/frontend/encore/simple-example.rst @@ -39,7 +39,7 @@ of your project. It already holds the basic config you need: Encore // directory where compiled assets will be stored - .setOutputPath('web/build/') + .setOutputPath('public/build/') // public path used by the web server to access the output path .setPublicPath('/build') @@ -53,7 +53,7 @@ of your project. It already holds the basic config you need: They *key* part is ``addEntry()``: this tells Encore to load the ``assets/js/app.js`` file and follow *all* of the ``require`` statements. It will then package everything together and - thanks to the first ``app`` argument - output final ``app.js`` and -``app.css`` files into the ``web/build`` directory. +``app.css`` files into the ``public/build`` directory. .. _encore-build-assets: @@ -76,16 +76,16 @@ To build the assets, run: Congrats! You now have three new files: -* ``web/build/app.js`` (holds all the JavaScript for your "app" entry) -* ``web/build/app.css`` (holds all the CSS for your "app" entry) -* ``web/build/runtime.js`` (a file that helps Webpack do its job) +* ``public/build/app.js`` (holds all the JavaScript for your "app" entry) +* ``public/build/app.css`` (holds all the CSS for your "app" entry) +* ``public/build/runtime.js`` (a file that helps Webpack do its job) Next, include these in your base layout file. Two Twig helpers from WebpackEncoreBundle can do most of the work for you: .. code-block:: twig - {# app/Resources/views/base.html.twig #} + {# templates/base.html.twig #} @@ -138,7 +138,7 @@ some optional features. Requiring JavaScript Modules ---------------------------- -Webpack is a module bundler... which means that you can ``require`` other JavaScript +Webpack is a module bundler, which means that you can ``require`` other JavaScript files. First, create a file that exports a function: .. code-block:: javascript @@ -183,7 +183,7 @@ Instead of using ``require`` and ``module.exports`` like shown above, JavaScript provides an alternate syntax based on the `ECMAScript 6 modules`_ that includes the ability to use dynamic imports. -To export values, use ``exports``: +To export values using the alternate syntax, use ``exports``: .. code-block:: diff diff --git a/frontend/encore/versioning.rst b/frontend/encore/versioning.rst index a5ec30256fd..2f3a114c857 100644 --- a/frontend/encore/versioning.rst +++ b/frontend/encore/versioning.rst @@ -16,7 +16,7 @@ ignoring any existing cache: // ... Encore - .setOutputPath('web/build/') + .setOutputPath('public/build/') // ... + .enableVersioning() @@ -51,18 +51,17 @@ the CSS and JavaScript files): In your app, you need to read this file if you want to be able to link (e.g. via an ``img`` tag) to certain assets. If you're using Symfony, just activate the -``json_manifest_file`` versioning strategy in ``config.yml``: +``json_manifest_file`` versioning strategy: .. code-block:: yaml - # app/config/config.yml + # this file is added automatically when installing Encore with Symfony Flex + # config/packages/assets.yaml framework: - # ... assets: - # feature is supported in Symfony 3.3 and higher - json_manifest_path: '%kernel.project_dir%/web/build/manifest.json' + json_manifest_path: '%kernel.project_dir%/public/build/manifest.json' -That's it! Just be sure to wrap each path in the Twig ``asset()`` function +That's it! Be sure to wrap each path in the Twig ``asset()`` function like normal: .. code-block:: twig diff --git a/frontend/encore/versus-assetic.rst b/frontend/encore/versus-assetic.rst index 9f606ae48e3..9c05d4bb6b5 100644 --- a/frontend/encore/versus-assetic.rst +++ b/frontend/encore/versus-assetic.rst @@ -1,7 +1,7 @@ Encore Versus Assetic? ====================== -Symfony originally shipped with support for :doc:`Assetic `: a +Symfony originally shipped with support for :doc:`Assetic `: a pure PHP library capable of processing, combining and minifying CSS and JavaScript files. And while Encore is now the recommended way of processing your assets, Assetic still works well. diff --git a/frontend/encore/vuejs.rst b/frontend/encore/vuejs.rst index a79755b2ff7..b73bc406a30 100644 --- a/frontend/encore/vuejs.rst +++ b/frontend/encore/vuejs.rst @@ -27,7 +27,7 @@ Hot Module Replacement (HMR) ---------------------------- The ``vue-loader`` supports hot module replacement: just update your code and watch -your Vue.js app update *without* a browser refresh! To activate it, just use the +your Vue.js app update *without* a browser refresh! To activate it, use the ``dev-server`` with the ``--hot`` option: .. code-block:: terminal diff --git a/http_cache.rst b/http_cache.rst index 58fbb760948..dfcb843c1b2 100644 --- a/http_cache.rst +++ b/http_cache.rst @@ -77,22 +77,37 @@ but is a great way to start. For details on setting up Varnish, see :doc:`/http_cache/varnish`. -Each application comes with a caching kernel (``AppCache``) that wraps the -default one (``AppKernel``). The caching Kernel *is* the reverse proxy. +To enable the proxy, first create a caching kernel:: -To enable caching, modify the code of your front controller. You can also make these -changes to ``app_dev.php`` to add caching to the ``dev`` environment:: + // src/CacheKernel.php + namespace App; - // web/app.php - use Symfony\Component\HttpFoundation\Request; + use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; + + class CacheKernel extends HttpCache + { + } + +Modify the code of your front controller to wrap the default kernel into the +caching kernel: + +.. code-block:: diff + + // public/index.php + + + use App\CacheKernel; + use App\Kernel; // ... - $kernel = new AppKernel('prod', false); - $kernel->loadClassCache(); + $env = $_SERVER['APP_ENV'] ?? 'dev'; + $debug = (bool) ($_SERVER['APP_DEBUG'] ?? ('prod' !== $env)); + // ... + $kernel = new Kernel($env, $debug); - // add (or uncomment) this new line! - // wrap the default AppKernel with the AppCache one - $kernel = new AppCache($kernel); + + // Wrap the default Kernel with the CacheKernel one in 'prod' environment + + if ('prod' === $env) { + + $kernel = new CacheKernel($kernel); + + } $request = Request::createFromGlobals(); // ... @@ -114,15 +129,17 @@ from your application and returning them to the client. error_log($kernel->getLog()); -The ``AppCache`` object has a sensible default configuration, but it can be +The ``CacheKernel`` object has a sensible default configuration, but it can be finely tuned via a set of options you can set by overriding the :method:`Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache::getOptions` method:: - // app/AppCache.php + // src/CacheKernel.php + namespace App; + use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; - class AppCache extends HttpCache + class CacheKernel extends HttpCache { protected function getOptions() { @@ -136,10 +153,9 @@ method:: For a full list of the options and their meaning, see the :method:`HttpCache::__construct() documentation `. -When you're in debug mode (either because your booting a ``debug`` kernel, like -in ``app_dev.php`` *or* you manually set the ``debug`` option to true), Symfony -automatically adds an ``X-Symfony-Cache`` header to the response. Use this to get -information about cache hits and misses. +When you're in debug mode (the second argument of ``Kernel`` constructor in the +front controller is ``true``), Symfony automatically adds an ``X-Symfony-Cache`` +header to the response. Use this to get information about cache hits and misses. .. _http-cache-symfony-versus-varnish: @@ -209,11 +225,11 @@ Expiration Caching The *easiest* way to cache a response is by caching it for a specific amount of time:: - // src/AppBundle/Controller/BlogController.php + // src/Controller/BlogController.php use Symfony\Component\HttpFoundation\Response; // ... - public function indexAction() + public function index() { // somehow create a Response object, like by rendering a template $response = $this->render('blog/index.html.twig', []); @@ -342,6 +358,28 @@ When pages contain dynamic parts, you may not be able to cache entire pages, but only parts of it. Read :doc:`/http_cache/esi` to find out how to configure different cache strategies for specific parts of your page. +HTTP Caching and User Sessions +------------------------------ + +Whenever the session is started during a request, Symfony turns the response +into a private non-cacheable response. This is the best default behavior to not +cache private user information (e.g. a shopping cart, a user profile details, +etc.) and expose it to other visitors. + +However, even requests making use of the session can be cached under some +circumstances. For example, information related to some user group could be +cached for all the users belonging to that group. Handling these advanced +caching scenarios is out of the scope of Symfony, but they can be solved with +the `FOSHttpCacheBundle`_. + +In order to disable the default Symfony behavior that makes requests using the +session uncacheable, add the following internal header to your response and +Symfony won't modify it:: + + use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener; + + $response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true'); + Summary ------- diff --git a/http_cache/_expiration-and-validation.rst.inc b/http_cache/_expiration-and-validation.rst.inc index 26cc483845d..3ae2113e242 100644 --- a/http_cache/_expiration-and-validation.rst.inc +++ b/http_cache/_expiration-and-validation.rst.inc @@ -1,10 +1,10 @@ .. sidebar:: Expiration and Validation - You can of course use both validation and expiration within the same ``Response``. - As expiration wins over validation, you can benefit from the best of both worlds. - In other words, by using both expiration and validation, you can instruct the cache - to serve the cached content, while checking back at some interval (the expiration) - to verify that the content is still valid. + You can use both validation and expiration within the same ``Response``. + As expiration wins over validation, you can benefit from the best of + both worlds. In other words, by using both expiration and validation, you + can instruct the cache to serve the cached content, while checking back + at some interval (the expiration) to verify that the content is still valid. .. tip:: diff --git a/http_cache/cache_invalidation.rst b/http_cache/cache_invalidation.rst index 764ccd09610..0d062df58b2 100644 --- a/http_cache/cache_invalidation.rst +++ b/http_cache/cache_invalidation.rst @@ -47,16 +47,18 @@ the word "PURGE" is a convention, technically this can be any string) instead of ``GET`` and make the cache proxy detect this and remove the data from the cache instead of going to the application to get a response. -Here is how you can configure the Symfony reverse proxy to support the -``PURGE`` HTTP method:: +Here is how you can configure the Symfony reverse proxy (See :doc:`/http_cache`) +to support the ``PURGE`` HTTP method:: + + // src/CacheKernel.php + namespace App; - // app/AppCache.php use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; // ... - class AppCache extends HttpCache + class CacheKernel extends HttpCache { protected function invalidate(Request $request, $catch = false) { diff --git a/http_cache/esi.rst b/http_cache/esi.rst index a6633089238..295a84b2816 100644 --- a/http_cache/esi.rst +++ b/http_cache/esi.rst @@ -62,22 +62,22 @@ First, to use ESI, be sure to enable it in your application configuration: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: # ... esi: { enabled: true } .. code-block:: xml - + - + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -87,7 +87,7 @@ First, to use ESI, be sure to enable it in your application configuration: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ // ... 'esi' => ['enabled' => true], @@ -97,12 +97,12 @@ Now, suppose you have a page that is relatively static, except for a news ticker at the bottom of the content. With ESI, you can cache the news ticker independently of the rest of the page:: - // src/AppBundle/Controller/DefaultController.php + // src/Controller/DefaultController.php // ... - class DefaultController extends Controller + class DefaultController extends AbstractController { - public function aboutAction() + public function about() { $response = $this->render('static/about.html.twig'); // sets the shared max age - which also marks the response as public @@ -122,10 +122,10 @@ matter), Symfony uses the standard ``render`` helper to configure ESI tags: .. code-block:: twig - {# app/Resources/views/static/about.html.twig #} + {# templates/static/about.html.twig #} {# you can use a controller reference #} - {{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }} + {{ render_esi(controller('App\\Controller\\NewsController::latest', { 'maxPerPage': 5 })) }} {# ... or a URL #} {{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }} @@ -161,13 +161,13 @@ used ``render()``. The embedded action can now specify its own caching rules entirely independently of the master page:: - // src/AppBundle/Controller/NewsController.php - namespace AppBundle\Controller; + // src/Controller/NewsController.php + namespace App\Controller; // ... - class NewsController extends Controller + class NewsController extends AbstractController { - public function latestAction($maxPerPage) + public function latest($maxPerPage) { // ... $response->setSharedMaxAge(60); @@ -192,14 +192,14 @@ that must be enabled in your configuration: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: # ... fragments: { path: /_fragment } .. code-block:: xml - + + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -217,7 +217,7 @@ that must be enabled in your configuration: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ // ... 'fragments' => ['path' => '/_fragment'], diff --git a/http_cache/form_csrf_caching.rst b/http_cache/form_csrf_caching.rst deleted file mode 100644 index bae8894dbb1..00000000000 --- a/http_cache/form_csrf_caching.rst +++ /dev/null @@ -1,43 +0,0 @@ -.. index:: - single: Cache; CSRF; Forms - -Caching Pages that Contain CSRF Protected Forms -=============================================== - -CSRF tokens are meant to be different for every user. This is why you -need to be cautious if you try to cache pages with forms including them. - -For more information about how CSRF protection works in Symfony, please -check :doc:`CSRF Protection `. - -Why Caching Pages with a CSRF token is Problematic --------------------------------------------------- - -Typically, each user is assigned a unique CSRF token, which is stored in -the session for validation. This means that if you *do* cache a page with -a form containing a CSRF token, you'll cache the CSRF token of the *first* -user only. When a user submits the form, the token won't match the token -stored in the session and all users (except for the first) will fail CSRF -validation when submitting the form. - -In fact, many reverse proxies (like Varnish) will refuse to cache a page -with a CSRF token. This is because a cookie is sent in order to preserve -the PHP session open and Varnish's default behavior is to not cache HTTP -requests with cookies. - -How to Cache Most of the Page and still be able to Use CSRF Protection ----------------------------------------------------------------------- - -To cache a page that contains a CSRF token, you can use more advanced caching -techniques like :doc:`ESI fragments `, where you cache the full -page and embedding the form inside an ESI tag with no cache at all. - -Another option would be to load the form via an uncached AJAX request, but -cache the rest of the HTML response. - -Or you can even load just the CSRF token with an AJAX request and replace the -form field value with it. Take a look at :doc:`hinclude.js ` -for a nice solution. - -.. _`Cross-site request forgery`: http://en.wikipedia.org/wiki/Cross-site_request_forgery -.. _`Security CSRF Component`: https://github.com/symfony/security-csrf diff --git a/http_cache/validation.rst b/http_cache/validation.rst index 64fdf370579..b82c9bee5fd 100644 --- a/http_cache/validation.rst +++ b/http_cache/validation.rst @@ -23,8 +23,8 @@ page again (see below for an implementation example). The 304 status code means "Not Modified". It's important because with this status code the response does *not* contain the actual content being - requested. Instead, the response is a light-weight set of directions that - tells the cache that it should use its stored version. + requested. Instead, the response only consists of the response headers that + tells the cache that it can use its stored version of the content. Like with expiration, there are two different HTTP headers that can be used to implement the validation model: ``ETag`` and ``Last-Modified``. @@ -48,15 +48,15 @@ each ``ETag`` must be unique across all representations of the same resource. To see a simple implementation, generate the ETag as the md5 of the content:: - // src/AppBundle/Controller/DefaultController.php - namespace AppBundle\Controller; + // src/Controller/DefaultController.php + namespace App\Controller; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; - class DefaultController extends Controller + class DefaultController extends AbstractController { - public function homepageAction(Request $request) + public function homepage(Request $request) { $response = $this->render('static/homepage.html.twig'); $response->setEtag(md5($response->getContent())); @@ -111,17 +111,17 @@ For instance, you can use the latest update date for all the objects needed to compute the resource representation as the value for the ``Last-Modified`` header value:: - // src/AppBundle/Controller/ArticleController.php - namespace AppBundle\Controller; + // src/Controller/ArticleController.php + namespace App\Controller; // ... use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; - use AppBundle\Entity\Article; + use App\Entity\Article; - class ArticleController extends Controller + class ArticleController extends AbstractController { - public function showAction(Article $article, Request $request) + public function show(Article $article, Request $request) { $author = $article->getAuthor(); @@ -171,16 +171,16 @@ Put another way, the less you do in your application to return a 304 response, the better. The ``Response::isNotModified()`` method does exactly that by exposing a simple and efficient pattern:: - // src/AppBundle/Controller/ArticleController.php - namespace AppBundle\Controller; + // src/Controller/ArticleController.php + namespace App\Controller; // ... use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; - class ArticleController extends Controller + class ArticleController extends AbstractController { - public function showAction($articleSlug, Request $request) + public function show($articleSlug, Request $request) { // Get the minimum information to compute // the ETag or the Last-Modified value diff --git a/http_cache/varnish.rst b/http_cache/varnish.rst index 43d4473b9f2..4274fa6bbe1 100644 --- a/http_cache/varnish.rst +++ b/http_cache/varnish.rst @@ -5,7 +5,7 @@ How to Use Varnish to Speed up my Website ========================================= Because Symfony's cache uses the standard HTTP cache headers, the -:ref:`symfony-gateway-cache` can easily be replaced with any other reverse +:ref:`symfony-gateway-cache` can be replaced with any other reverse proxy. `Varnish`_ is a powerful, open-source, HTTP accelerator capable of serving cached content fast and including support for :doc:`Edge Side Includes `. @@ -62,10 +62,10 @@ If you know for sure that the backend never uses sessions or basic authentication, have Varnish remove the corresponding header from requests to prevent clients from bypassing the cache. In practice, you will need sessions at least for some parts of the site, e.g. when using forms with -:doc:`CSRF Protection `. In this situation, make sure to -:doc:`only start a session when actually needed ` +:doc:`CSRF Protection `. In this situation, make sure to +:ref:`only start a session when actually needed ` and clear the session when it is no longer needed. Alternatively, you can look -into :doc:`/http_cache/form_csrf_caching`. +into :ref:`caching pages that contain CSRF protected forms `. Cookies created in JavaScript and used only in the frontend, e.g. when using Google Analytics, are nonetheless sent to the server. These cookies are not @@ -211,7 +211,7 @@ Symfony adds automatically: .. tip:: If you followed the advice about ensuring a consistent caching - behavior, those VCL functions already exist. Just append the code + behavior, those VCL functions already exist. Append the code to the end of the function, they won't interfere with each other. .. index:: diff --git a/index.rst b/index.rst index 7bf5ec89109..30b8a473ef0 100644 --- a/index.rst +++ b/index.rst @@ -14,8 +14,7 @@ Get started fast with the Symfony :doc:`Quick Tour `: quick_tour/index * :doc:`quick_tour/the_big_picture` -* :doc:`quick_tour/the_view` -* :doc:`quick_tour/the_controller` +* :doc:`quick_tour/flex_recipes` * :doc:`quick_tour/the_architecture` Getting Started @@ -35,7 +34,6 @@ Topics bundles console doctrine - debug deployment email event_dispatcher @@ -43,6 +41,8 @@ Topics frontend http_cache logging + mercure + messenger performance profiler routing diff --git a/introduction/from_flat_php_to_symfony2.rst b/introduction/from_flat_php_to_symfony2.rst index bd678900a5c..3af19f3fce1 100644 --- a/introduction/from_flat_php_to_symfony2.rst +++ b/introduction/from_flat_php_to_symfony2.rst @@ -128,7 +128,7 @@ is primarily an HTML file that uses a template-like PHP syntax: By convention, the file that contains all the application logic - ``index.php`` - is known as a "controller". The term controller is a word you'll hear a lot, -regardless of the language or framework you use. It refers simply to the area +regardless of the language or framework you use. It refers to the area of *your* code that processes user input and prepares the response. In this case, the controller prepares data from the database and then includes @@ -181,7 +181,7 @@ of the application are isolated in a new file called ``model.php``:: in this example, only a portion (or none) of the model is actually concerned with accessing a database. -The controller (``index.php``) is now very simple:: +The controller (``index.php``) is now just a few lines of code:: // index.php require_once 'model.php'; @@ -192,7 +192,7 @@ The controller (``index.php``) is now very simple:: Now, the sole task of the controller is to get data from the model layer of the application (the model) and to call a template to render that data. -This is a very simple example of the model-view-controller pattern. +This is a very concise example of the model-view-controller pattern. Isolating the Layout ~~~~~~~~~~~~~~~~~~~~ @@ -262,7 +262,7 @@ an individual blog result based on a given id:: { $connection = open_database_connection(); - $query = 'SELECT created_at, title, body FROM post WHERE id=:id'; + $query = 'SELECT created_at, title, body FROM post WHERE id=:id'; $statement = $connection->prepare($query); $statement->bindValue(':id', $id, PDO::PARAM_INT); $statement->execute(); @@ -303,7 +303,7 @@ the individual blog post: -Creating the second page is now very easy and no code is duplicated. Still, +Creating the second page now requires very little work and no code is duplicated. Still, this page introduces even more lingering problems that a framework can solve for you. For example, a missing or invalid ``id`` query parameter will cause the page to crash. It would be better if this caused a 404 page to be rendered, @@ -430,7 +430,7 @@ content: { "require": { - "symfony/symfony": "3.1.*" + "symfony/http-foundation": "^4.0" }, "autoload": { "files": ["model.php","controllers.php"] @@ -533,32 +533,30 @@ a simple application. Along the way, you've made a simple routing system and a method using ``ob_start()`` and ``ob_get_clean()`` to render templates. If, for some reason, you needed to continue building this "framework" from scratch, you could at least use Symfony's standalone -:doc:`Routing ` and -:doc:`Templating ` components, which already -solve these problems. +:doc:`Routing ` component and :doc:`Twig `, +which already solve these problems. Instead of re-solving common problems, you can let Symfony take care of them for you. Here's the same sample application, now built in Symfony:: - // src/AppBundle/Controller/BlogController.php - namespace AppBundle\Controller; + // src/Controller/BlogController.php + namespace App\Controller; - use AppBundle\Entity\Post; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use App\Entity\Post; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - class BlogController extends Controller + class BlogController extends AbstractController { - public function listAction() + public function list() { $posts = $this->getDoctrine() - ->getManager() - ->createQuery('SELECT p FROM AppBundle:Post p') - ->execute(); + ->getRepository(Post::class) + ->findAll(); - return $this->render('Blog/list.html.php', ['posts' => $posts]); + return $this->render('blog/list.html.twig', ['posts' => $posts]); } - public function showAction($id) + public function show($id) { $post = $this->getDoctrine() ->getRepository(Post::class) @@ -569,7 +567,7 @@ them for you. Here's the same sample application, now built in Symfony:: throw $this->createNotFoundException(); } - return $this->render('Blog/show.html.php', ['post' => $post]); + return $this->render('blog/show.html.twig', ['post' => $post]); } } @@ -580,79 +578,79 @@ nice way to group related pages. The controller functions are also sometimes cal The two controllers (or actions) are still lightweight. Each uses the :doc:`Doctrine ORM library ` to retrieve objects from the database and the Templating component to render a template and return a -``Response`` object. The ``list.php`` template is now quite a bit simpler: +``Response`` object. The ``list.html.twig`` template is now quite a bit simpler, +and uses Twig: -.. code-block:: html+php +.. code-block:: html+twig - - extend('layout.html.php') ?> + + {% extends 'base.html.twig' %} - set('title', 'List of Posts') ?> + {% block title %}List of Posts{% endblock %} + {% block body %}

List of Posts

+ {% endblock %} The ``layout.php`` file is nearly identical: -.. code-block:: html+php +.. code-block:: html+twig - + - <?= $view['slots']->output( - 'title', - 'Default title' - ) ?> + + {% block title %}Welcome!{% endblock %} + {% block stylesheets %}{% endblock %} - output('_content') ?> + {% block body %}{% endblock %} + {% block javascripts %}{% endblock %} .. note:: - The ``show.php`` template is left as an exercise: updating it should be - really similar to updating the ``list.php`` template. + The ``show.html.twig`` template is left as an exercise: updating it should be + really similar to updating the ``list.html.twig`` template. When Symfony's engine (called the Kernel) boots up, it needs a map so that it knows which controllers to execute based on the request information. -A routing configuration map - ``app/config/routing.yml`` - provides this information +A routing configuration map - ``config/routes.yaml`` - provides this information in a readable format: .. code-block:: yaml - # app/config/routing.yml + # config/routes.yaml blog_list: path: /blog - defaults: { _controller: AppBundle:Blog:list } + controller: App\Controller\BlogController::list blog_show: path: /blog/show/{id} - defaults: { _controller: AppBundle:Blog:show } + controller: App\Controller\BlogController::show Now that Symfony is handling all the mundane tasks, the front controller -``web/app.php`` is dead simple. And since it does so little, you'll never +``public/index.php`` is reduced to bootstrapping. And since it does so little, you'll never have to touch it:: - // web/app.php + // public/index.php require_once __DIR__.'/../app/bootstrap.php'; - require_once __DIR__.'/../app/AppKernel.php'; + require_once __DIR__.'/../src/Kernel.php'; use Symfony\Component\HttpFoundation\Request; - $kernel = new AppKernel('prod', false); + $kernel = new Kernel('prod', false); $kernel->handle(Request::createFromGlobals())->send(); The front controller's only job is to initialize Symfony's engine (called the @@ -666,57 +664,9 @@ object are sent back to the client. It's a beautiful thing. -.. figure:: /_images/http/request-flow.png - :align: center - :alt: Symfony request flow - -Better Templates -~~~~~~~~~~~~~~~~ - -If you choose to use it, Symfony comes with a templating engine -called `Twig`_ that makes templates faster to write and easier to read. -It means that the sample application could contain even less code! For -example, rewriting the ``list.html.php`` template in Twig would look like -this: - -.. code-block:: html+twig - - {# app/Resources/views/blog/list.html.twig #} - {% extends "layout.html.twig" %} - - {% block title %}List of Posts{% endblock %} - - {% block body %} -

List of Posts

- - {% endblock %} - -And rewriting the ``layout.html.php`` template in Twig would look like this: - -.. code-block:: html+twig - - {# app/Resources/views/layout.html.twig #} - - - - {% block title %}Default title{% endblock %} - - - {% block body %}{% endblock %} - - +.. raw:: html -Twig is well-supported in Symfony. And while PHP templates will always -be supported in Symfony, the many advantages of Twig will continue to -be discussed. For more information, see the :doc:`templating article `. + Where Symfony Delivers ---------------------- diff --git a/introduction/http_fundamentals.rst b/introduction/http_fundamentals.rst index 13c14800dc9..82c08057116 100644 --- a/introduction/http_fundamentals.rst +++ b/introduction/http_fundamentals.rst @@ -24,7 +24,7 @@ to communicate with each other. For example, when checking for the latest :align: center And while the actual language used is a bit more formal, it's still dead-simple. -HTTP is the term used to describe this simple text-based language. The goal of +HTTP is the term used to describe this text-based language. The goal of your server is *always* to understand text requests and return text responses. Symfony is built from the ground up around that reality. Whether you realize @@ -57,7 +57,7 @@ In HTTP-speak, this HTTP request would actually look something like this: Accept: text/html User-Agent: Mozilla/5.0 (Macintosh) -This simple message communicates *everything* necessary about exactly which +These few lines communicate *everything* necessary about exactly which resource the client is requesting. The first line of an HTTP request is the most important, because it contains two important things: the HTTP method (GET) and the URI (``/``). @@ -245,7 +245,7 @@ Symfony Response Object ~~~~~~~~~~~~~~~~~~~~~~~ Symfony also provides a :class:`Symfony\\Component\\HttpFoundation\\Response` -class: a simple PHP representation of an HTTP response message. This allows your +class: a PHP representation of an HTTP response message. This allows your application to use an object-oriented interface to construct the response that needs to be returned to the client:: @@ -275,7 +275,7 @@ and more. that you can use in *any* PHP project. This also contains classes for handling sessions, file uploads and more. -If Symfony offered nothing else, you would already have a toolkit for easily +If Symfony offered nothing else, you would already have a toolkit for conveniently accessing request information and an object-oriented interface for creating the response. Even as you learn the many powerful features in Symfony, keep in mind that the goal of your application is always *to interpret a request @@ -285,7 +285,7 @@ The Journey from the Request to the Response -------------------------------------------- Like HTTP itself, using the ``Request`` and ``Response`` objects is pretty -simple. The hard part of building an application is writing what comes in +straightforward. The hard part of building an application is writing what comes in between. In other words, the real work comes in writing the code that interprets the request information and creates the response. @@ -361,12 +361,12 @@ to do: .. _request-flow-figure: -.. figure:: /_images/http/request-flow.png - :align: center - :alt: Symfony request flow +.. raw:: html + + - Incoming requests are interpreted by the :doc:`Routing component ` and - passed to PHP functions that return ``Response`` objects. +Incoming requests are interpreted by the :doc:`Routing component ` and +passed to PHP functions that return ``Response`` objects. This may not make sense yet, but as you keep reading, you'll learn about :doc:`routes ` and :doc:`controllers `: the two fundamental parts to creating a page. diff --git a/logging.rst b/logging.rst index 207c7726dc9..a4243b4c6dd 100644 --- a/logging.rst +++ b/logging.rst @@ -27,11 +27,8 @@ To log a message, inject the default logger in your controller:: use Psr\Log\LoggerInterface; - public function indexAction(LoggerInterface $logger) + public function index(LoggerInterface $logger) { - // alternative way of getting the logger - // $logger = $this->get('logger'); - $logger->info('I just got the logger'); $logger->error('An error occurred'); @@ -67,12 +64,8 @@ The following sections assume that Monolog is installed. Where Logs are Stored --------------------- -The configuration for *where* logs are stored lives in the specific -:doc:`environment ` configuration files: ``config_dev.yml`` -and ``config_prod.yml``. - -By default, log entries are written to the ``var/logs/dev.log`` file when you're in -the ``dev`` environment. In the ``prod`` environment, logs are written to ``var/logs/prod.log``, +By default, log entries are written to the ``var/log/dev.log`` file when you're in +the ``dev`` environment. In the ``prod`` environment, logs are written to ``var/log/prod.log``, but *only* during a request where an error or high-priority log entry was made (i.e. ``error()`` , ``critical()``, ``alert()`` or ``emergency()``). @@ -91,8 +84,8 @@ to different locations (e.g. files, database, Slack, etc). channel can have its *own* handlers, which means you can store different log messages in different places. See :doc:`/logging/channels_handlers`. -Symfony pre-configures some basic handlers in the ``config_dev.yml`` and ``config_prod.yml`` -files. Check these out for some real-world examples. +Symfony pre-configures some basic handlers in the default ``monolog.yaml`` +config files. Check these out for some real-world examples. This example uses *two* handlers: ``stream`` (to write to a file) and ``syslog`` to write logs using the :phpfunction:`syslog` function: @@ -101,13 +94,13 @@ to write logs using the :phpfunction:`syslog` function: .. code-block:: yaml - # app/config/config.yml + # config/packages/prod/monolog.yaml monolog: handlers: # this "file_log" key could be anything file_log: type: stream - # log to var/logs/(environment).log + # log to var/log/(environment).log path: "%kernel.logs_dir%/%kernel.environment%.log" # log *all* messages (debug is lowest level) level: debug @@ -119,7 +112,7 @@ to write logs using the :phpfunction:`syslog` function: .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> loadFromExtension('monolog', [ 'handlers' => [ 'file_log' => [ @@ -177,7 +170,7 @@ one of the messages reaches an ``action_level``. Take this example: .. code-block:: yaml - # app/config/config.yml + # config/packages/prod/monolog.yaml monolog: handlers: filter_for_errors: @@ -198,7 +191,7 @@ one of the messages reaches an ``action_level``. Take this example: .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> loadFromExtension('monolog', [ 'handlers' => [ 'filter_for_errors' => [ @@ -284,14 +277,14 @@ Linux command to rotate log files before they become too large. Another option is to have Monolog rotate the files for you by using the ``rotating_file`` handler. This handler creates a new log file every day -and can also remove old files automatically. To use it, just set the ``type`` +and can also remove old files automatically. To use it, set the ``type`` option of your handler to ``rotating_file``: .. configuration-block:: .. code-block:: yaml - # app/config/config_dev.yml + # config/packages/prod/monolog.yaml monolog: handlers: main: @@ -304,7 +297,7 @@ option of your handler to ``rotating_file``: .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> @@ -76,7 +75,7 @@ in all environments, or just ``config_prod.yml`` to happen only in ``prod``: .. code-block:: php - // app/config/config.php + // config/packages/prod/monolog.php $container->loadFromExtension('monolog', [ 'handlers' => [ 'security' => [ @@ -138,20 +137,20 @@ You can also configure additional channels without the need to tag your services .. code-block:: yaml - # app/config/config.yml + # config/packages/prod/monolog.yaml monolog: channels: ['foo', 'bar'] .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> foo @@ -161,7 +160,7 @@ You can also configure additional channels without the need to tag your services .. code-block:: php - // app/config/config.php + // config/packages/prod/monolog.php $container->loadFromExtension('monolog', [ 'channels' => [ 'foo', diff --git a/logging/disable_microsecond_precision.rst b/logging/disable_microsecond_precision.rst deleted file mode 100644 index e2397a12fa7..00000000000 --- a/logging/disable_microsecond_precision.rst +++ /dev/null @@ -1,57 +0,0 @@ -How to Disable Microseconds Precision (for a Performance Boost) -=============================================================== - -Setting the parameter ``use_microseconds`` to ``false`` forces the logger to reduce -the precision in the ``datetime`` field of the log messages from microsecond to second, -avoiding a call to the ``microtime(true)`` function and the subsequent parsing. -Disabling the use of microseconds can provide a small performance gain speeding up the -log generation. This is recommended for systems that generate a large number of log events. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - monolog: - use_microseconds: false - handlers: - applog: - type: stream - path: /var/log/symfony.log - level: error - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('monolog', [ - 'use_microseconds' => false, - 'handlers' => [ - 'applog' => [ - 'type' => 'stream', - 'path' => '/var/log/symfony.log', - 'level' => 'error', - ], - ], - ]); diff --git a/logging/formatter.rst b/logging/formatter.rst index dfde6aabbff..e769ef48318 100644 --- a/logging/formatter.rst +++ b/logging/formatter.rst @@ -13,13 +13,13 @@ configure your handler to use it: .. code-block:: yaml - # app/config/services.yml + # config/services.yaml services: # ... Monolog\Formatter\JsonFormatter: ~ - # app/config/config_prod.yml (and/or config_dev.yml) + # config/packages/prod/monolog.yaml (and/or config/packages/dev/monolog.yaml) monolog: handlers: file: @@ -29,7 +29,7 @@ configure your handler to use it: .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> - + register(JsonFormatter::class); - // app/config/config_prod.php (or config_dev.php) + // config/packages/prod/monolog.php (and/or config/packages/dev/monolog.php) $container->loadFromExtension('monolog', [ 'handlers' => [ 'file' => [ diff --git a/logging/monolog_console.rst b/logging/monolog_console.rst index 46bb96f2155..0713fa6e8bd 100644 --- a/logging/monolog_console.rst +++ b/logging/monolog_console.rst @@ -34,16 +34,27 @@ current log level and the console verbosity. The example above could then be rewritten as:: + use Psr\Log\LoggerInterface; + use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; + // ... - protected function execute(InputInterface $input, OutputInterface $output) + class YourCommand extends Command { - // assuming the Command extends ContainerAwareCommand... - $logger = $this->getContainer()->get('logger'); - $logger->debug('Some info'); + private $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } - $logger->notice('Some more info'); + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->logger->debug('Some info'); + // ... + $this->logger->notice('Some more info'); + } } Depending on the verbosity level that the command is run in and the user's @@ -52,14 +63,13 @@ the console. If they are displayed, they are timestamped and colored appropriate Additionally, error logs are written to the error output (php://stderr). There is no need to conditionally handle the verbosity settings anymore. -The Monolog console handler is enabled by default in the Symfony Framework. For -example, in ``config_dev.yml``: +The Monolog console handler is enabled by default: .. configuration-block:: .. code-block:: yaml - # app/config/config_dev.yml + # config/packages/dev/monolog.yaml monolog: handlers: # ... @@ -74,7 +84,7 @@ example, in ``config_dev.yml``: .. code-block:: xml - + loadFromExtension('monolog', [ 'handlers' => [ 'console' => [ - 'type' => 'console', - 'process_psr_3_messages' => false, - 'channels' => ['!event', '!doctrine', '!console'], + 'type' => 'console', + 'process_psr_3_messages' => false, + 'channels' => ['!event', '!doctrine', '!console'], ], ], ]); diff --git a/logging/monolog_email.rst b/logging/monolog_email.rst index 14a1cb505ec..372f4709502 100644 --- a/logging/monolog_email.rst +++ b/logging/monolog_email.rst @@ -14,7 +14,7 @@ it is broken down. .. code-block:: yaml - # app/config/config_prod.yml + # config/packages/prod/monolog.yaml monolog: handlers: main: @@ -42,14 +42,14 @@ it is broken down. .. code-block:: xml - + + http://symfony.com/schema/dic/monolog https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> + loadFromExtension('monolog', [ 'handlers' => [ // ... @@ -196,7 +196,7 @@ get logged on the server as well as the emails being sent: .. code-block:: yaml - # app/config/config_prod.yml + # config/packages/prod/monolog.yaml monolog: handlers: main: @@ -224,13 +224,13 @@ get logged on the server as well as the emails being sent: .. code-block:: xml - + + http://symfony.com/schema/dic/monolog https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> loadFromExtension('monolog', [ 'handlers' => [ 'main' => [ diff --git a/logging/monolog_exclude_http_codes.rst b/logging/monolog_exclude_http_codes.rst new file mode 100644 index 00000000000..c0bc4f92fa6 --- /dev/null +++ b/logging/monolog_exclude_http_codes.rst @@ -0,0 +1,61 @@ +.. index:: + single: Logging + single: Logging; Exclude HTTP Codes + single: Monolog; Exclude HTTP Codes + +How to Configure Monolog to Exclude Specific HTTP Codes from the Log +==================================================================== + +Sometimes your logs become flooded with unwanted HTTP errors, for example, +403s and 404s. When using a ``fingers_crossed`` handler, you can exclude +logging these HTTP codes based on the MonologBundle configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/prod/monolog.yaml + monolog: + handlers: + main: + # ... + type: fingers_crossed + handler: ... + excluded_http_codes: [403, 404, { 400: ['^/foo', '^/bar'] }] + + .. code-block:: xml + + + + + + + + + ^/foo + ^/bar + + + + + + + .. code-block:: php + + // config/packages/prod/monolog.php + $container->loadFromExtension('monolog', [ + 'handlers' => [ + 'main' => [ + // ... + 'type' => 'fingers_crossed', + 'handler' => ..., + 'excluded_http_codes' => [403, 404], + ], + ], + ]); diff --git a/logging/monolog_regex_based_excludes.rst b/logging/monolog_regex_based_excludes.rst index c7005ddd35b..37cf9ded6f8 100644 --- a/logging/monolog_regex_based_excludes.rst +++ b/logging/monolog_regex_based_excludes.rst @@ -6,6 +6,12 @@ How to Configure Monolog to Exclude 404 Errors from the Log =========================================================== +.. tip:: + + Read :doc:`/logging/monolog_exclude_http_codes` to learn about a similar + but more generic feature that allows to exclude logs for any HTTP status + code and not only 404 errors. + Sometimes your logs become flooded with unwanted 404 HTTP errors, for example, when an attacker scans your app for some well-known application paths (e.g. `/phpmyadmin`). When using a ``fingers_crossed`` handler, you can exclude @@ -16,7 +22,7 @@ configuration: .. code-block:: yaml - # app/config/config.yml + # config/packages/prod/monolog.yaml monolog: handlers: main: @@ -28,14 +34,14 @@ configuration: .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> @@ -47,7 +53,7 @@ configuration: .. code-block:: php - // app/config/config.php + // config/packages/prod/monolog.php $container->loadFromExtension('monolog', [ 'handlers' => [ 'main' => [ diff --git a/logging/processors.rst b/logging/processors.rst index 56dd9b514eb..691185de322 100644 --- a/logging/processors.rst +++ b/logging/processors.rst @@ -16,7 +16,7 @@ Sometimes it is hard to tell which entries in the log belong to which session and/or request. The following example will add a unique token for each request using a processor:: - namespace AppBundle\Logger; + namespace App\Logger; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -53,21 +53,20 @@ information: .. code-block:: yaml - # app/config/services.yml + # config/services.yaml services: monolog.formatter.session_request: class: Monolog\Formatter\LineFormatter arguments: - "[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%%\n" - AppBundle\Logger\SessionRequestProcessor: - autowire: true + App\Logger\SessionRequestProcessor: tags: - { name: monolog.processor } .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> [%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%% - + @@ -92,8 +91,8 @@ information: .. code-block:: php - // app/config/services.php - use AppBundle\Logger\SessionRequestProcessor; + // config/services.php + use App\Logger\SessionRequestProcessor; use Monolog\Formatter\LineFormatter; $container @@ -101,7 +100,7 @@ information: ->addArgument('[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%%\n'); $container - ->autowire(SessionRequestProcessor::class) + ->register(SessionRequestProcessor::class) ->addTag('monolog.processor', ['method' => 'processRecord']); Finally, set the formatter to be used on whatever handler you want: @@ -110,7 +109,7 @@ Finally, set the formatter to be used on whatever handler you want: .. code-block:: yaml - # app/config/config.yml + # config/packages/prod/monolog.yaml monolog: handlers: main: @@ -121,7 +120,7 @@ Finally, set the formatter to be used on whatever handler you want: .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> loadFromExtension('monolog', [ 'handlers' => [ 'main' => [ @@ -170,16 +169,15 @@ the ``monolog.processor`` tag: .. code-block:: yaml - # app/config/config.yml + # config/services.yaml services: - AppBundle\Logger\SessionRequestProcessor: - autowire: true + App\Logger\SessionRequestProcessor: tags: - { name: monolog.processor, handler: main } .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> - + @@ -198,11 +196,11 @@ the ``monolog.processor`` tag: .. code-block:: php - // app/config/config.php + // config/services.php // ... $container - ->autowire(SessionRequestProcessor::class) + ->register(SessionRequestProcessor::class) ->addTag('monolog.processor', ['handler' => 'main']); Registering Processors per Channel @@ -215,16 +213,15 @@ the ``monolog.processor`` tag: .. code-block:: yaml - # app/config/config.yml + # config/services.yaml services: - AppBundle\Logger\SessionRequestProcessor: - autowire: true + App\Logger\SessionRequestProcessor: tags: - { name: monolog.processor, channel: main } .. code-block:: xml - + + https://symfony.com/schema/dic/monolog/monolog-1.0.xsd"> - + @@ -243,9 +240,9 @@ the ``monolog.processor`` tag: .. code-block:: php - // app/config/config.php + // config/services.php // ... $container - ->autowire(SessionRequestProcessor::class) + ->register(SessionRequestProcessor::class) ->addTag('monolog.processor', ['channel' => 'main']); diff --git a/mercure.rst b/mercure.rst new file mode 100644 index 00000000000..34d6ea6231c --- /dev/null +++ b/mercure.rst @@ -0,0 +1,545 @@ +.. index:: + single: Mercure + +Pushing Data to Clients Using the Mercure Protocol +================================================== + +Being able to broadcast data in real-time from servers to clients is a +requirement for many modern web and mobile applications. + +Creating an UI reacting in live to changes made by other users +(e.g. a user changes the data currently browsed by several other users, +all UIs are instantly updated), +notifying the user when :doc:`an asynchronous job ` has been +completed or creating chat applications are among the typical use cases +requiring "push" capabilities. + +Symfony provides a straightforward component, built on top of +`the Mercure protocol`_, specifically designed for this class of use cases. + +Mercure is an open protocol designed from the ground to publish updates from +server to clients. It is a modern and efficient alternative to timer-based +polling and to WebSocket. + +Because it is built on top `Server-Sent Events (SSE)`_, Mercure is supported +out of the box in most modern browsers (Edge and IE require `a polyfill`_) and +has `high-level implementations`_ in many programming languages. + +Mercure comes with an authorization mechanism, +automatic re-connection in case of network issues +with retrieving of lost updates, "connection-less" push for smartphones and +auto-discoverability (a supported client can automatically discover and +subscribe to updates of a given resource thanks to a specific HTTP header). + +All these features are supported in the Symfony integration. + +Unlike WebSocket, which is only compatible with HTTP 1.x, +Mercure leverages the multiplexing capabilities provided by HTTP/2 +and HTTP/3 (but also supports older versions of HTTP). + +`In this recording`_ you can see how a Symfony web API leverages Mercure +and API Platform to update in live a React app and a mobile app (React Native) +generated using the API Platform client generator. + +Installation +------------ + +Installing the Symfony Component +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In applications using :doc:`Symfony Flex `, run this command to +install the Mercure support before using it: + +.. code-block:: terminal + + $ composer require mercure + +Running a Mercure Hub +~~~~~~~~~~~~~~~~~~~~~ + +To manage persistent connections, Mercure relies on a Hub: a dedicated server +that handles persistent SSE connections with the clients. +The Symfony app publishes the updates to the hub, that will broadcast them to +clients. + +.. image:: /_images/mercure/schema.png + +An official and open source (AGPL) implementation of a Hub can be downloaded +as a static binary from `Mercure.rocks`_. + +Run the following command to start it: + +.. code-block:: terminal + + $ JWT_KEY='aVerySecretKey' ADDR='localhost:3000' ALLOW_ANONYMOUS=1 CORS_ALLOWED_ORIGINS=* ./mercure + +.. note:: + + Alternatively to the binary, a Docker image, a Helm chart for Kubernetes + and a managed, High Availability Hub are also provided by Mercure.rocks. + +.. tip:: + + The `API Platform distribution`_ comes with a Docker Compose configuration + as well as a Helm chart for Kubernetes that are 100% compatible with Symfony, + and contain a Mercure hub. + You can copy them in your project, even if you don't use API Platform. + +Configuration +------------- + +The preferred way to configure the MercureBundle is using +:doc:`environment variables `. + +Set the URL of your hub as the value of the ``MERCURE_PUBLISH_URL`` env var. +The ``.env`` file of your project has been updated by the Flex recipe to +provide example values. +Set it to the URL of the Mercure Hub (``http://localhost:3000/hub`` by default). + +In addition, the Symfony application must bear a `JSON Web Token`_ (JWT) +to the Mercure Hub to be authorized to publish updates. + +This JWT should be stored in the ``MERCURE_JWT_SECRET`` environment variable. + +The JWT must be signed with the same secret key than the one used by +the Hub to verify the JWT (``aVerySecretKey`` in our example). +Its payload must contain at least the following structure to be allowed to +publish: + +.. code-block:: json + + { + "mercure": { + "publish": [] + } + } + +Because the array is empty, the Symfony app will only be authorized to publish +public updates (see the authorization_ section for further informations). + +.. tip:: + + The jwt.io website is a convenient way to create and sign JWTs. + Checkout this `example JWT`_, that grants publishing rights for all *targets* + (notice the star in the array). + Don't forget to set your secret key properly in the bottom of the right panel of the form! + +.. caution:: + + Don't put the secret key in ``MERCURE_JWT_SECRET``, it will not work! + This environment variable must contain a JWT, signed with the secret key. + + Also, be sure to keep both the secret key and the JWTs... secrets! + +Basic Usage +----------- + +Publishing +~~~~~~~~~~ + +The Mercure Component provides an ``Update`` value object representing +the update to publish. It also provides a ``Publisher`` service to dispatch +updates to the Hub. + +The ``Publisher`` service can be injected using the +:doc:`autowiring ` in any other +service, including controllers:: + + // src/Controller/PublishController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Mercure\Publisher; + use Symfony\Component\Mercure\Update; + + class PublishController + { + public function __invoke(Publisher $publisher): Response + { + $update = new Update( + 'http://example.com/books/1', + json_encode(['status' => 'OutOfStock']) + ); + + // The Publisher service is an invokable object + $publisher($update); + + return new Response('published!'); + } + } + +The first parameter to pass to the ``Update`` constructor is +the **topic** being updated. This topic should be an IRI_ +(Internationalized Resource Identifier, RFC 3987): a unique identifier +of the resource being dispatched. + +Usually, this parameter contains the original URL of the resource +transmitted to the client, but it can be any valid IRI_, it doesn't +have to be an URL that exists (similarly to XML namespaces). + +The second parameter of the constructor is the content of the update. +It can be anything, stored in any format. +However, serializing the resource in a hypermedia format such as JSON-LD, +Atom, HTML or XML is recommended. + +Subscribing +~~~~~~~~~~~ + +Subscribing to updates in JavaScript is straightforward: + +.. code-block:: javascript + + const es = new EventSource('http://localhost:3000/hub?topic=' + encodeURIComponent('http://example.com/books/1')); + es.onmessage = e => { + // Will be called every time an update is published by the server + console.log(JSON.parse(e.data)); + } + +Mercure also allows to subscribe to several topics, +and to use URI Templates as patterns: + +.. code-block:: javascript + + // URL is a built-in JavaScript class to manipulate URLs + const u = new URL('http://localhost:3000/hub'); + u.searchParams.append('topic', 'http://example.com/books/1'); + // Subscribe to updates of several Book resources + u.searchParams.append('topic', 'http://example.com/books/2'); + // All Review resources will match this pattern + u.searchParams.append('topic', 'http://example.com/reviews/{id}'); + + const es = new EventSource(u); + es.onmessage = e => { + console.log(JSON.parse(e.data)); + } + +.. tip:: + + Google Chrome DevTools natively integrate a `practical UI`_ displaying in live + the received events: + + .. image:: /_images/mercure/chrome.png + + To use it: + + * open the DevTools + * select the "Network" tab + * click on the request to the Mercure hub + * click on the "EventStream" sub-tab. + +.. tip:: + + Test if a URI Template match an URL using `the online debugger`_ + +Async dispatching +----------------- + +Instead of calling the ``Publisher`` service directly, you can also let Symfony +dispatching the updates asynchronously thanks to the provided integration with +the Messenger component. + +First, be sure :doc:`to install the Messenger component ` +and to configure properly a transport (if you don't, the handler will +be called synchronously). + +Then, dispatch the Mercure ``Update`` to the Messenger's Message Bus, +it will be handled automatically:: + + // src/Controller/PublishController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Messenger\MessageBusInterface; + use Symfony\Component\Mercure\Update; + + class PublishController + { + public function __invoke(MessageBusInterface $bus): Response + { + $update = new Update( + 'http://example.com/books/1', + json_encode(['status' => 'OutOfStock']) + ); + + // Sync, or async (RabbitMQ, Kafka...) + $bus->dispatch($update); + + return new Response('published!'); + } + } + +Discovery +--------- + +The Mercure protocol comes with a discovery mechanism. +To leverage it, the Symfony application must expose the URL of the Mercure Hub +in a ``Link`` HTTP header. + +.. image:: /_images/mercure/discovery.png + +You can create ``Link`` headers with the :doc:`WebLink Component `, +by using the ``AbstractController::addLink`` helper method:: + + // src/Controller/DiscoverController.php + namespace App\Controller; + + use Fig\Link\Link; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\JsonResponse; + use Symfony\Component\HttpFoundation\Request; + + class DiscoverController extends AbstractController + { + public function __invoke(Request $request): JsonResponse + { + // This parameter is automatically created by the MercureBundle + $hubUrl = $this->getParameter('mercure.default_hub'); + + // Link: ; rel="mercure" + $this->addLink($request, new Link('mercure', $hubUrl)); + + return $this->json([ + '@id' => '/books/1', + 'availability' => 'https://schema.org/InStock', + ]); + } + } + +Then, this header can be parsed client-side to find the URL of the Hub, +and to subscribe to it: + +.. code-block:: javascript + + // Fetch the original resource served by the Symfony web API + fetch('/books/1') // Has Link: ; rel="mercure" + .then(response => { + // Extract the hub URL from the Link header + const hubUrl = response.headers.get('Link').match(/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/)[1]; + + // Append the topic(s) to subscribe as query parameter + const h = new URL(hubUrl); + h.searchParams.append('topic', 'http://example.com/books/{id}'); + + // Subscribe to updates + const es = new EventSource(h); + es.onmessage = e => console.log(e.data); + }); + +Authorization +------------- + +Mercure also allows to dispatch updates only to authorized clients. +To do so, set the list of **targets** allowed to receive the update +as the third parameter of the ``Update`` constructor:: + + // src/Controller/Publish.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Mercure\Publisher; + use Symfony\Component\Mercure\Update; + + class PublishController + { + public function __invoke(Publisher $publisher): Response + { + $update = new Update( + 'http://example.com/books/1', + json_encode(['status' => 'OutOfStock']), + ['http://example.com/user/kevin', 'http://example.com/groups/admin'] // Here are the targets + ); + + // Publisher's JWT must contain all of these targets or * in mercure.publish or you'll get a 401 + // Subscriber's JWT must contain at least one of these targets or * in mercure.subscribe to receive the update + $publisher($update); + + return new Response('published to the selected targets!'); + } + } + +To subscribe to private updates, subscribers must provide +a JWT containing at least one target marking the update to the Hub. + +To provide this JWT, the subscriber can use a cookie, +or a ``Authorization`` HTTP header. +Cookies are automatically sent by the browsers when opening an ``EventSource`` connection. +They are the most secure and preferred way when the client is a web browser. +If the client is not a web browser, then using an authorization header is the way to go. + +In the following example controller, +the generated cookie contains a JWT, itself containing the appropriate targets. +This cookie will be automatically sent by the web browser when connecting to the Hub. +Then, the Hub will verify the validity of the provided JWT, and extract the targets +from it. + +To generate the JWT, we'll use the ``lcobucci/jwt`` library. Install it: + +.. code-block:: terminal + + $ composer require lcobucci/jwt + +And here is the controller:: + + // src/Controller/DiscoverController.php + namespace App\Controller; + + use Fig\Link\Link; + use Lcobucci\JWT\Builder; + use Lcobucci\JWT\Signer\Hmac\Sha256; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + class DiscoverController extends AbstractController + { + public function __invoke(Request $request): Response + { + $hubUrl = $this->getParameter('mercure.default_hub'); + $this->addLink($request, new Link('mercure', $hubUrl)); + + $username = $this->getUser()->getUsername(); // Retrieve the username of the current user + $token = (new Builder()) + // set other appropriate JWT claims, such as an expiration date + ->set('mercure', ['subscribe' => "http://example.com/user/$username"]) // could also include the security roles, or anything else + ->sign(new Sha256(), $this->getParameter('mercure_secret_key')) // don't forget to set this parameter! Test value: aVerySecretKey + ->getToken(); + + $response = $this->json(['@id' => '/demo/books/1', 'availability' => 'https://schema.org/InStock']); + $response->headers->set( + 'set-cookie', + sprintf('mercureAuthorization=%s; path=/hub; secure; httponly; SameSite=strict', $token) + ); + + return $response; + } + } + +.. caution:: + + To use the cookie authentication method, the Symfony app and the Hub + must be served from the same domain (can be different sub-domains). + +Generating Programmatically The JWT Used to Publish +--------------------------------------------------- + +Instead of directly storing a JWT in the configuration, +you can create a service that will return the token used by +the ``Publisher`` object:: + + // src/Mercure/MyJwtProvider.php + namespace App\Mercure; + + final class MyJwtProvider + { + public function __invoke(): string + { + return 'the-JWT'; + } + } + +Then, reference this service in the bundle configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/mercure.yaml + mercure: + hubs: + default: + url: https://mercure-hub.example.com/hub + jwt_provider: App\Mercure\MyJwtProvider + + .. code-block:: xml + + + + + + + + .. code-block:: php + + // config/packages/mercure.php + use App\Mercure\MyJwtProvider; + + $container->loadFromExtension('mercure', [ + 'hubs' => [ + 'default' => [ + 'url' => 'https://mercure-hub.example.com/hub', + 'jwt_provider' => MyJwtProvider::class, + ], + ], + ]); + +This method is especially convenient when using tokens having an expiration +date, that can be refreshed programmatically. + +Web APIs +-------- + +When creating a web API, it's convenient to be able to instantly push +new versions of the resources to all connected devices, and to update +their views. + +API Platform can use the Mercure Component to dispatch updates automatically, +every time an API resource is created, modified or deleted. + +Start by installing the library using its official recipe: + +.. code-block:: terminal + + $ composer require api + +Then, creating the following entity is enough to get a fully-featured +hypermedia API, and automatic update broadcasting through the Mercure hub:: + + // src/Entity/Book.php + namespace App\Entity; + + use ApiPlatform\Core\Annotation\ApiResource; + use Doctrine\ORM\Mapping as ORM; + + /** + * @ApiResource(mercure=true) + * @ORM\Entity + */ + class Book + { + /** + * @ORM\Id + * @ORM\Column + */ + public $name; + + /** + * @ORM\Column + */ + public $status; + } + +As showcased `in this recording`_, the API Platform Client Generator also +allows to scaffold complete React and React Native applications from this API. +These applications will render the content of Mercure updates in real-time. + +Checkout `the dedicated API Platform documentation`_ to learn more about +its Mercure support. + +.. _`the Mercure protocol`: https://github.com/dunglas/mercure#protocol-specification +.. _`Server-Sent Events (SSE)`: https://developer.mozilla.org/docs/Server-sent_events +.. _`a polyfill`: https://github.com/Yaffle/EventSource +.. _`high-level implementations`: https://github.com/dunglas/mercure#tools +.. _`In this recording`: https://www.youtube.com/watch?v=UI1l0JOjLeI +.. _`API Platform`: https://api-platform.com +.. _`Mercure.rocks`: https://mercure.rocks +.. _`API Platform distribution`: https://api-platform.com/docs/distribution/ +.. _`JSON Web Token`: https://tools.ietf.org/html/rfc7519 +.. _`example JWT`: https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.iHLdpAEjX4BqCsHJEegxRmO-Y6sMxXwNATrQyRNt3GY +.. _`IRI`: https://tools.ietf.org/html/rfc3987 +.. _`practical UI`: https://twitter.com/chromedevtools/status/562324683194785792 +.. _`the dedicated API Platform documentation`: https://api-platform.com/docs/core/mercure/ +.. _`the online debugger`: https://uri-template-tester.mercure.rocks diff --git a/messenger.rst b/messenger.rst new file mode 100644 index 00000000000..698b375153b --- /dev/null +++ b/messenger.rst @@ -0,0 +1,832 @@ +.. index:: + single: Messenger + +How to Use the Messenger +======================== + +Symfony's Messenger provides a message bus and some routing capabilities to send +messages within your application and through transports such as message queues. +Before using it, read the :doc:`Messenger component docs ` +to get familiar with its concepts. + +Installation +------------ + +In applications using :doc:`Symfony Flex `, run this command to +install messenger before using it: + +.. code-block:: terminal + + $ composer require messenger + +Message +------- + +Before you can send a message, you must create it first. There is no specific +requirement for a message, except it should be serializable and unserializable +by a Symfony Serializer instance:: + + // src/Message/SmsNotification.php + namespace App\Message; + + class SmsNotification + { + private $content; + + public function __construct(string $content) + { + $this->content = $content; + } + + // ...getters + } + +Using the Messenger Service +--------------------------- + +Once enabled, the ``message_bus`` service can be injected in any service where +you need it, like in a controller:: + + // src/Controller/DefaultController.php + namespace App\Controller; + + use App\Message\SmsNotification; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Messenger\MessageBusInterface; + + class DefaultController extends AbstractController + { + public function index(MessageBusInterface $bus) + { + $bus->dispatch(new SmsNotification('A string to be sent...')); + } + } + +Registering Handlers +-------------------- + +In order to do something when your message is dispatched, you need to create a +message handler. It's a class with an ``__invoke`` method:: + + // src/MessageHandler/SmsNotificationHandler.php + namespace App\MessageHandler; + + use App\Message\SmsNotification; + use Symfony\Component\Messenger\Handler\MessageHandlerInterface; + + class SmsNotificationHandler implements MessageHandlerInterface + { + public function __invoke(SmsNotification $message) + { + // do something with it. + } + } + +Message handlers must be registered as services and :doc:`tagged ` +with the ``messenger.message_handler`` tag. If you're using the +:ref:`default services.yaml configuration ` and implement +:class:`Symfony\\Component\\Messenger\\Handler\\MessageHandlerInterface` +or :class:`Symfony\\Component\\Messenger\\Handler\\MessageSubscriberInterface`, +this is already done for you, thanks to :ref:`autoconfiguration `. + +If you're not using service autoconfiguration, then you need to add this config: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\MessageHandler\SmsNotificationHandler: + tags: [messenger.message_handler] + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + use App\MessageHandler\SmsNotificationHandler; + + $container->register(SmsNotificationHandler::class) + ->addTag('messenger.message_handler'); + +.. note:: + + If the message cannot be guessed from the handler's type-hint, use the + ``handles`` attribute on the tag. + +Transports +---------- + +By default, messages are processed as soon as they are dispatched. If you prefer +to process messages asynchronously, you must configure a transport. These +transports communicate with your application via queuing systems or third parties. +The built-in AMQP transport allows you to communicate with most of the AMQP +brokers such as RabbitMQ. + +.. note:: + + If you need more message brokers, you should have a look at `Enqueue's transport`_ + which supports things like Kafka, Amazon SQS or Google Pub/Sub. + +A transport is registered using a "DSN", which is a string that represents the +connection credentials and configuration. By default, when you've installed +the messenger component, the following configuration should have been created: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + transports: + amqp: "%env(MESSENGER_TRANSPORT_DSN)%" + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'transports' => [ + 'amqp' => '%env(MESSENGER_TRANSPORT_DSN)%', + ], + ], + ]); + +.. code-block:: bash + + # .env + ###> symfony/messenger ### + MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages + ###< symfony/messenger ### + +This is enough to allow you to route your message to the ``amqp`` transport. +This will also configure the following services for you: + +#. A ``messenger.sender.amqp`` sender to be used when routing messages; +#. A ``messenger.receiver.amqp`` receiver to be used when consuming messages. + +.. note:: + + In order to use Symfony's built-in AMQP transport, you will need the AMQP + PHP extension and the Serializer Component. Ensure that they are installed with: + + .. code-block:: terminal + + $ composer require symfony/amqp-pack + +Routing +------- + +Instead of calling a handler, you have the option to route your message(s) to a +sender. Part of a transport, it is responsible for sending your message somewhere. +You can configure which message is routed to which sender with the following +configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + routing: + 'My\Message\Message': amqp # The name of the defined transport + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'routing' => [ + 'My\Message\Message' => 'amqp', + ], + ], + ]); + +Such configuration would only route the ``My\Message\Message`` message to be +asynchronous, the rest of the messages would still be directly handled. + +You can route all classes of messages to the same sender using an asterisk +instead of a class name: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + routing: + 'My\Message\MessageAboutDoingOperationalWork': another_transport + '*': amqp + + .. code-block:: xml + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'routing' => [ + 'My\Message\Message' => 'another_transport', + '*' => 'amqp', + ], + ], + ]); + +A class of messages can also be routed to multiple senders by specifying a list: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + routing: + 'My\Message\ToBeSentToTwoSenders': [amqp, audit] + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'routing' => [ + 'My\Message\ToBeSentToTwoSenders' => ['amqp', 'audit'], + ], + ], + ]); + +By specifying the ``send_and_handle`` option, you can also route a class of messages to a sender +while still having them passed to their respective handler: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + routing: + 'My\Message\ThatIsGoingToBeSentAndHandledLocally': + senders: [amqp] + send_and_handle: true + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'routing' => [ + 'My\Message\ThatIsGoingToBeSentAndHandledLocally' => [ + 'senders' => ['amqp'], + 'send_and_handle' => true, + ], + ], + ], + ]); + +Consuming Messages +------------------ + +Once your messages have been routed, you will like to consume your messages in most +of the cases. To do so, you can use the ``messenger:consume-messages`` command +like this: + +.. code-block:: terminal + + $ php bin/console messenger:consume-messages amqp + +The first argument is the receiver's service name. It might have been created by +your ``transports`` configuration or it can be your own receiver. +It also requires a ``--bus`` option in case you have multiple buses configured, +which is the name of the bus to which received messages should be dispatched. + +Middleware +---------- + +What happens when you dispatch a message to a message bus(es) depends on its +collection of middleware (and their order). By default, the middleware configured +for each bus looks like this: + +#. ``logging`` middleware. Responsible for logging the beginning and the end of the + message within the bus; + +#. _Your own collection of middleware_; + +#. ``send_message`` middleware. Will route the messages you configured to their + corresponding sender and stop the middleware chain; + +#. ``handle_message`` middleware. Will call the message handler(s) for the + given message. + +.. note:: + + These middleware names are actually shortcuts working by convention. + The real service ids are prefixed with the ``messenger.middleware.`` namespace. + +Disabling default Middleware +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you don't want the default collection of middleware to be present on your bus, +you can disable them like this: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + buses: + messenger.bus.default: + default_middleware: false + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'buses' => [ + 'messenger.bus.default' => [ + 'default_middleware' => false, + ], + ], + ], + ]); + +Adding your own Middleware +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As described in the component documentation, you can add your own middleware +within the buses to add some extra capabilities like this: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + buses: + messenger.bus.default: + middleware: + - 'App\Middleware\MyMiddleware' + - 'App\Middleware\AnotherMiddleware' + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'buses' => [ + 'messenger.bus.default' => [ + 'middleware' => [ + 'App\Middleware\MyMiddleware', + 'App\Middleware\AnotherMiddleware', + ], + ], + ], + ], + ]); + +Note that if the service is abstract, a different instance of the service will +be created per bus. + +Using Middleware Factories +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some third-party bundles and libraries provide configurable middleware via +factories. + +For instance, the ``messenger.middleware.doctrine_transaction`` is a +built-in middleware wired automatically when the DoctrineBundle and the Messenger +component are installed and enabled. +This middleware can be configured to define the entity manager to use: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + buses: + command_bus: + middleware: + # Using the default configured entity manager name + - doctrine_transaction + # Using another entity manager + - doctrine_transaction: ['custom'] + + .. code-block:: xml + + + + + + + + + + + + + custom + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'buses' => [ + 'command_bus' => [ + 'middleware' => [ + // Using the default configured entity manager name + 'doctrine_transaction', + // Using another entity manager + ['id' => 'doctrine_transaction', 'arguments' => ['custom']], + ], + ], + ], + ], + ]); + +Defining such configurable middleware is based on Symfony's +:doc:`dependency injection ` features: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + messenger.middleware.doctrine_transaction: + class: Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware + # Definition is abstract, so a child definition will be created, per bus + abstract: true + # Main dependencies are defined by the parent definitions. + # Arguments provided in the middleware config will be appended on the child definition. + arguments: ['@doctrine'] + + .. code-block:: xml + + + + + + + + abstract="true"> + + + + + + + + .. code-block:: php + + // config/services.php + use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware; + use Symfony\Component\DependencyInjection\Reference; + + $container->register('messenger.middleware.doctrine_transaction', DoctrineTransactionMiddleware::class) + // Definition is abstract, so a child definition will be created, per bus + ->setAbstract(true) + // Main dependencies are defined by the parent definitions. + // Arguments provided in the middleware config will be appended on the child definition. + ->setArguments([new Reference('doctrine')]); + +.. note:: + + Middleware factories only allow appending scalar and array arguments in config + (no references to other services). For most advanced use-cases, register a + concrete definition of the middleware manually and use its id. + +Your own Transport +------------------ + +Once you have written your transport's sender and receiver, you can register your +transport factory to be able to use it via a DSN in the Symfony application. + +Create your Transport Factory +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You need to give FrameworkBundle the opportunity to create your transport from a +DSN. You will need a transport factory:: + + use Symfony\Component\Messenger\Transport\TransportFactoryInterface; + use Symfony\Component\Messenger\Transport\TransportInterface; + use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; + use Symfony\Component\Messenger\Transport\Sender\SenderInterface; + + class YourTransportFactory implements TransportFactoryInterface + { + public function createTransport(string $dsn, array $options): TransportInterface + { + return new YourTransport(/* ... */); + } + + public function supports(string $dsn, array $options): bool + { + return 0 === strpos($dsn, 'my-transport://'); + } + } + +The transport object needs to implement the ``TransportInterface`` (which simply combines +the ``SenderInterface`` and ``ReceiverInterface``). It will look like this:: + + class YourTransport implements TransportInterface + { + public function send(Envelope $envelope): Envelope + { + // ... + } + + public function receive(callable $handler): void + { + // ... + } + + public function stop(): void + { + // ... + } + } + +Register your Factory +~~~~~~~~~~~~~~~~~~~~~ + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + Your\Transport\YourTransportFactory: + tags: [messenger.transport_factory] + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + use Your\Transport\YourTransportFactory; + + $container->register(YourTransportFactory::class) + ->setTags(['messenger.transport_factory']); + +Use your Transport +~~~~~~~~~~~~~~~~~~ + +Within the ``framework.messenger.transports.*`` configuration, create your +named transport using your own DSN: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + transports: + yours: 'my-transport://...' + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'transports' => [ + 'yours' => 'my-transport://...', + ], + ], + ]); + +In addition of being able to route your messages to the ``yours`` sender, this +will give you access to the following services: + +#. ``messenger.sender.yours``: the sender; +#. ``messenger.receiver.yours``: the receiver. + +Learn more +---------- +.. toctree:: + :maxdepth: 1 + :glob: + + /messenger/* + +.. _`enqueue's transport`: https://github.com/php-enqueue/messenger-adapter diff --git a/messenger/handler_results.rst b/messenger/handler_results.rst new file mode 100644 index 00000000000..7dede3b0f48 --- /dev/null +++ b/messenger/handler_results.rst @@ -0,0 +1,105 @@ +.. index:: + single: Messenger; Getting results / Working with command & query buses + +Getting Results from your Handler +--------------------------------- + +When a message is handled, the :class:`Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware` +adds a :class:`Symfony\\Component\\Messenger\\Stamp\\HandledStamp` for each object that handled the message. +You can use this to get the value returned by the handler(s):: + + use Symfony\Component\Messenger\MessageBusInterface; + use Symfony\Component\Messenger\Stamp\HandledStamp; + + $envelope = $messageBus->dispatch(SomeMessage()); + + // get the value that was returned by the last message handler + $handledStamp = $envelope->last(HandledStamp::class); + $handledStamp->getResult(); + + // or get info about all of handlers + $handledStamps = $envelope->all(HandledStamp::class); + +A :class:`Symfony\\Component\\Messenger\\HandleTrait` also exists in order to ease +leveraging a Messenger bus for synchronous needs. +The :method:`Symfony\\Component\\Messenger\\HandleTrait::handle` method ensures +there is exactly one handler registered and returns its result. + +Working with Command & Query Buses +---------------------------------- + +The Messenger component can be used in CQRS architectures where command & query +buses are central pieces of the application. Read Martin Fowler's +`article about CQRS`_ to learn more and +:doc:`how to configure multiple buses `. + +As queries are usually synchronous and expected to be handled once, +getting the result from the handler is a common need. + +To avoid boilerplate code, you can leverage the ``HandleTrait`` in any class +that has a ``$messageBus`` property:: + + // src/Action/ListItems.php + namespace App\Action; + + use App\Message\ListItemsQuery; + use App\MessageHandler\ListItemsQueryResult; + use Symfony\Component\Messenger\HandleTrait; + use Symfony\Component\Messenger\MessageBusInterface; + + class ListItems + { + use HandleTrait; + + public function __construct(MessageBusInterface $messageBus) + { + $this->messageBus = $messageBus; + } + + public function __invoke() + { + $result = $this->query(new ListItemsQuery(/* ... */)); + + // Do something with the result + // ... + } + + // Creating such a method is optional, but allows type-hinting the result + private function query(ListItemsQuery $query): ListItemsResult + { + return $this->handle($query); + } + } + +Hence, you can use the trait to create command & query bus classes. +For example, you could create a special ``QueryBus`` class and inject it +wherever you need a query bus behavior instead of the ``MessageBusInterface``:: + + // src/MessageBus/QueryBus.php + namespace App\MessageBus; + + use Symfony\Component\Messenger\Envelope; + use Symfony\Component\Messenger\HandleTrait; + use Symfony\Component\Messenger\MessageBusInterface; + + class QueryBus + { + use HandleTrait; + + public function __construct(MessageBusInterface $messageBus) + { + $this->messageBus = $messageBus; + } + + /** + * @param object|Envelope $query + * + * @return mixed The handler returned value + */ + public function query($query) + { + return $this->handle($query); + } + } + +.. _`article about CQRS`: https://martinfowler.com/bliki/CQRS.html diff --git a/messenger/multiple_buses.rst b/messenger/multiple_buses.rst new file mode 100644 index 00000000000..bf895efe21d --- /dev/null +++ b/messenger/multiple_buses.rst @@ -0,0 +1,291 @@ +.. index:: + single: Messenger; Multiple buses + +Multiple Buses +============== + +A common architecture when building applications is to separate commands from +queries. Commands are actions that do something and queries fetch data. This +is called CQRS (Command Query Responsibility Segregation). See Martin Fowler's +`article about CQRS`_ to learn more. This architecture could be used together +with the Messenger component by defining multiple buses. + +A **command bus** is a little different from a **query bus**. For example, command +buses usually don't provide any results and query buses are rarely asynchronous. +You can configure these buses and their rules by using middleware. + +It might also be a good idea to separate actions from reactions by introducing +an **event bus**. The event bus could have zero or more subscribers. + +.. configuration-block:: + + .. code-block:: yaml + + framework: + messenger: + # The bus that is going to be injected when injecting MessageBusInterface + default_bus: messenger.bus.commands + buses: + messenger.bus.commands: + middleware: + - validation + - doctrine_transaction + messenger.bus.queries: + middleware: + - validation + messenger.bus.events: + default_middleware: allow_no_handlers + middleware: + - validation + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + // The bus that is going to be injected when injecting MessageBusInterface + 'default_bus' => 'messenger.bus.commands', + 'buses' => [ + 'messenger.bus.commands' => [ + 'middleware' => [ + 'validation', + 'doctrine_transaction', + ], + ], + 'messenger.bus.queries' => [ + 'middleware' => [ + 'validation', + ], + ], + 'messenger.bus.events' => [ + 'default_middleware' => 'allow_no_handlers', + 'middleware' => [ + 'validation', + ], + ], + ], + ], + ]); + +This will generate the ``messenger.bus.commands``, ``messenger.bus.queries`` +and ``messenger.bus.events`` services that you can inject in your services. + +Type-hints and Auto-wiring +-------------------------- + +Auto-wiring is a great feature that allows you to reduce the amount of configuration +required for your service container to be created. By using ``MessageBusInterface`` +as argument typehint in your services, the default configured bus will be injected +(i.e ``messenger.bus.commands`` in above examples). + +When working with multiple buses, you can use the ``DependencyInjection`` component's +binding capabilities to clarify which bus will be injected based on the argument's name: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + _defaults: + # ... + + bind: + $commandBus: '@messenger.bus.commands' + $queryBus: '@messenger.bus.queries' + $eventBus: '@messenger.bus.events' + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + + $container->bind('$commandBus', 'messenger.bus.commands'); + $container->bind('$queryBus', 'messenger.bus.queries'); + $container->bind('$eventBus', 'messenger.bus.events'); + +Restrict Handlers per Bus +------------------------- + +By default, each handler will be available to handle messages on *all* +of your buses. To prevent dispatching a message to the wrong bus without an error, +you can restrict each handler to a specific bus using the ``messenger.message_handler`` tag: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\MessageHandler\SomeCommandHandler: + tags: [{ name: messenger.message_handler, bus: messenger.bus.commands }] + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + + $container->services() + ->set(App\MessageHandler\SomeCommandHandler::class) + ->tag('messenger.message_handler', ['bus' => 'messenger.bus.commands']); + +This way, the ``App\MessageHandler\SomeCommandHandler`` handler will only be +known by the ``messenger.bus.commands`` bus. + +You can also automatically add this tag to a number of classes by following +a naming convention and registering all of the handler services by name with +the correct tag: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + + # put this after the `App\` line that registers all your services + command_handlers: + namespace: App\MessageHandler\ + resource: '%kernel.project_dir%/src/MessageHandler/*CommandHandler.php' + tags: + - { name: messenger.message_handler, bus: messenger.bus.commands } + + query_handlers: + namespace: App\MessageHandler\ + resource: '%kernel.project_dir%/src/MessageHandler/*QueryHandler.php' + tags: + - { name: messenger.message_handler, bus: messenger.bus.queries } + + .. code-block:: xml + + + + + + + + + +
+ + + + +
+
+ + .. code-block:: php + + // config/services.php + + // Command handlers + $container->services() + ->load('App\MessageHandler\\', '%kernel.project_dir%/src/MessageHandler/*CommandHandler.php') + ->tag('messenger.message_handler', ['bus' => 'messenger.bus.commands']); + + // Query handlers + $container->services() + ->load('App\MessageHandler\\', '%kernel.project_dir%/src/MessageHandler/*QueryHandler.php') + ->tag('messenger.message_handler', ['bus' => 'messenger.bus.queries']); + +Debugging the Buses +------------------- + +The ``debug:messenger`` command lists available messages & handlers per bus. +You can also restrict the list to a specific bus by providing its name as argument. + +.. code-block:: terminal + + $ php bin/console debug:messenger + + Messenger + ========= + + messenger.bus.commands + ---------------------- + + The following messages can be dispatched: + + --------------------------------------------------------------------------------------- + App\Message\DummyCommand + handled by App\MessageHandler\DummyCommandHandler + App\Message\MultipleBusesMessage + handled by App\MessageHandler\MultipleBusesMessageHandler + --------------------------------------------------------------------------------------- + + messenger.bus.queries + --------------------- + + The following messages can be dispatched: + + --------------------------------------------------------------------------------------- + App\Message\DummyQuery + handled by App\MessageHandler\DummyQueryHandler + App\Message\MultipleBusesMessage + handled by App\MessageHandler\MultipleBusesMessageHandler + --------------------------------------------------------------------------------------- + +.. _article about CQRS: https://martinfowler.com/bliki/CQRS.html diff --git a/page_creation.rst b/page_creation.rst index eb61beaa2c5..3f6e6cc2f62 100644 --- a/page_creation.rst +++ b/page_creation.rst @@ -21,7 +21,7 @@ two-step process: .. admonition:: Screencast :class: screencast - Do you prefer video tutorials? Check out the `Joyful Development with Symfony`_ + Do you prefer video tutorials? Check out the `Stellar Development with Symfony`_ screencast series. .. seealso:: @@ -42,22 +42,17 @@ Creating a Page: Route and Controller Suppose you want to create a page - ``/lucky/number`` - that generates a lucky (well, random) number and prints it. To do that, create a "Controller class" and a -"controller" method inside of it that will be executed when someone goes to -``/lucky/number``:: +"controller" method inside of it:: ` - in its own section, including how to make *variable* URLs; +#. *Create a route*: In ``config/routes.yaml``, the route defines the URL to your + page (``path``) and what ``controller`` to call. You'll learn more about :doc:`routing ` + in its own section, including how to make *variable* URLs; -#. *Create a controller*: The method below the route - ``numberAction()`` - is called - the *controller*. This is a function where *you* build the page and ultimately +#. *Create a controller*: This is a function where *you* build the page and ultimately return a ``Response`` object. You'll learn more about :doc:`controllers ` in their own section, including how to return JSON responses. +.. _annotation-routes: + +Annotation Routes +----------------- + +Instead of defining your route in YAML, Symfony also allows you to use *annotation* +routes. To do this, install the annotations package: + +.. code-block:: terminal + + $ composer require annotations + +You can now add your route directly *above* the controller: + +.. code-block:: diff + + // src/Controller/LuckyController.php + + // ... + + use Symfony\Component\Routing\Annotation\Route; + + class LuckyController + { + + /** + + * @Route("/lucky/number") + + */ + public function number() + { + // this looks exactly the same + } + } + +That's it! The page - ``http://localhost:8000/lucky/number`` will work exactly +like before! Annotations are the recommended way to configure routes. + +.. _flex-quick-intro: + +Auto-Installing Recipes with Symfony Flex +----------------------------------------- + +You may not have noticed, but when you ran ``composer require annotations``, two +special things happened, both thanks to a powerful Composer plugin called +:doc:`Flex `. + +First, ``annotations`` isn't a real package name: it's an *alias* (i.e. shortcut) +that Flex resolves to ``sensio/framework-extra-bundle``. + +Second, after this package was downloaded, Flex executed a *recipe*, which is a +set of automated instructions that tell Symfony how to integrate an external +package. `Flex recipes`_ exist for many packages and have the ability +to do a lot, like adding configuration files, creating directories, updating ``.gitignore`` +and adding new config to your ``.env`` file. Flex *automates* the installation of +packages so you can get back to coding. + +You can learn more about Flex by reading ":doc:`/setup/flex`". But that's not necessary: +Flex works automatically in the background when you add packages. + +The bin/console Command +----------------------- + +Your project already has a powerful debugging tool inside: the ``bin/console`` command. +Try running it: + +.. code-block:: terminal + + $ php bin/console + +You should see a list of commands that can give you debugging information, help generate +code, generate database migrations and a lot more. As you install more packages, +you'll see more commands. + +To get a list of *all* of the routes in your system, use the ``debug:router`` command: + +.. code-block:: terminal + + $ php bin/console debug:router + +You should see your ``app_lucky_number`` route at the very top: + +================== ======== ======== ====== =============== + Name Method Scheme Host Path +================== ======== ======== ====== =============== + app_lucky_number ANY ANY ANY /lucky/number +================== ======== ======== ====== =============== + +You will also see debugging routes below ``app_lucky_number`` -- more on +the debugging routes in the next section. + +You'll learn about many more commands as you continue! + The Web Debug Toolbar: Debugging Dream -------------------------------------- -If your page is working, then you should *also* see a bar along the bottom of your -browser. This is called the Web Debug Toolbar: and it's your debugging best friend. -You'll learn more about all the information it holds along the way, but feel free -to experiment: hover over and click the different icons to get information about -routing, performance, logging and more. +One of Symfony's *killer* features is the Web Debug Toolbar: a bar that displays +a *huge* amount of debugging information along the bottom of your page while developing. This is all +included out of the box using a package called ``symfony/profiler-pack``. -Rendering a Template (with the Service Container) -------------------------------------------------- +You will see a black bar along the bottom of the page. You'll learn more about all the information it holds +along the way, but feel free to experiment: hover over and click +the different icons to get information about routing, performance, logging and more. + +Rendering a Template +-------------------- If you're returning HTML from your controller, you'll probably want to render a template. Fortunately, Symfony comes with `Twig`_: a templating language that's easy, powerful and actually quite fun. -First, import the base :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` -class as shown on line 5 below. Then, let your ``LuckyController`` class -extend the base class:: +Make sure that ``LuckyController`` extends Symfony's base +:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController` class: + +.. code-block:: diff - // src/AppBundle/Controller/LuckyController.php + // src/Controller/LuckyController.php // ... - // --> add this new use statement - use Symfony\Bundle\FrameworkBundle\Controller\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - class LuckyController extends Controller + - class LuckyController + + class LuckyController extends AbstractController { // ... } -Now, use the handy ``render()`` function to render a template. Pass it our ``number`` -variable so we can render that:: +Now, use the handy ``render()`` function to render a template. Pass it a ``number`` +variable so you can use it in Twig:: - // src/AppBundle/Controller/LuckyController.php + // src/Controller/LuckyController.php // ... - class LuckyController extends Controller + class LuckyController extends AbstractController { /** * @Route("/lucky/number") */ - public function numberAction() + public function number() { $number = random_int(0, 100); @@ -137,13 +236,13 @@ variable so we can render that:: } } -Finally, template files should live in the ``app/Resources/views`` directory. Create -a new ``app/Resources/views/lucky`` directory with a new ``number.html.twig`` file -inside: +Template files live in the ``templates/`` directory, which was created for you automatically +when you installed Twig. Create a new ``templates/lucky`` directory with a new +``number.html.twig`` file inside: .. code-block:: html+twig - {# app/Resources/views/lucky/number.html.twig #} + {# templates/lucky/number.html.twig #}

Your lucky number is {{ number }}

@@ -152,24 +251,31 @@ to get your *new* lucky number! http://localhost:8000/lucky/number +Now you may wonder where the Web Debug Toolbar has gone: that's because there is +no ```` tag in the current template. You can add the body element yourself, +or extend ``base.html.twig``, which contains all default HTML elements. + In the :doc:`/templating` article, you'll learn all about Twig: how to loop, render other templates and leverage its powerful layout inheritance system. Checking out the Project Structure ---------------------------------- -Great news! You've already worked inside the two most important directories in your +Great news! You've already worked inside the most important directories in your project: -``app/`` - Contains things like configuration and templates. Basically, anything - that is *not* PHP code goes here. +``config/`` + Contains... configuration of course!. You will configure routes, :doc:`services ` + and packages. ``src/`` - Your PHP code lives here. + All your PHP code lives here. -99% of the time, you'll be working in ``src/`` (PHP files) or ``app/`` (everything -else). As you keep reading, you'll learn what can be done inside each of these. +``templates/`` + All your Twig templates live here. + +Most of the time, you'll be working in ``src/``, ``templates/`` or ``config/``. +As you keep reading, you'll learn what can be done inside each of these. So what about the other directories in the project? @@ -177,58 +283,20 @@ So what about the other directories in the project? The famous ``bin/console`` file lives here (and other, less important executable files). -``tests/`` - The automated tests (e.g. Unit tests) for your application live here. - ``var/`` This is where automatically-created files are stored, like cache files - (``var/cache/``), logs (``var/logs/``) and sessions (``var/sessions/``). + (``var/cache/``) and logs (``var/log/``). ``vendor/`` Third-party (i.e. "vendor") libraries live here! These are downloaded via the `Composer`_ package manager. -``web/`` - This is the document root for your project: put any publicly accessible files - here (e.g. CSS, JS and images). - -Bundles & Configuration ------------------------ - -Your Symfony application comes pre-installed with a collection of *bundles*, like -``FrameworkBundle`` and ``TwigBundle``. Bundles are similar to the idea of a *plugin*, -but with one important difference: *all* functionality in a Symfony application comes -from a bundle. - -Bundles are registered in your ``app/AppKernel.php`` file (a rare PHP file in the -``app/`` directory) and each gives you more *tools*, sometimes called *services*:: - - class AppKernel extends Kernel - { - public function registerBundles() - { - $bundles = [ - new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new Symfony\Bundle\TwigBundle\TwigBundle(), - // ... - ]; - // ... - - return $bundles; - } - - // ... - } - -For example, ``TwigBundle`` is responsible for adding the Twig tool to your app! - -Eventually, you'll download and add more third-party bundles to your app in order -to get even more tools. Imagine a bundle that helps you create paginated lists. -That exists! +``public/`` + This is the document root for your project: you put any publicly accessible files + here. -You can control how your bundles behave via the ``app/config/config.yml`` file. -That file - and other details like environments & parameters - are discussed in -the :doc:`/configuration` article. +And when you install new packages, new directories will be created automatically +when needed. What's Next? ------------ @@ -266,4 +334,5 @@ Go Deeper with HTTP & Framework Fundamentals .. _`Twig`: https://twig.symfony.com .. _`Composer`: https://getcomposer.org -.. _`Joyful Development with Symfony`: https://symfonycasts.com/screencast/symfony3 +.. _`Stellar Development with Symfony`: https://symfonycasts.com/screencast/symfony/setup +.. _`Flex recipes`: https://flex.symfony.com diff --git a/performance.rst b/performance.rst index 9d11b542866..c1aac5f2094 100644 --- a/performance.rst +++ b/performance.rst @@ -12,8 +12,6 @@ Symfony Application Checklist ----------------------------- #. :ref:`Install APCu Polyfill if your server uses APC ` -#. :ref:`Enable APC Caching for the Autoloader ` -#. :ref:`Use Bootstrap Files ` Production Server Checklist --------------------------- @@ -34,80 +32,6 @@ OPcache, install the `APCu Polyfill component`_ in your application to enable compatibility with `APCu PHP functions`_ and unlock support for advanced Symfony features, such as the APCu Cache adapter. -.. _performance-autoloader-apc-cache: - -Enable APC Caching for the Autoloader -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The class autoloading mechanism is one of the slowest parts in PHP applications -that make use of lots of classes, such as Symfony. A simple way to improve its -performance is to use the :class:`Symfony\\Component\\ClassLoader\\ApcClassLoader`, -which caches the location of each class after it's located the first time. - -To use it, adapt your front controller file like this:: - - // app.php - // ... - - $loader = require_once __DIR__.'/../app/bootstrap.php.cache'; - - // Change 'sf' by something unique to this app to prevent - // conflicts with other applications running in the same server - $loader = new ApcClassLoader('sf', $loader); - $loader->register(true); - - // ... - -For more details, see :doc:`/components/class_loader/cache_class_loader`. - -.. note:: - - When using the APC autoloader, if you add new classes, they will be found - automatically and everything will work the same as before (i.e. no - reason to "clear" the cache). However, if you change the location of a - particular namespace or prefix, you'll need to flush your APC cache. Otherwise, - the autoloader will still be looking at the old location for all classes - inside that namespace. - -.. _performance-use-bootstrap-files: - -Use Bootstrap Files -~~~~~~~~~~~~~~~~~~~ - -.. caution:: - - Thanks to the optimizations introduced in PHP 7, bootstrap files are no - longer necessary when running your Symfony applications with PHP 7 or a - newer PHP version. - -The Symfony Standard Edition includes a script to generate a so-called -`bootstrap file`_, which is a large file containing the code of the most -commonly used classes. This saves a lot of IO operations because Symfony no -longer needs to look for and read those files. - -If you're using the Symfony Standard Edition, then you're probably already -using the bootstrap file. To be sure, open your front controller (usually -``app.php``) and check to make sure that the following line exists:: - - require_once __DIR__.'/../app/bootstrap.php.cache'; - -Note that there are two disadvantages when using a bootstrap file: - -* the file needs to be regenerated whenever any of the original sources change - (i.e. when you update the Symfony source or vendor libraries); - -* when debugging, one will need to place break points inside the bootstrap file. - -If you're using the Symfony Standard Edition, the bootstrap file is automatically -rebuilt after updating the vendor libraries via the ``composer install`` command. - -.. note:: - - Even when using a byte code cache, performance will improve when using a - bootstrap file since there will be fewer files to monitor for changes. Of - course, if this feature is disabled in the byte code cache (e.g. - ``apc.stat=0`` in APC), there is no longer a reason to use a bootstrap file. - .. _performance-use-opcache: Use the OPcache Byte Code Cache @@ -214,7 +138,6 @@ Learn more ---------- * :doc:`/http_cache/varnish` -* :doc:`/http_cache/form_csrf_caching` .. _`byte code caches`: https://en.wikipedia.org/wiki/List_of_PHP_accelerators .. _`OPcache`: https://php.net/manual/en/book.opcache.php diff --git a/profiler.rst b/profiler.rst index 496b86fbf62..4521b21df08 100644 --- a/profiler.rst +++ b/profiler.rst @@ -1,8 +1,221 @@ Profiler ======== +The profiler is a powerful **development tool** that gives detailed information +about the execution of any request. **Never** enable the profiler in production +environments as it will lead to major security vulnerabilities in your project. + +Installation +------------ + +In applications using :doc:`Symfony Flex `, run this command to +install the profiler before using it: + +.. code-block:: terminal + + $ composer require --dev symfony/profiler-pack + +Now browse any page of your application in the development environment to let +the profiler collect information. Then, click on any element of the debug +toolbar injected at the bottom of your pages to open the web interface of the +Symfony Profiler, which will look like this: + +.. image:: /_images/profiler/web-interface.png + :align: center + :class: with-browser + +Accessing Profiling Data Programmatically +----------------------------------------- + +Most of the times, the profiler information is accessed and analyzed using its +web-based interface. However, you can also retrieve profiling information +programmatically thanks to the methods provided by the ``profiler`` service. + +When the response object is available, use the +:method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::loadProfileFromResponse` +method to access to its associated profile:: + + // ... $profiler is the 'profiler' service + $profile = $profiler->loadProfileFromResponse($response); + +When the profiler stores data about a request, it also associates a token with it; +this token is available in the ``X-Debug-Token`` HTTP header of the response. +Using this token, you can access the profile of any past response thanks to the +:method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::loadProfile` method:: + + $token = $response->headers->get('X-Debug-Token'); + $profile = $profiler->loadProfile($token); + +.. tip:: + + When the profiler is enabled but not the web debug toolbar, inspect the page + with your browser's developer tools to get the value of the ``X-Debug-Token`` + HTTP header. + +The ``profiler`` service also provides the +:method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::find` method to +look for tokens based on some criteria:: + + // gets the latest 10 tokens + $tokens = $profiler->find('', '', 10, '', '', ''); + + // gets the latest 10 tokens for all URL containing /admin/ + $tokens = $profiler->find('', '/admin/', 10, '', '', ''); + + // gets the latest 10 tokens for local POST requests + $tokens = $profiler->find('127.0.0.1', '', 10, 'POST', '', ''); + + // gets the latest 10 tokens for requests that happened between 2 and 4 days ago + $tokens = $profiler->find('', '', 10, '', '4 days ago', '2 days ago'); + +Data Collectors +--------------- + +The profiler gets its information using some services called "data collectors". +Symfony comes with several collectors that get information about the request, +the logger, the routing, the cache, etc. + +Run this command to get the list of collectors actually enabled in your app: + +.. code-block:: terminal + + $ php bin/console debug:container --tag=data_collector + +You can also :doc:`create your own data collector ` to +store any data generated by your app and display it in the debug toolbar and the +profiler web interface. + +.. _profiler-timing-execution: + +Timing the Execution of the Application +--------------------------------------- + +If you want to measure the time some tasks take in your application, there's no +need to create a custom data collector. Instead, use the `Stopwatch component`_ +which provides utilities to profile code and displays the results on the +"Performance" panel of the Profiler web interface. + +When using :ref:`autowiring `, type-hint any argument with +the :class:`Symfony\\Component\\Stopwatch\\Stopwatch` class and Symfony will +inject the Stopwatch service. Then, use the ``start()``, ``lapse()`` and +``stop()`` methods to measure time:: + + // a user signs up and the timer starts... + $stopwatch->start('user-sign-up'); + + // ...do things to sign up the user... + $stopwatch->lapse('user-sign-up'); + + // ...the sign up process is finished + $stopwatch->stop('user-sign-up'); + +.. tip:: + + Consider using a professional profiler such as `Blackfire`_ to measure and + analyze the execution of your application in detail. + +Enabling the Profiler Conditionally +----------------------------------- + +.. caution:: + + The possibility to use a matcher to enable the profiler conditionally was + removed in Symfony 4.0. + +Symfony Profiler cannot be enabled/disabled conditionally using matchers, because +that feature was removed in Symfony 4.0. However, you can use the ``enable()`` +and ``disable()`` methods of the :class:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler` +class in your controllers to manage the profiler programmatically:: + + use Symfony\Component\HttpKernel\Profiler\Profiler; + // ... + + class DefaultController + { + // ... + + public function someMethod(?Profiler $profiler) + { + // $profiler won't be set if your environment doesn't have the profiler (like prod, by default) + if (null !== $profiler) { + // if it exists, disable the profiler for this particular controller action + $profiler->disable(); + } + + // ... + } + } + +In order for the profiler to be injected into your controller you need to +create an alias pointing to the existing ``profiler`` service: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services_dev.yaml + services: + Symfony\Component\HttpKernel\Profiler\Profiler: '@profiler' + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/services_dev.php + use Symfony\Component\HttpKernel\Profiler\Profiler; + + $container->setAlias(Profiler::class, 'profiler'); + +Updating the Web Debug Toolbar After AJAX Requests +-------------------------------------------------- + +`Single-page applications`_ (SPA) are web applications that interact with the +user by dynamically rewriting the current page rather than loading entire new +pages from a server. + +By default, the debug toolbar displays the information of the initial page load +and doesn't refresh after each AJAX request. However, you can set the +``Symfony-Debug-Toolbar-Replace`` header to a value of ``1`` in the response to +the AJAX request to force the refresh of the toolbar:: + + $response->headers->set('Symfony-Debug-Toolbar-Replace', 1); + +Ideally this header should only be set during development and not for +production. To do that, create an :doc:`event subscriber ` +and listen to the :ref:`kernel.response` +event:: + + use Symfony\Component\HttpKernel\Event\FilterResponseEvent; + + // ... + + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$this->getKernel()->isDebug()) { + return; + } + + $response = $event->getResponse(); + $response->headers->set('Symfony-Debug-Toolbar-Replace', 1); + } + .. toctree:: - :maxdepth: 1 - :glob: + :hidden: + + profiler/data_collector - profiler/* +.. _`Single-page applications`: https://en.wikipedia.org/wiki/Single-page_application +.. _`Stopwatch component`: https://symfony.com/components/Stopwatch +.. _`Blackfire`: https://blackfire.io/ diff --git a/profiler/data_collector.rst b/profiler/data_collector.rst index 8ec9f4822b1..f3d00ad7c19 100644 --- a/profiler/data_collector.rst +++ b/profiler/data_collector.rst @@ -21,8 +21,8 @@ property to store the collected information. The following example shows a custom collector that stores information about the request:: - // src/AppBundle/DataCollector/RequestCollector.php - namespace AppBundle\DataCollector; + // src/DataCollector/RequestCollector.php + namespace App\DataCollector; use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpFoundation\Request; @@ -84,7 +84,7 @@ request:: Enabling Custom Data Collectors ------------------------------- -If you're using the :ref:`default services.yml configuration ` +If you're using the :ref:`default services.yaml configuration ` with ``autoconfigure``, then Symfony will automatically see your new data collector! Your ``collect()`` method should be called next time your refresh. @@ -101,8 +101,8 @@ template that includes some specific blocks. However, first you must add some getters in the data collector class to give the template access to the collected information:: - // src/AppBundle/DataCollector/RequestCollector.php - namespace AppBundle\DataCollector; + // src/DataCollector/RequestCollector.php + namespace App\DataCollector; use Symfony\Component\HttpKernel\DataCollector\DataCollector; @@ -132,8 +132,8 @@ block and set the value of two variables called ``icon`` and ``text``: {% block toolbar %} {% set icon %} {# this is the content displayed as a panel in the toolbar #} - - Request + ... + Request {% endset %} {% set text %} @@ -157,23 +157,15 @@ block and set the value of two variables called ``icon`` and ``text``: .. tip:: - Built-in collector templates define all their images as embedded base64-encoded - images. This makes them work everywhere without having to mess with web assets - links: - - .. code-block:: html - - - - Another solution is to define the images as SVG files. In addition to being - resolution-independent, these images can be embedded in the Twig template - or included from an external file to reuse them in several templates: + Built-in collector templates define all their images as embedded SVG files. + This makes them work everywhere without having to mess with web assets links: .. code-block:: twig - {{ include('data_collector/icon.svg') }} - - You are encouraged to use the latter technique for your own toolbar panels. + {% set icon %} + {{ include('data_collector/icon.svg') }} + {# ... #} + {% endset %} If the toolbar panel includes extended web profiler information, the Twig template must also define additional blocks: @@ -184,8 +176,7 @@ must also define additional blocks: {% block toolbar %} {% set icon %} - - Request + {# ... #} {% endset %} {% set text %} @@ -237,9 +228,9 @@ to specify a tag that contains the template: .. code-block:: yaml - # app/config/services.yml + # config/services.yaml services: - AppBundle\DataCollector\RequestCollector: + App\DataCollector\RequestCollector: tags: - name: data_collector @@ -252,7 +243,7 @@ to specify a tag that contains the template: .. code-block:: xml - + - + autowire(RequestCollector::class) @@ -285,7 +276,7 @@ to specify a tag that contains the template: ]) ; -The position of each panel in the toolbar is determined by the priority defined -by each collector. Priorities are defined as positive or negative integers and -they default to ``0``. Most built-in collectors use ``255`` as their priority. -If you want your collector to be displayed before them, use a higher value (like 300). +The position of each panel in the toolbar is determined by the collector priority. +Priorities are defined as positive or negative integers and they default to ``0``. +Most built-in collectors use ``255`` as their priority. If you want your collector +to be displayed before them, use a higher value (like 300). diff --git a/profiler/matchers.rst b/profiler/matchers.rst deleted file mode 100644 index 790f905002d..00000000000 --- a/profiler/matchers.rst +++ /dev/null @@ -1,163 +0,0 @@ -.. index:: - single: Profiling; Matchers - -How to Use Matchers to Enable the Profiler Conditionally -======================================================== - -.. caution:: - - The possibility to use a matcher to enable the profiler conditionally is - deprecated since Symfony 3.4 and will be removed in 4.0. - -The Symfony profiler is only activated in the development environment to not hurt -your application performance. However, sometimes it may be useful to conditionally -enable the profiler in the production environment to assist you in debugging -issues. This behavior is implemented with the **Request Matchers**. - -Using the built-in Matcher --------------------------- - -A request matcher is a class that checks whether a given ``Request`` instance -matches a set of conditions. Symfony provides a -:class:`built-in matcher ` -which matches paths and IPs. For example, if you want to only show the profiler -when accessing the page with the ``168.0.0.1`` IP, then you can use this -configuration: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - profiler: - matcher: - ip: 168.0.0.1 - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', [ - // ... - 'profiler' => [ - 'matcher' => [ - 'ip' => '168.0.0.1', - ] - ], - ]); - -You can also set a ``path`` option to define the path on which the profiler -should be enabled. For instance, setting it to ``^/admin/`` will enable the -profiler only for the URLs which start with ``/admin/``. - -Creating a Custom Matcher -------------------------- - -Leveraging the concept of Request Matchers you can define a custom matcher to -enable the profiler conditionally in your application. To do so, create a class -which implements -:class:`Symfony\\Component\\HttpFoundation\\RequestMatcherInterface`. This -interface requires one method: -:method:`Symfony\\Component\\HttpFoundation\\RequestMatcherInterface::matches`. -This method returns ``false`` when the request doesn't match the conditions and -``true`` otherwise. Therefore, the custom matcher must return ``false`` to -disable the profiler and ``true`` to enable it. - -Suppose that the profiler must be enabled whenever a user with a -``ROLE_SUPER_ADMIN`` is logged in. This is the only code needed for that custom -matcher:: - - // src/AppBundle/Profiler/SuperAdminMatcher.php - namespace AppBundle\Profiler; - - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpFoundation\RequestMatcherInterface; - use Symfony\Component\Security\Core\Security; - - class SuperAdminMatcher implements RequestMatcherInterface - { - protected $security; - - public function __construct(Security $security) - { - $this->security = $security; - } - - public function matches(Request $request) - { - return $this->security->isGranted('ROLE_SUPER_ADMIN'); - } - } - -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: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - profiler: - matcher: - service: AppBundle\Profiler\SuperAdminMatcher - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - use AppBundle\Profiler\SuperAdminMatcher; - - $container->loadFromExtension('framework', [ - // ... - 'profiler' => [ - 'matcher' => [ - 'service' => SuperAdminMatcher::class, - ] - ], - ]); diff --git a/profiler/profiling_data.rst b/profiler/profiling_data.rst deleted file mode 100644 index bc59bc9580d..00000000000 --- a/profiler/profiling_data.rst +++ /dev/null @@ -1,47 +0,0 @@ -.. index:: - single: Profiling; Profiling data - -How to Access Profiling Data Programmatically -============================================= - -Most of the times, the profiler information is accessed and analyzed using its -web-based visualizer. However, you can also retrieve profiling information -programmatically thanks to the methods provided by the ``profiler`` service. - -When the response object is available, use the -:method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::loadProfileFromResponse` -method to access to its associated profile:: - - // ... $profiler is the 'profiler' service - $profile = $profiler->loadProfileFromResponse($response); - -When the profiler stores data about a request, it also associates a token with it; -this token is available in the ``X-Debug-Token`` HTTP header of the response. -Using this token, you can access the profile of any past response thanks to the -:method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::loadProfile` method:: - - $token = $response->headers->get('X-Debug-Token'); - $profile = $container->get('profiler')->loadProfile($token); - -.. tip:: - - When the profiler is enabled but not the web debug toolbar, inspect the page - with your browser's developer tools to get the value of the ``X-Debug-Token`` - HTTP header. - -The ``profiler`` service also provides the -:method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::find` method to -look for tokens based on some criteria:: - - // gets the latest 10 tokens - $tokens = $container->get('profiler')->find('', '', 10, '', '', ''); - - // gets the latest 10 tokens for all URL containing /admin/ - $tokens = $container->get('profiler')->find('', '/admin/', 10, '', '', ''); - - // gets the latest 10 tokens for local POST requests - $tokens = $container->get('profiler')->find('127.0.0.1', '', 10, 'POST', '', ''); - - // gets the latest 10 tokens for requests that happened between 2 and 4 days ago - $tokens = $container->get('profiler') - ->find('', '', 10, '', '4 days ago', '2 days ago'); diff --git a/profiler/storage.rst b/profiler/storage.rst deleted file mode 100644 index a4763c13605..00000000000 --- a/profiler/storage.rst +++ /dev/null @@ -1,54 +0,0 @@ -.. index:: - single: Profiling; Storage Configuration - -Switching the Profiler Storage -============================== - -In Symfony versions prior to 3.0, profiles could be stored in files, databases, -services like Redis and Memcache, etc. Starting from Symfony 3.0, the only storage -mechanism with built-in support is the filesystem. - -By default the profile stores the collected data in the ``%kernel.cache_dir%/profiler/`` -directory. If you want to use another location to store the profiles, define the -``dsn`` option of the ``framework.profiler``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - profiler: - dsn: 'file:/tmp/symfony/profiler' - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - - // ... - $container->loadFromExtension('framework', [ - 'profiler' => [ - 'dsn' => 'file:/tmp/symfony/profiler', - ], - ]); - -You can also create your own profile storage service implementing the -:class:`Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface` and -overriding the ``profiler.storage`` service. diff --git a/quick_tour/flex_recipes.rst b/quick_tour/flex_recipes.rst new file mode 100644 index 00000000000..247cd57fe68 --- /dev/null +++ b/quick_tour/flex_recipes.rst @@ -0,0 +1,264 @@ +Flex: Compose your Application +============================== + +After reading the first part of this tutorial, you have decided that Symfony was +worth another 10 minutes. Great choice! In this second part, you'll learn about +Symfony Flex: the amazing tool that makes adding new features as simple as running +one command. It's also the reason why Symfony is ideal for a small micro-service +or a huge application. Curious? Perfect! + +Symfony: Start Micro! +--------------------- + +Unless you're building a pure API (more on that soon!), you'll probably want to +render HTML. To do that, you'll use `Twig`_. Twig is a flexible, fast, and secure +template engine for PHP. It makes your templates more readable and concise; it also +makes them more friendly for web designers. + +Is Twig already installed in our application? Actually, not yet! And that's great! +When you start a new Symfony project, it's *small*: only the most critical dependencies +are included in your ``composer.json`` file: + +.. code-block:: text + + "require": { + "...", + "symfony/console": "^4.1", + "symfony/flex": "^1.0", + "symfony/framework-bundle": "^4.1", + "symfony/yaml": "^4.1" + } + +This makes Symfony different than any other PHP framework! Instead of starting with +a *bulky* app with *every* possible feature you might ever need, a Symfony app is +small, simple and *fast*. And you're in total control of what you add. + +Flex Recipes and Aliases +------------------------ + +So how can we install and configure Twig? By running one single command: + +.. code-block:: terminal + + $ composer require twig + +Two *very* interesting things happen behind the scenes thanks to Symfony Flex: a +Composer plugin that is already installed in our project. + +First, ``twig`` is not the name of a Composer package: it's a Flex *alias* that +points to ``symfony/twig-bundle``. Flex resolves that alias for Composer. + +And second, Flex installs a *recipe* for ``symfony/twig-bundle``. What's a recipe? +It's a way for a library to automatically configure itself by adding and modifying +files. Thanks to recipes, adding features is seamless and automated: install a package +and you're done! + +You can find a full list of recipes and aliases by going to `https://flex.symfony.com`_. + +What did this recipe do? In addition to automatically enabling the feature in +``config/bundles.php``, it added 3 things: + +``config/packages/twig.yaml`` + A configuration file that sets up Twig with sensible defaults. + +``config/routes/dev/twig.yaml`` + A route that helps you debug your error pages. + +``templates/`` + This is the directory where template files will live. The recipe also added + a ``base.html.twig`` layout file. + +Twig: Rendering a Template +-------------------------- + +Thanks to Flex, after one command, you can start using Twig immediately: + +.. code-block:: diff + + // src/Controller/DefaultController.php + namespace App\Controller; + + use Symfony\Component\Routing\Annotation\Route; + - use Symfony\Component\HttpFoundation\Response; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + + -class DefaultController + +class DefaultController extends AbstractController + { + /** + * @Route("/hello/{name}") + */ + public function index($name) + { + - return new Response("Hello $name!"); + + return $this->render('default/index.html.twig', [ + + 'name' => $name, + + ]); + } + } + +By extending ``AbstractController``, you now have access to a number of shortcut +methods and tools, like ``render()``. Create the new template: + +.. code-block:: twig + + {# templates/default/index.html.twig #} +

Hello {{ name }}

+ +That's it! The ``{{ name }}`` syntax will print the ``name`` variable that's passed +in from the controller. If you're new to Twig, welcome! You'll learn more about +its syntax and power later. + +But, right now, the page *only* contains the ``h1`` tag. To give it an HTML layout, +extend ``base.html.twig``: + +.. code-block:: twig + + {# templates/default/index.html.twig #} + {% extends 'base.html.twig' %} + + {% block body %} +

Hello {{ name }}

+ {% endblock %} + +This is called template inheritance: our page now inherits the HTML structure from +``base.html.twig``. + +Profiler: Debugging Paradise +---------------------------- + +One of the *coolest* features of Symfony isn't even installed yet! Let's fix that: + +.. code-block:: terminal + + $ composer require profiler + +Yes! This is another alias! And Flex *also* installs another recipe, which automates +the configuration of Symfony's Profiler. What's the result? Refresh! + +See that black bar on the bottom? That's the web debug toolbar, and it's your new +best friend. By hovering over each icon, you can get information about what controller +was executed, performance information, cache hits & misses and a lot more. Click +any icon to go into the *profiler* where you have even *more* detailed debugging +and performance data! + +Oh, and as you install more libraries, you'll get more tools (like a web debug toolbar +icon that shows database queries). + +You can now directly use the profiler because it configured *itself* thanks to the recipe. +What else can we install this easily? + +Rich API Support +---------------- + +Are you building an API? You can already return JSON easily from any controller:: + + // src/Controller/DefaultController.php + namespace App\Controller; + + use Symfony\Component\Routing\Annotation\Route; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + + class DefaultController extends AbstractController + { + // ... + + /** + * @Route("/api/hello/{name}") + */ + public function apiExample($name) + { + return $this->json([ + 'name' => $name, + 'symfony' => 'rocks', + ]); + } + } + +But for a *truly* rich API, try installing `API Platform`_: + +.. code-block:: terminal + + $ composer require api + +This is an alias to ``api-platform/api-pack``, which has dependencies on several +other packages, like Symfony's Validator and Security components, as well as the Doctrine +ORM. In fact, Flex installed *5* recipes! + +But like usual, we can immediately start using the new library. Want to create a +rich API for a ``product`` table? Create a ``Product`` entity and give it the +``@ApiResource()`` annotation:: + + // src/Entity/Product.php + namespace App\Entity; + + use ApiPlatform\Core\Annotation\ApiResource; + use Doctrine\ORM\Mapping as ORM; + + /** + * @ORM\Entity() + * @ApiResource() + */ + class Product + { + /** + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="string") + */ + private $name; + + /** + * @ORM\Column(type="int") + */ + private $price; + + // ... + } + +Done! You now have endpoints to list, add, update and delete products! Don't believe +me? List your routes by running: + +.. code-block:: terminal + + $ php bin/console debug:router + + ------------------------------ -------- ------------------------------------- + Name Method Path + ------------------------------ -------- ------------------------------------- + api_products_get_collection GET /api/products.{_format} + api_products_post_collection POST /api/products.{_format} + api_products_get_item GET /api/products/{id}.{_format} + api_products_put_item PUT /api/products/{id}.{_format} + api_products_delete_item DELETE /api/products/{id}.{_format} + ... + ------------------------------ -------- ------------------------------------- + +Easily Remove Recipes +--------------------- + +Not convinced yet? No problem: remove the library: + +.. code-block:: terminal + + $ composer remove api + +Flex will *uninstall* the recipes: removing files and un-doing changes to put your +app back in its original state. Experiment without worry. + +More Features, Architecture and Speed +------------------------------------- + +I hope you're as excited about Flex as I am! But we still have *one* more chapter, +and it's the most important yet. I want to show you how Symfony empowers you to quickly +build features *without* sacrificing code quality or performance. It's all about +the service container, and it's Symfony's super power. Read on: about :doc:`/quick_tour/the_architecture`. + +.. _`https://flex.symfony.com`: https://flex.symfony.com +.. _`API Platform`: https://api-platform.com/ +.. _`Twig`: https://twig.symfony.com/ diff --git a/quick_tour/index.rst b/quick_tour/index.rst index 47972e911b3..6239a463be0 100644 --- a/quick_tour/index.rst +++ b/quick_tour/index.rst @@ -5,6 +5,5 @@ The Quick Tour :maxdepth: 1 the_big_picture - the_view - the_controller + flex_recipes the_architecture diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index 1c0b6325663..8c1f7003cf0 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -1,318 +1,345 @@ The Architecture ================ -You are my hero! Who would have thought that you would still be here after -the first three parts? Your efforts will be well rewarded soon. The first -three parts didn't look too deeply at the architecture of the framework. -Because it makes Symfony stand apart from the framework crowd, let's dive -into the architecture now. - -Understanding the Directory Structure -------------------------------------- - -The directory structure of a Symfony application is rather flexible, but the -recommended structure is as follows: - -``app/`` - The application configuration, templates and translations. -``bin/`` - Executable files (e.g. ``bin/console``). -``src/`` - The project's PHP code. -``tests/`` - Automatic tests (e.g. Unit tests). -``var/`` - Generated files (cache, logs, etc.). -``vendor/`` - The third-party dependencies. -``web/`` - The web root directory. - -The ``web/`` Directory -~~~~~~~~~~~~~~~~~~~~~~ - -The web root directory is the home of all public and static files like images, -stylesheets and JavaScript files. It is also where each front controller (the -file that handles all requests to your application) lives, such as the -production controller shown here:: - - // web/app.php - require_once __DIR__.'/../var/bootstrap.php.cache'; - require_once __DIR__.'/../app/AppKernel.php'; - - use Symfony\Component\HttpFoundation\Request; - - $kernel = new AppKernel('prod', false); - $request = Request::createFromGlobals(); - $response = $kernel->handle($request); - $response->send(); - -The controller first bootstraps the application using a kernel class (``AppKernel`` -in this case). Then, it creates the ``Request`` object using the PHP's global -variables and passes it to the kernel. The last step is to send the response -contents returned by the kernel back to the user. - -.. _the-app-dir: - -The ``app/`` Directory -~~~~~~~~~~~~~~~~~~~~~~ - -The ``AppKernel`` class is the main entry point of the application -configuration and as such, it is stored in the ``app/`` directory. - -This class must implement two methods: - -``registerBundles()`` - Must return an array of all bundles needed to run the application, as - explained in the next section. -``registerContainerConfiguration()`` - Loads the application configuration (more on this later). - -Autoloading is handled automatically via `Composer`_, which means that you -can use any PHP class without doing anything at all! All dependencies -are stored under the ``vendor/`` directory, but this is just a convention. -You can store them wherever you want, globally on your server or locally -in your projects. - -Understanding the Bundle System -------------------------------- - -This section introduces one of the greatest and most powerful features of -Symfony: The bundle system. - -A bundle is kind of like a plugin in other software. So why is it -called a *bundle* and not a *plugin*? This is because *everything* is a -bundle in Symfony, from the core framework features to the code you write -for your application. - -All the code you write for your application is organized in bundles. In -Symfony speak, a bundle is a structured set of files (PHP files, stylesheets, -JavaScripts, images, ...) that implements a single feature (a blog, a forum, -...) and which can be easily shared with other developers. - -Bundles are first-class citizens in Symfony. This gives you the flexibility -to use pre-built features packaged in third-party bundles or to distribute -your own bundles. It makes it easy to pick and choose which features to -enable in your application and optimize them the way you want. And at the -end of the day, your application code is just as *important* as the core -framework itself. - -Symfony already includes an AppBundle that you may use to start developing -your application. Then, if you need to split the application into reusable -components, you can create your own bundles. - -Registering a Bundle -~~~~~~~~~~~~~~~~~~~~ - -An application is made up of bundles as defined in the ``registerBundles()`` -method of the ``AppKernel`` class. Each bundle is a directory that contains -a single Bundle class that describes it:: - - // app/AppKernel.php - public function registerBundles() +You are my hero! Who would have thought that you would still be here after the first +two parts? Your efforts will be well-rewarded soon. The first two parts didn't look +too deeply at the architecture of the framework. Because it makes Symfony stand apart +from the framework crowd, let's dive into the architecture now. + +Add Logging +----------- + +A new Symfony app is micro: it's basically just a routing & controller system. But +thanks to Flex, installing more features is simple. + +Want a logging system? No problem: + +.. code-block:: terminal + + $ composer require logger + +This installs and configures (via a recipe) the powerful `Monolog`_ library. To +use the logger in a controller, add a new argument type-hinted with ``LoggerInterface``:: + + // src/Controller/DefaultController.php + namespace App\Controller; + + use Psr\Log\LoggerInterface; + use Symfony\Component\Routing\Annotation\Route; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + + class DefaultController extends AbstractController { - $bundles = [ - new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new Symfony\Bundle\SecurityBundle\SecurityBundle(), - new Symfony\Bundle\TwigBundle\TwigBundle(), - new Symfony\Bundle\MonologBundle\MonologBundle(), - new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), - new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), - new Symfony\Bundle\AsseticBundle\AsseticBundle(), - new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), - new AppBundle\AppBundle(), - ]; - - if (in_array($this->getEnvironment(), ['dev', 'test'])) { - $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); - $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); - $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); + /** + * @Route("/hello/{name}") + */ + public function index($name, LoggerInterface $logger) + { + $logger->info("Saying hello to $name!"); + + // ... } - - return $bundles; } -In addition to the AppBundle that was already talked about, notice that -the kernel also enables other bundles that are part of Symfony, such as -FrameworkBundle, DoctrineBundle, SwiftmailerBundle and AsseticBundle. - -Configuring a Bundle -~~~~~~~~~~~~~~~~~~~~ - -Each bundle can be customized via configuration files written in YAML, XML, -or PHP. Have a look at this sample of the default Symfony configuration: - -.. code-block:: yaml - - # app/config/config.yml - imports: - - { resource: parameters.yml } - - { resource: security.yml } - - { resource: services.yml } - - framework: - #esi: ~ - #translator: { fallbacks: ['%locale%'] } - secret: '%secret%' - router: - resource: '%kernel.project_dir%/app/config/routing.yml' - strict_requirements: '%kernel.debug%' - form: true - csrf_protection: true - validation: { enable_annotations: true } - templating: { engines: ['twig'] } - default_locale: '%locale%' - trusted_proxies: ~ - session: ~ - - # Twig Configuration - twig: - debug: '%kernel.debug%' - strict_variables: '%kernel.debug%' - - # Swift Mailer Configuration - swiftmailer: - transport: '%mailer_transport%' - host: '%mailer_host%' - username: '%mailer_user%' - password: '%mailer_password%' - spool: { type: memory } - - # ... - -Each first level entry like ``framework``, ``twig`` and ``swiftmailer`` -defines the configuration for a specific bundle. For example, ``framework`` -configures the FrameworkBundle while ``swiftmailer`` configures the -SwiftmailerBundle. - -Each environment can override the default configuration by providing a -specific configuration file. For example, the ``dev`` environment loads -the ``config_dev.yml`` file, which loads the main configuration (i.e. -``config.yml``) and then modifies it to add some debugging tools: - -.. code-block:: yaml - - # app/config/config_dev.yml - imports: - - { resource: config.yml } - - framework: - router: { resource: '%kernel.project_dir%/app/config/routing_dev.yml' } - profiler: { only_exceptions: false } - - web_profiler: - toolbar: true - intercept_redirects: false - - # ... - -Extending a Bundle -~~~~~~~~~~~~~~~~~~ - -In addition to being a nice way to organize and configure your code, a bundle -can extend another bundle. Bundle inheritance allows you to override any -existing bundle in order to customize its controllers, templates, or any -of its files. - -Logical File Names -.................. - -When you want to reference a file from a bundle, use this notation: -``@BUNDLE_NAME/path/to/file``; Symfony will resolve ``@BUNDLE_NAME`` -to the real path to the bundle. For instance, the logical path -``@AppBundle/Controller/DefaultController.php`` would be converted to -``src/AppBundle/Controller/DefaultController.php``, because Symfony knows -the location of the AppBundle. - -Logical Controller Names -........................ - -For controllers, you need to reference actions using the format -``BUNDLE_NAME:CONTROLLER_NAME:ACTION_NAME``. For instance, -``AppBundle:Default:index`` maps to the ``indexAction()`` method from the -``AppBundle\Controller\DefaultController`` class. - -Extending Bundles -................. - -If you follow these conventions, then you can use -:doc:`bundle inheritance ` to override files, -controllers or templates. For example, you can create a bundle - NewBundle -- and specify that it overrides AppBundle. When Symfony loads the -``AppBundle:Default:index`` controller, it will first look for the -``DefaultController`` class in NewBundle and, if it doesn't exist, then -look inside AppBundle. This means that one bundle can override almost any -part of another bundle! - -Do you understand now why Symfony is so flexible? Share your bundles between -applications, store them locally or globally, your choice. - -.. _using-vendors: - -Using Vendors -------------- - -Odds are that your application will depend on third-party libraries. Those -should be stored in the ``vendor/`` directory. You should never touch anything -in this directory, because it is exclusively managed by Composer. This directory -already contains the Symfony libraries, the SwiftMailer library, the Doctrine -ORM, the Twig templating system and some other third party libraries and -bundles. - -Understanding the Cache and Logs --------------------------------- - -Symfony applications can contain several configuration files defined in -several formats (YAML, XML, PHP, etc.). Instead of parsing and combining -all those files for each request, Symfony uses its own cache system. In -fact, the application configuration is only parsed for the very first request -and then compiled down to plain PHP code stored in the ``var/cache/`` -directory. - -In the development environment, Symfony is smart enough to update the cache -when you change a file. But in the production environment, to speed things -up, it is your responsibility to clear the cache when you update your code -or change its configuration. Execute this command to clear the cache in -the ``prod`` environment: +That's it! The new log message will be written to ``var/log/dev.log``. The log +file path or even a different method of logging can be configured by updating +one of the config files added by the recipe. + +Services & Autowiring +--------------------- + +But wait! Something *very* cool just happened. Symfony read the ``LoggerInterface`` +type-hint and automatically figured out that it should pass us the Logger object! +This is called *autowiring*. + +Every bit of work that's done in a Symfony app is done by an *object*: the Logger +object logs things and the Twig object renders templates. These objects are called +*services* and they are *tools* that help you build rich features. + +To make life awesome, you can ask Symfony to pass you a service by using a type-hint. +What other possible classes or interfaces could you use? Find out by running: .. code-block:: terminal - $ php bin/console cache:clear --env=prod + $ php bin/console debug:autowiring -When developing a web application, things can go wrong in many ways. The -log files in the ``var/logs/`` directory tell you everything about the requests -and help you fix the problem quickly. + # this is just a *small* sample of the output... -Using the Command Line Interface --------------------------------- + Describes a logger instance. + Psr\Log\LoggerInterface (monolog.logger) -Each application comes with a command line interface tool (``bin/console``) -that helps you maintain your application. It provides commands that boost -your productivity by automating tedious and repetitive tasks. + Request stack that controls the lifecycle of requests. + Symfony\Component\HttpFoundation\RequestStack (request_stack) -Run it without any arguments to learn more about its capabilities: + Interface for the session. + Symfony\Component\HttpFoundation\Session\SessionInterface (session) -.. code-block:: terminal + RouterInterface is the interface that all Router classes must implement. + Symfony\Component\Routing\RouterInterface (router.default) + + [...] + +This is just a short summary of the full list! And as you add more packages, this +list of tools will grow! + +Creating Services +----------------- + +To keep your code organized, you can even create your own services! Suppose you +want to generate a random greeting (e.g. "Hello", "Yo", etc). Instead of putting +this code directly in your controller, create a new class:: + + // src/GreetingGenerator.php + namespace App; + + class GreetingGenerator + { + public function getRandomGreeting() + { + $greetings = ['Hey', 'Yo', 'Aloha']; + $greeting = $greetings[array_rand($greetings)]; + + return $greeting; + } + } + +Great! You can use this immediately in your controller:: + + // src/Controller/DefaultController.php + namespace App\Controller; + + use App\GreetingGenerator; + use Psr\Log\LoggerInterface; + use Symfony\Component\Routing\Annotation\Route; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + + class DefaultController extends AbstractController + { + /** + * @Route("/hello/{name}") + */ + public function index($name, LoggerInterface $logger, GreetingGenerator $generator) + { + $greeting = $generator->getRandomGreeting(); + + $logger->info("Saying $greeting to $name!"); + + // ... + } + } + +That's it! Symfony will instantiate the ``GreetingGenerator`` automatically and +pass it as an argument. But, could we *also* move the logger logic to ``GreetingGenerator``? +Yes! You can use autowiring inside a service to access *other* services. The only +difference is that it's done in the constructor: + +.. code-block:: diff + + // src/GreetingGenerator.php + + use Psr\Log\LoggerInterface; + + class GreetingGenerator + { + + private $logger; + + + + public function __construct(LoggerInterface $logger) + + { + + $this->logger = $logger; + + } + + public function getRandomGreeting() + { + // ... + + + $this->logger->info('Using the greeting: '.$greeting); + + return $greeting; + } + } + +Yes! This works too: no configuration, no time wasted. Keep coding! + +Twig Extension & Autoconfiguration +---------------------------------- + +Thanks to Symfony's service handling, you can *extend* Symfony in many ways, like +by creating an event subscriber or a security voter for complex authorization +rules. Let's add a new filter to Twig called ``greet``. How? Create a class +that extends ``AbstractExtension``:: + + // src/Twig/GreetExtension.php + namespace App\Twig; + + use App\GreetingGenerator; + use Twig\Extension\AbstractExtension; + use Twig\TwigFilter; + + class GreetExtension extends AbstractExtension + { + private $greetingGenerator; + + public function __construct(GreetingGenerator $greetingGenerator) + { + $this->greetingGenerator = $greetingGenerator; + } + + public function getFilters() + { + return [ + new TwigFilter('greet', [$this, 'greetUser']), + ]; + } + + public function greetUser($name) + { + $greeting = $this->greetingGenerator->getRandomGreeting(); + + return "$greeting $name!"; + } + } + +After creating just *one* file, you can use this immediately: + +.. code-block:: twig + + {# templates/default/index.html.twig #} + {# Will print something like "Hey Symfony!" #} +

{{ name|greet }}

+ +How does this work? Symfony notices that your class extends ``AbstractExtension`` +and so *automatically* registers it as a Twig extension. This is called autoconfiguration, +and it works for *many* many things. Create a class and then extend a base class +(or implement an interface). Symfony takes care of the rest. + +Blazing Speed: The Cached Container +----------------------------------- + +After seeing how much Symfony handles automatically, you might be wondering: "Doesn't +this hurt performance?" Actually, no! Symfony is blazing fast. + +How is that possible? The service system is managed by a very important object called +the "container". Most frameworks have a container, but Symfony's is unique because +it's *cached*. When you loaded your first page, all of the service information was +compiled and saved. This means that the autowiring and autoconfiguration features +add *no* overhead! It also means that you get *great* errors: Symfony inspects and +validates *everything* when the container is built. - $ php bin/console +Now you might be wondering what happens when you update a file and the cache needs +to rebuild? I like your thinking! It's smart enough to rebuild on the next page +load. But that's really the topic of the next section. -The ``--help`` option helps you discover the usage of a command: +Development Versus Production: Environments +------------------------------------------- + +One of a framework's main jobs is to make debugging easy! And our app is *full* of +great tools for this: the web debug toolbar displays at the bottom of the page, errors +are big, beautiful & explicit, and any configuration cache is automatically rebuilt +whenever needed. + +But what about when you deploy to production? We will need to hide those tools and +optimize for speed! + +This is solved by Symfony's *environment* system and there are three: ``dev``, ``prod`` +and ``test``. Based on the environment, Symfony loads different files in the ``config/`` +directory: + +.. code-block:: text + + config/ + ├─ services.yaml + ├─ ... + └─ packages/ + ├─ framework.yaml + ├─ ... + ├─ **dev/** + ├─ monolog.yaml + └─ ... + ├─ **prod/** + └─ monolog.yaml + └─ **test/** + ├─ framework.yaml + └─ ... + └─ routes/ + ├─ annotations.yaml + └─ **dev/** + ├─ twig.yaml + └─ web_profiler.yaml + +This is a *powerful* idea: by changing one piece of configuration (the environment), +your app is transformed from a debugging-friendly experience to one that's optimized +for speed. + +Oh, how do you change the environment? Change the ``APP_ENV`` environment variable +from ``dev`` to ``prod``: + +.. code-block:: diff + + # .env + - APP_ENV=dev + + APP_ENV=prod + +But I want to talk more about environment variables next. Change the value back +to ``dev``: debugging tools are great when you're working locally. + +Environment Variables +--------------------- + +Every app contains configuration that's different on each server - like database +connection information or passwords. How should these be stored? In files? Or some +other way? + +Symfony follows the industry best practice by storing server-based configuration +as *environment* variables. This means that Symfony works *perfectly* with +Platform as a Service (PaaS) deployment systems as well as Docker. + +But setting environment variables while developing can be a pain. That's why your +app automatically loads a ``.env`` file. The keys in this file then become environment +variables and are read by your app: + +.. code-block:: bash + + # .env + ###> symfony/framework-bundle ### + APP_ENV=dev + APP_SECRET=cc86c7ca937636d5ddf1b754beb22a10 + ###< symfony/framework-bundle ### + +At first, the file doesn't contain much. But as your app grows, you'll add more +configuration as you need it. But, actually, it gets much more interesting! Suppose +your app needs a database ORM. Let's install the Doctrine ORM: .. code-block:: terminal - $ php bin/console debug:router --help + $ composer require doctrine + +Thanks to a new recipe installed by Flex, look at the ``.env`` file again: + +.. code-block:: diff + + ###> symfony/framework-bundle ### + APP_ENV=dev + APP_SECRET=cc86c7ca937636d5ddf1b754beb22a10 + ###< symfony/framework-bundle ### + + + ###> doctrine/doctrine-bundle ### + + # ... + + DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name + + ###< doctrine/doctrine-bundle ### + +The new ``DATABASE_URL`` environment variable was added *automatically* and is already +referenced by the new ``doctrine.yaml`` configuration file. By combining environment +variables and Flex, you're using industry best practices without any extra effort. -Final Thoughts --------------- +Keep Going! +----------- -Call me crazy, but after reading this part, you should be comfortable with -moving things around and making Symfony work for you. Everything in Symfony -is designed to get out of your way. So, feel free to rename and move directories -around as you see fit. +Call me crazy, but after reading this part, you should be comfortable with the most +*important* parts of Symfony. Everything in Symfony is designed to get out of your +way so you can keep coding and adding features, all with the speed and quality you +demand. -And that's all for the quick tour. From testing to sending emails, you still -need to learn a lot to become a Symfony master. Ready to dig into these -topics now? Look no further - go to the official :doc:`/index` and -pick any topic you want. +That's all for the quick tour. From authentication, to forms, to caching, there is +so much more to discover. Ready to dig into these topics now? Look no further - go +to the official :doc:`/index` and pick any guide you want. -.. _`Composer`: https://getcomposer.org +.. _`Monolog`: https://github.com/Seldaek/monolog diff --git a/quick_tour/the_big_picture.rst b/quick_tour/the_big_picture.rst index 525c0e8c411..de20f8d6c8c 100644 --- a/quick_tour/the_big_picture.rst +++ b/quick_tour/the_big_picture.rst @@ -1,261 +1,200 @@ The Big Picture =============== -Start using Symfony in 10 minutes! This chapter will walk you through the -most important concepts behind Symfony and explain how you can get started -quickly by showing you a simple project in action. +Start using Symfony in 10 minutes! Really! That's all you need to understand the +most important concepts and start building a real project! If you've used a web framework before, you should feel right at home with -Symfony. If not, welcome to a whole new way of developing web applications. +Symfony. If not, welcome to a whole new way of developing web applications. Symfony +*embraces* best practices, keeps backwards compatibility (Yes! Upgrading is always +safe & easy!) and offers long-term support. .. _installing-symfony2: -Installing Symfony ------------------- +Downloading Symfony +------------------- -Before continuing reading this chapter, make sure to have installed both PHP -and Symfony as explained in the :doc:`/setup` article. +First, make sure you've installed `Composer`_ and have PHP 7.1.3 or higher. -Understanding the Fundamentals ------------------------------- +Ready? In a terminal, run: -One of the main goals of a framework is to keep your code organized and -to allow your application to evolve easily over time by avoiding the mixing -of database calls, HTML tags and other PHP code in the same script. To achieve -this goal with Symfony, you'll first need to learn a few fundamental concepts. +.. code-block:: terminal -When developing a Symfony application, your responsibility as a developer -is to write the code that maps the user's *request* (e.g. ``http://localhost:8000/``) -to the *resource* associated with it (the ``Homepage`` HTML page). + $ composer create-project symfony/skeleton quick_tour -The code to execute is defined as methods of PHP classes. The methods are -called **actions** and the classes **controllers**, but in practice most -developers use **controllers** to refer to both of them. The mapping between -user's requests and that code is defined via the **routing** configuration. -And the contents displayed in the browser are usually rendered using -**templates**. +This creates a new ``quick_tour/`` directory with a small, but powerful new +Symfony application: -When you go to ``http://localhost:8000/app/example``, Symfony will execute -the controller in ``src/AppBundle/Controller/DefaultController.php`` and -render the ``app/Resources/views/default/index.html.twig`` template. +.. code-block:: text -In the following sections you'll learn in detail the inner workings of Symfony -controllers, routes and templates. + quick_tour/ + ├─ .env + ├─ bin/console + ├─ composer.json + ├─ composer.lock + ├─ config/ + ├─ public/index.php + ├─ src/ + ├─ symfony.lock + ├─ var/ + └─ vendor/ -Actions and Controllers -~~~~~~~~~~~~~~~~~~~~~~~ +Can we already load the project in a browser? Of course! You can setup +:doc:`Nginx or Apache ` and configure their document +root to be the ``public/`` directory. But, for development, Symfony has its own server. +Install and run it with: -Open the ``src/AppBundle/Controller/DefaultController.php`` file and you'll -see the following code (for now, don't look at the ``@Route`` configuration -because that will be explained in the next section):: +.. code-block:: terminal - namespace AppBundle\Controller; + $ composer require --dev server + $ php bin/console server:start - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\Routing\Annotation\Route; +Try your new app by going to ``http://localhost:8000`` in a browser! - class DefaultController extends Controller - { - /** - * @Route("/", name="homepage") - */ - public function indexAction() - { - return $this->render('default/index.html.twig', [ - // ... - ]); - } - } +.. image:: /_images/quick_tour/no_routes_page.png + :align: center + :class: with-browser -In Symfony applications, **controllers** are usually PHP classes whose names -are suffixed with the ``Controller`` word. In this example, the controller -is called ``Default`` and the PHP class is called ``DefaultController``. +Fundamentals: Route, Controller, Response +----------------------------------------- -The methods defined in a controller are called **actions**, they are usually -associated with one URL of the application and their names are suffixed -with ``Action``. In this example, the ``Default`` controller has only one -action called ``index`` and defined in the ``indexAction()`` method. +Our project only has about 15 files, but it's ready to become a sleek API, a robust +web app, or a microservice. Symfony starts small, but scales with you. -Actions are usually very short - around 10-15 lines of code - because they -just call other parts of the application to get or generate the needed -information and then they render a template to show the results to the user. +But before we go too far, let's dig into the fundamentals by building our first page. -In this example, the ``index`` action is practically empty because it doesn't -need to call any other method. The action just renders a template to welcome -you to Symfony. +Start in ``config/routes.yaml``: this is where *we* can define the URL to our new +page. Uncomment the example that already lives in the file: -Routing -~~~~~~~ +.. code-block:: yaml -Symfony routes each request to the action that handles it by matching the -requested URL against the paths configured by the application. Open again -the ``src/AppBundle/Controller/DefaultController.php`` file and take a look -at the three lines of code above the ``indexAction()`` method:: + # config/routes.yaml + index: + path: / + controller: 'App\Controller\DefaultController::index' - // src/AppBundle/Controller/DefaultController.php - namespace AppBundle\Controller; +This is called a *route*: it defines the URL to your page (``/``) and the "controller": +the *function* that will be called whenever anyone goes to this URL. That function +doesn't exist yet, so let's create it! - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\Routing\Annotation\Route; +In ``src/Controller``, create a new ``DefaultController`` class and an ``index`` +method inside:: - class DefaultController extends Controller + // src/Controller/DefaultController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + + class DefaultController { - /** - * @Route("/", name="homepage") - */ - public function indexAction() + public function index() { - return $this->render('default/index.html.twig', [ - // ... - ]); + return new Response('Hello!'); } } -These three lines define the routing configuration via the ``@Route()`` -annotation. A **PHP annotation** is a convenient way to configure a method -without having to write regular PHP code. Beware that annotation blocks -start with ``/**``, whereas regular PHP comments start with ``/*``. - -The first value of ``@Route()`` defines the URL that will trigger the execution -of the action. As you don't have to add the host of your application to -the URL (e.g. ``http://example.com``), these URLs are always relative and -they are usually called *paths*. In this case, the ``/`` path refers to the -application homepage. The second value of ``@Route()`` (e.g. ``name="homepage"``) -is optional and sets the name of this route. For now this name is not needed, -but later it'll be useful for linking pages. - -Considering all this, the ``@Route("/", name="homepage")`` annotation creates a -new route called ``homepage`` which makes Symfony execute the ``index`` action -of the ``Default`` controller when the user browses the ``/`` path of the application. - -.. tip:: - - In addition to PHP annotations, routes can be configured in YAML, XML - or PHP files, as explained in the :doc:`/routing` guide. This flexibility - is one of the main features of Symfony, a framework that never imposes a - particular configuration format on you. - -Templates -~~~~~~~~~ - -The only content of the ``index`` action is this PHP instruction:: - - return $this->render('default/index.html.twig', [ - // ... - ]); +That's it! Try going to the homepage: ``http://localhost:8000/``. Symfony sees +that the URL matches our route and then executes the new ``index()`` method. -The ``$this->render()`` method is a convenient shortcut to render a template. -Symfony provides some useful shortcuts to any controller extending from -the base Symfony :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` -class. +A controller is just a normal function with *one* rule: it must return a Symfony +``Response`` object. But that response can contain anything: simple text, JSON or +a full HTML page. -By default, application templates are stored in the ``app/Resources/views/`` -directory. Therefore, the ``default/index.html.twig`` template corresponds -to the ``app/Resources/views/default/index.html.twig``. Open that file and -you'll see the following code: +But the routing system is *much* more powerful. So let's make the route more interesting: -.. code-block:: html+twig +.. code-block:: diff - {# app/Resources/views/default/index.html.twig #} - {% extends 'base.html.twig' %} + # config/routes.yaml + index: + - path: / + + path: /hello/{name} + controller: 'App\Controller\DefaultController::index' - {% block body %} -

Welcome to Symfony

+The URL to this page has changed: it is *now* ``/hello/*``: the ``{name}`` acts +like a wildcard that matches anything. And it gets better! Update the controller too: - {# ... #} - {% endblock %} +.. code-block:: diff -This template is created with `Twig`_, a template engine created for modern PHP -applications. The :doc:`second part of this tutorial ` -explains how templates work in Symfony. + // src/Controller/DefaultController.php + namespace App\Controller; -.. _quick-tour-big-picture-environments: + use Symfony\Component\HttpFoundation\Response; -Working with Environments -------------------------- + class DefaultController + { + - public function index() + + public function index($name) + { + - return new Response('Hello!'); + + return new Response("Hello $name!"); + } + } -Now that you have a better understanding of how Symfony works, take a closer -look at the bottom of any Symfony rendered page. You should notice a small -bar with the Symfony logo. This is the "web debug toolbar" and it is a Symfony -developer's best friend! +Try the page out by going to ``http://localhost:8000/hello/Symfony``. You should +see: Hello Symfony! The value of the ``{name}`` in the URL is available as a ``$name`` +argument in your controller. -.. image:: /_images/quick_tour/web_debug_toolbar.png - :align: center - :class: with-browser +But this can be even simpler! So let's install annotations support: -But what you see initially is only the tip of the iceberg; click on any -of the bar sections to open the profiler and get much more detailed information -about the request, the query parameters, security details and database queries: +.. code-block:: terminal -.. image:: /_images/quick_tour/profiler.png - :align: center - :class: with-browser + $ composer require annotations -This tool provides so much internal information about your application that -you may be worried about your visitors accessing sensible information. Symfony -is aware of this issue and for that reason, it won't display this bar when -your application is running in the production server. +Now, comment-out the YAML route by adding the ``#`` character: -How does Symfony know whether your application is running locally or on -a production server? Keep reading to discover the concept of **execution -environments**. - -.. _quick-tour-big-picture-environments-intro: +.. code-block:: yaml -What is an Environment? -~~~~~~~~~~~~~~~~~~~~~~~ + # config/routes.yaml + # index: + # path: /hello/{name} + # controller: 'App\Controller\DefaultController::index' -An environment represents a group of configurations that's used to run your -application. Symfony defines two environments by default: ``dev`` (suited for -when developing the application locally) and ``prod`` (optimized for when -executing the application on production). +Instead, add the route *right above* the controller method: -When you visit the ``http://localhost:8000`` URL in your browser, you're -executing your Symfony application in the ``dev`` environment. To visit -your application in the ``prod`` environment, visit the ``http://localhost:8000/app.php`` -URL instead. If you prefer to always show the ``dev`` environment in the -URL, you can visit ``http://localhost:8000/app_dev.php`` URL. +.. code-block:: diff -The main difference between environments is that ``dev`` is optimized to -provide lots of information to the developer, which means worse application -performance. Meanwhile, ``prod`` is optimized to get the best performance, -which means that debug information is disabled, as well as the web debug -toolbar. + // src/Controller/DefaultController.php + namespace App\Controller; -The other difference between environments is the configuration options used -to execute the application. When you access the ``dev`` environment, Symfony -loads the ``app/config/config_dev.yml`` configuration file. When you access -the ``prod`` environment, Symfony loads ``app/config/config_prod.yml`` file. + use Symfony\Component\HttpFoundation\Response; + + use Symfony\Component\Routing\Annotation\Route; -Typically, the environments share a large amount of configuration options. -For that reason, you put your common configuration in ``config.yml`` and -override the specific configuration file for each environment where necessary: + class DefaultController + { + + /** + + * @Route("/hello/{name}") + + */ + public function index($name) { + // ... + } + } -.. code-block:: yaml +This works just like before! But by using annotations, the route and controller +live right next to each other. Need another page? Add another route and method +in ``DefaultController``:: - # app/config/config_dev.yml - imports: - - { resource: config.yml } + // src/Controller/DefaultController.php + namespace App\Controller; - web_profiler: - toolbar: true - intercept_redirects: false + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Routing\Annotation\Route; -In this example, the ``config_dev.yml`` configuration file imports the common -``config.yml`` file and then overrides any existing web debug toolbar configuration -with its own options. + class DefaultController + { + // ... -For more details on environments, see -:ref:`the "Environments" section ` of the -Configuration guide. + /** + * @Route("/simplicity") + */ + public function simple() + { + return new Response('Simple! Easy! Great!'); + } + } -Final Thoughts --------------- +Routing can do *even* more, but we'll save that for another time! Right now, our +app needs more features! Like a template engine, logging, debugging tools and more. -Congratulations! You've had your first taste of Symfony code. That wasn't -so hard, was it? There's a lot more to explore, but you should already see -how Symfony makes it really easy to implement web sites better and faster. -If you are eager to learn more about Symfony, dive into the next section: -":doc:`The View `". +Keep reading with :doc:`/quick_tour/flex_recipes`. -.. _`Twig`: https://twig.symfony.com/ +.. _`Composer`: https://getcomposer.org/ diff --git a/quick_tour/the_controller.rst b/quick_tour/the_controller.rst deleted file mode 100644 index 9aef4a9b155..00000000000 --- a/quick_tour/the_controller.rst +++ /dev/null @@ -1,348 +0,0 @@ -The Controller -============== - -Still here after the first two parts? You are already becoming a Symfony -fan! Without further ado, discover what controllers can do for you. - -Returning Raw Responses ------------------------ - -Symfony defines itself as a Request-Response framework. When the user makes -a request to your application, Symfony creates a ``Request`` object to -encapsulate all the information related to that request. Similarly, the -result of executing any action of any controller is the creation of a -``Response`` object which Symfony uses to generate the HTML content returned -to the user. - -So far, all the actions shown in this tutorial used the ``$this->render()`` -controller shortcut method to return a rendered template as result. In case -you need it, you can also create a raw ``Response`` object to return any -text content:: - - // src/AppBundle/Controller/DefaultController.php - namespace AppBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\Routing\Annotation\Route; - - class DefaultController extends Controller - { - /** - * @Route("/", name="homepage") - */ - public function indexAction() - { - return new Response('Welcome to Symfony!'); - } - } - -Route Parameters ----------------- - -Most of the time, the URLs of applications include variable parts on them. -If you are creating for example a blog application, the URL to display the -articles should include their title or some other unique identifier to let -the application know the exact article to display. - -In Symfony applications, the variable parts of the routes are enclosed in -curly braces (e.g. ``/blog/read/{article_title}/``). Each variable part -is assigned a unique name that can be used later in the controller to retrieve -each value. - -Let's create a new action with route variables to show this feature in action. -Open the ``src/AppBundle/Controller/DefaultController.php`` file and add -a new method called ``helloAction()`` with the following content:: - - // src/AppBundle/Controller/DefaultController.php - namespace AppBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\Routing\Annotation\Route; - - class DefaultController extends Controller - { - // ... - - /** - * @Route("/hello/{name}", name="hello") - */ - public function helloAction($name) - { - return $this->render('default/hello.html.twig', [ - 'name' => $name, - ]); - } - } - -Open your browser and access the ``http://localhost:8000/hello/fabien`` -URL to see the result of executing this new action. Instead of the action -result, you'll see an error page. As you probably guessed, the cause of -this error is that we're trying to render a template -(``default/hello.html.twig``) that doesn't exist yet. - -Create the new ``app/Resources/views/default/hello.html.twig`` template -with the following content: - -.. code-block:: html+twig - - {# app/Resources/views/default/hello.html.twig #} - {% extends 'base.html.twig' %} - - {% block body %} -

Hi {{ name }}! Welcome to Symfony!

- {% endblock %} - -Browse again the ``http://localhost:8000/hello/fabien`` URL and you'll see -this new template rendered with the information passed by the controller. -If you change the last part of the URL (e.g. -``http://localhost:8000/hello/thomas``) and reload your browser, the page -will display a different message. And if you remove the last part of the -URL (e.g. ``http://localhost:8000/hello``), Symfony will display an error -because the route expects a name and you haven't provided it. - -Using Formats -------------- - -Nowadays, a web application should be able to deliver more than just HTML -pages. From XML for RSS feeds or Web Services, to JSON for Ajax requests, -there are plenty of different formats to choose from. Supporting those formats -in Symfony is straightforward thanks to a special variable called ``_format`` -which stores the format requested by the user. - -Tweak the ``hello`` route by adding a new ``_format`` variable with ``html`` -as its default value:: - - // src/AppBundle/Controller/DefaultController.php - use Symfony\Component\Routing\Annotation\Route; - - // ... - - /** - * @Route("/hello/{name}.{_format}", defaults={"_format"="html"}, name="hello") - */ - public function helloAction($name, $_format) - { - return $this->render('default/hello.'.$_format.'.twig', [ - 'name' => $name, - ]); - } - -Obviously, when you support several request formats, you have to provide -a template for each of the supported formats. In this case, you should create -a new ``hello.xml.twig`` template: - -.. code-block:: xml+php - - - - {{ name }} - - -Now, when you browse to ``http://localhost:8000/hello/fabien``, you'll see -the regular HTML page because ``html`` is the default format. When visiting -``http://localhost:8000/hello/fabien.html`` you'll get again the HTML page, -this time because you explicitly asked for the ``html`` format. Lastly, -if you visit ``http://localhost:8000/hello/fabien.xml`` you'll see the new -XML template rendered in your browser. - -That's all there is to it. For standard formats, Symfony will also -automatically choose the best ``Content-Type`` header for the response. -To restrict the formats supported by a given action, use the ``requirements`` -option of the ``@Route()`` annotation:: - - // src/AppBundle/Controller/DefaultController.php - use Symfony\Component\Routing\Annotation\Route; - - // ... - - /** - * @Route("/hello/{name}.{_format}", - * defaults = {"_format"="html"}, - * requirements = { "_format" = "html|xml|json" }, - * name = "hello" - * ) - */ - public function helloAction($name, $_format) - { - return $this->render('default/hello.'.$_format.'.twig', [ - 'name' => $name, - ]); - } - -The ``hello`` action will now match URLs like ``/hello/fabien.xml`` or -``/hello/fabien.json``, but it will show a 404 error if you try to get URLs -like ``/hello/fabien.js``, because the value of the ``_format`` variable -doesn't meet its requirements. - -.. _redirecting-and-forwarding: - -Redirecting ------------ - -If you want to redirect the user to another page, use the ``redirectToRoute()`` -method:: - - // src/AppBundle/Controller/DefaultController.php - class DefaultController extends Controller - { - /** - * @Route("/", name="homepage") - */ - public function indexAction() - { - return $this->redirectToRoute('hello', ['name' => 'Fabien']); - } - } - -The ``redirectToRoute()`` method takes as arguments the route name and an -optional array of parameters and redirects the user to the URL generated -with those arguments. - -Displaying Error Pages ----------------------- - -Errors will inevitably happen during the execution of every web application. -In the case of ``404`` errors, Symfony includes a handy shortcut that you -can use in your controllers:: - - // src/AppBundle/Controller/DefaultController.php - // ... - - class DefaultController extends Controller - { - /** - * @Route("/", name="homepage") - */ - public function indexAction() - { - // ... - throw $this->createNotFoundException(); - } - } - -For ``500`` errors, just throw a regular PHP exception inside the controller -and Symfony will transform it into a proper ``500`` error page:: - - // src/AppBundle/Controller/DefaultController.php - // ... - - class DefaultController extends Controller - { - /** - * @Route("/", name="homepage") - */ - public function indexAction() - { - // ... - throw new \Exception('Something went horribly wrong!'); - } - } - -Getting Information from the Request ------------------------------------- - -Sometimes your controllers need to access the information related to the -user request, such as their preferred language, IP address or the URL query -parameters. To get access to this information, add a new argument of type -``Request`` to the action. The name of this new argument doesn't matter, -but it must be preceded by the ``Request`` type in order to work (don't -forget to add the new ``use`` statement that imports this ``Request`` class):: - - // src/AppBundle/Controller/DefaultController.php - namespace AppBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\Routing\Annotation\Route; - - class DefaultController extends Controller - { - /** - * @Route("/", name="homepage") - */ - public function indexAction(Request $request) - { - // is it an Ajax request? - $isAjax = $request->isXmlHttpRequest(); - - // what's the preferred language of the user? - $language = $request->getPreferredLanguage(['en', 'fr']); - - // get the value of a $_GET parameter - $pageName = $request->query->get('page'); - - // get the value of a $_POST parameter - $pageName = $request->request->get('page'); - } - } - -In a template, you can also access the ``Request`` object via the special -``app.request`` variable automatically provided by Symfony: - -.. code-block:: html+twig - - {{ app.request.query.get('page') }} - - {{ app.request.request.get('page') }} - -Persisting Data in the Session ------------------------------- - -Even if the HTTP protocol is stateless, Symfony provides a nice session -object that represents the client (be it a real person using a browser, -a bot, or a web service). Between two requests, Symfony stores the attributes -in a cookie by using native PHP sessions. - -Storing and retrieving information from the session can be easily achieved -from any controller:: - - use Symfony\Component\HttpFoundation\Session\Session; - - public function indexAction(Session $session) - { - // stores an attribute for reuse during a later user request - $session->set('foo', 'bar'); - - // gets the value of a session attribute - $foo = $session->get('foo'); - - // uses a default value if the attribute doesn't exist - $foo = $session->get('foo', 'default_value'); - } - -You can also store "flash messages" that will auto-delete after the next -request. They are useful when you need to set a success message before -redirecting the user to another page (which will then show the message):: - - public function indexAction() - { - // ... - - // store a message for the very next request - $this->addFlash('notice', 'Congratulations, your action succeeded!'); - } - -And you can display the flash message in the template like this: - -.. code-block:: html+twig - - {% for message in app.flashes('notice') %} -
- {{ message }} -
- {% endfor %} - -.. versionadded:: 3.3 - - The ``app.flashes()`` Twig function was introduced in Symfony 3.3. Prior, - you had to use ``app.session.flashBag()``. - -Final Thoughts --------------- - -That's all there is to it and I'm not even sure you have spent the full -10 minutes. You were briefly introduced to bundles in the first part and -all the features you've learned about so far are part of the core FrameworkBundle. -But thanks to bundles, everything in Symfony can be extended or replaced. -That's the topic of the :doc:`next part of this tutorial `. diff --git a/quick_tour/the_view.rst b/quick_tour/the_view.rst deleted file mode 100644 index c33e2872096..00000000000 --- a/quick_tour/the_view.rst +++ /dev/null @@ -1,291 +0,0 @@ -The View -======== - -After reading the first part of this tutorial, you have decided that Symfony -was worth another 10 minutes. In this second part, you will learn more about -`Twig`_, the fast, flexible and secure template engine for PHP applications. -Twig makes your templates more readable and concise; it also makes them -more friendly for web designers. - -Getting Familiar with Twig --------------------------- - -The official `Twig documentation`_ is the best resource to learn everything -about this template engine. This section just gives you a quick overview -of its main concepts. - -A Twig template is a text file that can generate any type of content (HTML, -CSS, JavaScript, XML, CSV, LaTeX, etc.). Twig elements are separated from -the rest of the template contents using any of these delimiters: - -``{{ ... }}`` - Prints the content of a variable or the result of evaluating an expression; - -``{% ... %}`` - Controls the logic of the template; it is used for example to execute - ``for`` loops and ``if`` statements. - -``{# ... #}`` - Allows including comments inside templates. Contrary to HTML comments, - they aren't included in the rendered template. - -Below is a minimal template that illustrates a few basics, using two variables -``page_title`` and ``navigation``, which would be passed into the template: - -.. code-block:: html+twig - - - - - {{ page_title }} - - -

{{ page_title }}

- - - - - -To render a template in Symfony, use the ``render()`` method from within a -controller. If the template needs variables to generate its contents, pass -them as an array using the second optional argument:: - - $this->render('default/index.html.twig', [ - 'variable_name' => 'variable_value', - ]); - -Variables passed to a template can be strings, arrays or even objects. Twig -abstracts the difference between them and lets you access "attributes" of -a variable with the dot (``.``) notation. The following code listing shows -how to display the content of a variable passed by the controller depending -on its type: - -.. code-block:: twig - - {# 1. Simple variables #} - {# $this->render('template.html.twig', [ - 'name' => 'Fabien', - ]) #} - {{ name }} - - {# 2. Arrays #} - {# $this->render('template.html.twig', [ - 'user' => ['name' => 'Fabien'] - ]) #} - {{ user.name }} - - {# alternative syntax for arrays #} - {{ user['name'] }} - - {# 3. Objects #} - {# $this->render('template.html.twig', [ - 'user' => new User('Fabien') - ]) #} - {{ user.name }} - {{ user.getName }} - - {# alternative syntax for objects #} - {{ user.name() }} - {{ user.getName() }} - -Decorating Templates --------------------- - -More often than not, templates in a project share common elements, like -the well-known header and footer. Twig solves this problem elegantly with -a concept called "template inheritance". This feature allows you to build -a base template that contains all the common elements of your site and -defines "blocks" of contents that child templates can override. - -The ``index.html.twig`` template uses the ``extends`` tag to indicate that -it inherits from the ``base.html.twig`` template: - -.. code-block:: html+twig - - {# app/Resources/views/default/index.html.twig #} - {% extends 'base.html.twig' %} - - {% block body %} -

Welcome to Symfony!

- {% endblock %} - -Open the ``app/Resources/views/base.html.twig`` file that corresponds to -the ``base.html.twig`` template and you'll find the following Twig code: - -.. code-block:: html+twig - - {# app/Resources/views/base.html.twig #} - - - - - {% block title %}Welcome!{% endblock %} - {% block stylesheets %}{% endblock %} - - - - {% block body %}{% endblock %} - {% block javascripts %}{% endblock %} - - - -The ``{% block %}`` tags tell the template engine that a child template -may override those portions of the template. In this example, the -``index.html.twig`` template overrides the ``body`` block, but not the -``title`` block, which will display the default content defined in the -``base.html.twig`` template. - -Using Tags, Filters and Functions ---------------------------------- - -One of the best features of Twig is its extensibility via tags, filters -and functions. Take a look at the following sample template that uses filters -extensively to modify the information before displaying it to the user: - -.. code-block:: twig - -

{{ article.title|capitalize }}

- -

{{ article.content|striptags|slice(0, 255) }} ...

- -

Tags: {{ article.tags|sort|join(", ") }}

- -

Activate your account before {{ 'next Monday'|date('M j, Y') }}

- -Don't forget to check out the official `Twig documentation`_ to learn everything -about filters, functions and tags. - -.. _including-other-templates: - -Including other Templates -------------------------- - -The best way to share a snippet of code between several templates is to -create a new template fragment that can then be included from other templates. - -Imagine that we want to display ads on some pages of our application. First, -create a ``banner.html.twig`` template: - -.. code-block:: twig - - {# app/Resources/views/ads/banner.html.twig #} -
- ... -
- -To display this ad on any page, include the ``banner.html.twig`` template -using the ``include()`` function: - -.. code-block:: html+twig - - {# app/Resources/views/default/index.html.twig #} - {% extends 'base.html.twig' %} - - {% block body %} -

Welcome to Symfony!

- - {{ include('ads/banner.html.twig') }} - {% endblock %} - -Embedding other Controllers ---------------------------- - -And what if you want to embed the result of another controller in a template? -That's very useful when working with Ajax, or when the embedded template -needs some variable not available in the main template. - -Suppose you've created a ``topArticlesAction()`` controller method to display -the most popular articles of your website. If you want to "render" the result -of that method (usually some HTML content) inside the ``index`` template, -use the ``render()`` function: - -.. code-block:: twig - - {# app/Resources/views/index.html.twig #} - {{ render(controller('AppBundle:Default:topArticles')) }} - -Here, the ``render()`` and ``controller()`` functions use the special -``AppBundle:Default:topArticles`` syntax to refer to the ``topArticlesAction()`` -action of the ``Default`` controller (the ``AppBundle`` part will be explained -later):: - - // src/AppBundle/Controller/DefaultController.php - class DefaultController extends Controller - { - public function topArticlesAction() - { - // look for the most popular articles in the database - $articles = ...; - - return $this->render('default/top_articles.html.twig', [ - 'articles' => $articles, - ]); - } - - // ... - } - -Creating Links between Pages ----------------------------- - -Creating links between pages is a must for web applications. Instead of -hardcoding URLs in templates, the ``path()`` function knows how to generate -URLs based on the routing configuration. That way, all your URLs -can be easily updated by just changing the configuration: - -.. code-block:: html+twig - - Return to homepage - -The ``path()`` function takes the route name as the first argument and you -can optionally pass an array of route parameters as the second argument. - -.. tip:: - - The ``url()`` function is very similar to the ``path()`` function, but generates - *absolute* URLs, which is very handy when rendering emails and RSS files: - ``Visit our website``. - -Including Assets: Images, JavaScripts and Stylesheets ------------------------------------------------------ - -What would the Internet be without images, JavaScripts and stylesheets? -Symfony provides the ``asset()`` function to deal with them easily: - -.. code-block:: twig - - - - - -The ``asset()`` function looks for the web assets inside the ``web/`` directory. -If you store them in another directory, read -:doc:`this article ` -to learn how to manage web assets. - -Using the ``asset()`` function, your application is more portable. The reason -is that you can move the application root directory anywhere under your -web root directory without changing anything in your template's code. - -Final Thoughts --------------- - -Twig is simple yet powerful. Thanks to layouts, blocks, templates and action -inclusions, it is very easy to organize your templates in a logical and -extensible way. - -You have only been working with Symfony for about 20 minutes, but you can -already do pretty amazing stuff with it. That's the power of Symfony. Learning -the basics is easy and you will soon learn that this simplicity is hidden -under a very flexible architecture. - -But I'm getting ahead of myself. First, you need to learn more about the -controller and that's exactly the topic of the :doc:`next part of this tutorial -`. Ready for another 10 minutes with Symfony? - -.. _`Twig`: https://twig.symfony.com/ -.. _`Twig documentation`: https://twig.symfony.com/doc/2.x/ diff --git a/reference/configuration/assetic.rst b/reference/configuration/assetic.rst deleted file mode 100644 index c86b93840e4..00000000000 --- a/reference/configuration/assetic.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. index:: - pair: Assetic; Configuration reference - -Assetic Configuration Reference (AsseticBundle) -=============================================== - -.. include:: /assetic/_standard_edition_warning.rst.inc - -You can get the full Asstic configuration reference as follows: - -.. code-block:: terminal - - # displays the default config values defined by Symfony - $ php bin/console config:dump-reference assetic - - # displays the actual config values used by your application - $ php bin/console debug:config assetic diff --git a/reference/configuration/debug.rst b/reference/configuration/debug.rst index 08bfaf3630a..f64f2f1167d 100644 --- a/reference/configuration/debug.rst +++ b/reference/configuration/debug.rst @@ -50,10 +50,6 @@ be cloned. After this depth is reached, only ``max_items`` items will be cloned. The default value is ``1``, which is consistent with older Symfony versions. -.. versionadded:: 3.4 - - The ``min_depth`` option was introduced in Symfony 3.4. - max_string_length ~~~~~~~~~~~~~~~~~ @@ -62,6 +58,8 @@ max_string_length This option configures the maximum string length before truncating the string. The default value (``-1``) means that strings are never truncated. +.. _configuration-debug-dump_destination: + dump_destination ~~~~~~~~~~~~~~~~ @@ -77,13 +75,13 @@ destination for dumps. Typically, you would set this to ``php://stderr``: .. code-block:: yaml - # app/config/config.yml + # config/packages/debug.yaml debug: dump_destination: php://stderr .. code-block:: xml - + loadFromExtension('debug', [ 'dump_destination' => 'php://stderr', ]); + +Configure it to ``"tcp://%env(VAR_DUMPER_SERVER)%"`` in order to use the :ref:`ServerDumper feature `. diff --git a/reference/configuration/doctrine.rst b/reference/configuration/doctrine.rst index ead6d97d0c3..011539585d7 100644 --- a/reference/configuration/doctrine.rst +++ b/reference/configuration/doctrine.rst @@ -22,7 +22,7 @@ configuration. When using XML, you must use the ``http://symfony.com/schema/dic/doctrine`` namespace and the related XSD schema is available at: - ``http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd`` + ``https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd`` .. index:: single: Configuration; Doctrine DBAL @@ -53,23 +53,23 @@ The following block shows all possible configuration keys: # if the url option is specified, it will override the above config url: mysql://db_user:db_password@127.0.0.1:3306/db_name # the DBAL driverClass option - driver_class: MyNamespace\MyDriverImpl + driver_class: App\DBAL\MyDatabaseDriver # the DBAL driverOptions option options: foo: bar - path: '%kernel.project_dir%/app/data/data.sqlite' + path: '%kernel.project_dir%/var/data/data.sqlite' memory: true unix_socket: /tmp/mysql.sock # the DBAL wrapperClass option - wrapper_class: MyDoctrineDbalConnectionWrapper + wrapper_class: App\DBAL\MyConnectionWrapper charset: UTF8 logging: '%kernel.debug%' - platform_service: MyOwnDatabasePlatformService + platform_service: App\DBAL\MyDatabasePlatformService server_version: '5.6' mapping_types: enum: string types: - custom: Acme\HelloBundle\MyCustomType + custom: App\DBAL\MyCustomType .. code-block:: xml @@ -80,7 +80,7 @@ The following block shows all possible configuration keys: xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/doctrine - http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd"> + https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd"> bar string - Acme\HelloBundle\MyCustomType + App\DBAL\MyCustomType @@ -240,7 +240,7 @@ The following example shows an overview of the caching configurations: # the 'service' type requires to define the 'id' option too query_cache_driver: type: service - id: my_doctrine_common_cache_service + id: App\ORM\MyCacheService Mapping Configuration ~~~~~~~~~~~~~~~~~~~~~ @@ -252,42 +252,33 @@ you can control. The following configuration options exist for a mapping: type .... -One of ``annotation``, ``xml``, ``yml``, ``php`` or ``staticphp``. This -specifies which type of metadata type your mapping uses. +One of ``annotation`` (the default value), ``xml``, ``yml``, ``php`` or +``staticphp``. This specifies which type of metadata type your mapping uses. dir ... -Path to the mapping or entity files (depending on the driver). If this path -is relative it is assumed to be relative to the bundle root. This only works -if the name of your mapping is a bundle name. If you want to use this option -to specify absolute paths you should prefix the path with the kernel parameters -that exist in the DIC (for example ``%kernel.project_dir%``). +Absolute path to the mapping or entity files (depending on the driver). prefix ...... -A common namespace prefix that all entities of this mapping share. This -prefix should never conflict with prefixes of other defined mappings otherwise -some of your entities cannot be found by Doctrine. This option defaults -to the bundle namespace + ``Entity``, for example for an application bundle -called AcmeHelloBundle prefix would be ``Acme\HelloBundle\Entity``. +A common namespace prefix that all entities of this mapping share. This prefix +should never conflict with prefixes of other defined mappings otherwise some of +your entities cannot be found by Doctrine. alias ..... Doctrine offers a way to alias entity namespaces to simpler, shorter names -to be used in DQL queries or for Repository access. When using a bundle -the alias defaults to the bundle name. +to be used in DQL queries or for Repository access. is_bundle ......... -This option is a derived value from ``dir`` and by default is set to ``true`` -if dir is relative proved by a ``file_exists()`` check that returns ``false``. -It is ``false`` if the existence check returns ``true``. In this case an -absolute path was specified and the metadata files are most likely in a -directory outside of a bundle. +This option is ``false`` by default and it's considered a legacy option. It was +only useful in previous Symfony versions, when it was recommended to use bundles +to organize the application code. Custom Mapping Entities in a Bundle ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -353,9 +344,7 @@ directory instead: Mapping Entities Outside of a Bundle ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can also create new mappings, for example outside of the Symfony folder. - -For example, the following looks for entity classes in the ``App\Entity`` +For example, the following looks for entity classes in the ``Entity`` namespace in the ``src/Entity`` directory and gives them an ``App`` alias (so you can say things like ``App:Post``): @@ -422,7 +411,7 @@ If the ``type`` on the bundle configuration isn't set, the DoctrineBundle will try to detect the correct mapping configuration format for the bundle. DoctrineBundle will look for files matching ``*.orm.[FORMAT]`` (e.g. -``Post.orm.yml``) in the configured ``dir`` of your mapping (if you're mapping +``Post.orm.yaml``) in the configured ``dir`` of your mapping (if you're mapping a bundle, then ``dir`` is relative to the bundle's directory). The bundle looks for (in this order) XML, YAML and PHP files. diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index ceda4286b49..11b51338cf8 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -22,7 +22,7 @@ configured under the ``framework`` key in your application configuration. When using XML, you must use the ``http://symfony.com/schema/dic/symfony`` namespace and the related XSD schema is available at: - ``http://symfony.com/schema/dic/symfony/symfony-1.0.xsd`` + ``https://symfony.com/schema/dic/symfony/symfony-1.0.xsd`` Configuration ------------- @@ -50,6 +50,7 @@ Configuration * :ref:`app ` * `default_doctrine_provider`_ * `default_memcached_provider`_ + * `default_pdo_provider`_ * `default_psr6_provider`_ * `default_redis_provider`_ * `directory`_ @@ -62,6 +63,7 @@ Configuration * `default_lifetime`_ * `provider`_ * `public`_ + * `tags`_ * `prefix_seed`_ * `system`_ @@ -97,12 +99,6 @@ Configuration * `collect`_ * `dsn`_ * :ref:`enabled ` - * `matcher`_ - - * `ip`_ - * :ref:`path ` - * `service`_ - * `only_exceptions`_ * `only_master_requests`_ @@ -126,11 +122,11 @@ Configuration * `resource`_ * `strict_requirements`_ * :ref:`type ` + * `utf8`_ * `secret`_ * `serializer`_ - * :ref:`cache ` * :ref:`circular_reference_handler ` * :ref:`enable_annotations ` * :ref:`enabled ` @@ -146,6 +142,7 @@ Configuration * `cookie_httponly`_ * `cookie_lifetime`_ * `cookie_path`_ + * `cookie_samesite`_ * `cookie_secure`_ * :ref:`enabled ` * `gc_divisor`_ @@ -183,6 +180,7 @@ Configuration * `validation`_ * :ref:`cache ` + * `email_validation_mode`_ * :ref:`enable_annotations ` * :ref:`enabled ` * :ref:`mapping ` @@ -250,17 +248,17 @@ named ``kernel.http_method_override``. .. caution:: - If you're using the :ref:`AppCache Reverse Proxy ` + If you're using the :ref:`HttpCache Reverse Proxy ` with this option, the kernel will ignore the ``_method`` parameter, which could lead to errors. To fix this, invoke the ``enableHttpMethodParameterOverride()`` method before creating the ``Request`` object:: - // web/app.php + // public/index.php // ... - $kernel = new AppCache($kernel); + $kernel = new CacheKernel($kernel); Request::enableHttpMethodParameterOverride(); // <-- add this line $request = Request::createFromGlobals(); @@ -281,7 +279,8 @@ ide Symfony turns file paths seen in variable dumps and exception messages into links that open those files right inside your browser. If you prefer to open those files in your favorite IDE or text editor, set this option to any of the -following values: ``phpstorm``, ``sublime``, ``textmate``, ``macvim`` and ``emacs``. +following values: ``phpstorm``, ``sublime``, ``textmate``, ``macvim``, ``emacs``, +``atom`` and ``vscode``. .. note:: @@ -297,27 +296,27 @@ doubling them to prevent Symfony from interpreting them as container parameters) .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: ide: 'myide://open?url=file://%%f&line=%%l' .. code-block:: xml - + + https://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ 'ide' => 'myide://open?url=file://%%f&line=%%l', ]); @@ -350,10 +349,6 @@ need to escape the percent signs (``%``) by doubling them. // and /foo/.../file as /bar/.../file also 'myide://%f:%l&/path/to/guest/>/path/to/host/&/foo/>/bar/&...' - .. versionadded:: 3.2 - - Guest to host mappings were introduced in Symfony 3.2. - .. _reference-framework-test: test @@ -364,7 +359,7 @@ test If this configuration setting is present (and not ``false``), then the services related to testing your application (e.g. ``test.client``) are loaded. This setting should be present in your ``test`` environment (usually via -``app/config/config_test.yml``). +``config/packages/test/framework.yaml``). .. seealso:: @@ -415,20 +410,20 @@ the application won't respond and the user will receive a 400 response. .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: trusted_hosts: ['^example\.com$', '^example\.org$'] .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> ^example\.com$ @@ -439,7 +434,7 @@ the application won't respond and the user will receive a 400 response. .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ 'trusted_hosts' => ['^example\.com$', '^example\.org$'], ]); @@ -450,7 +445,7 @@ Hosts can also be configured to respond to any subdomain, via In addition, you can also set the trusted hosts in the front controller using the ``Request::setTrustedHosts()`` method:: - // web/app.php + // public/index.php Request::setTrustedHosts(['^(.+\.)?example\.com$', '^(.+\.)?example\.org$']); The default value for this option is an empty array, meaning that the application @@ -470,7 +465,7 @@ form enabled ....... -**type**: ``boolean`` **default**: ``false`` +**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation Whether to enable the form services or not in the service container. If you don't use forms, setting this to ``false`` may increase your application's @@ -487,23 +482,24 @@ settings is configured. For more details, see :doc:`/forms`. +.. _reference-framework-csrf-protection: + csrf_protection ~~~~~~~~~~~~~~~ .. seealso:: - For more information about CSRF protection in forms, see :doc:`/form/csrf_protection`. + For more information about CSRF protection, see :doc:`/security/csrf`. .. _reference-csrf_protection-enabled: enabled ....... -**type**: ``boolean`` **default**: ``true`` if form support is enabled, ``false`` -otherwise +**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation This option can be used to disable CSRF protection on *all* forms. But you -can also :ref:`disable CSRF protection on individual forms `. +can also :ref:`disable CSRF protection on individual forms `. If you're using forms, but want to avoid starting your session (e.g. using forms in an API-only website), ``csrf_protection`` will need to be set to @@ -531,20 +527,20 @@ You can also set ``esi`` to ``true`` to enable it: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: esi: true .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -553,7 +549,7 @@ You can also set ``esi`` to ``true`` to enable it: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ 'esi' => true, ]); @@ -600,7 +596,7 @@ enabled **type**: ``boolean`` **default**: ``false`` The profiler can be enabled by setting this option to ``true``. When you -are using the Symfony Standard Edition, the profiler is enabled in the ``dev`` +install it using Symfony Flex, the profiler is enabled in the ``dev`` and ``test`` environments. .. note:: @@ -614,11 +610,10 @@ collect **type**: ``boolean`` **default**: ``true`` -This option configures the way the profiler behaves when it is enabled. -If set to ``true``, the profiler collects data for all requests (unless -you configure otherwise, like a custom `matcher`_). If you want to only -collect information on-demand, you can set the ``collect`` flag to ``false`` -and activate the data collectors manually:: +This option configures the way the profiler behaves when it is enabled. If set +to ``true``, the profiler collects data for all requests. If you want to only +collect information on-demand, you can set the ``collect`` flag to ``false`` and +activate the data collectors manually:: $profiler->enable(); @@ -645,49 +640,6 @@ dsn The DSN where to store the profiling information. -.. seealso:: - - See :doc:`/profiler/storage` for more information about the - profiler storage. - -matcher -....... - -.. caution:: - - This option is deprecated since Symfony 3.4 and will be removed in 4.0. - -Matcher options are configured to dynamically enable the profiler. For -instance, based on the `ip`_ or :ref:`path `. - -.. seealso:: - - See :doc:`/profiler/matchers` for more information about using - matchers to enable/disable the profiler. - -ip -"" - -**type**: ``string`` - -If set, the profiler will only be enabled when the current IP address matches. - -.. _reference-profiler-matcher-path: - -path -"""" - -**type**: ``string`` - -If set, the profiler will only be enabled when the current path matches. - -service -""""""" - -**type**: ``string`` - -This setting contains the service id of a custom matcher. - request ~~~~~~~ @@ -712,7 +664,7 @@ To configure a ``jsonp`` format: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: request: formats: @@ -720,7 +672,7 @@ To configure a ``jsonp`` format: .. code-block:: xml - + + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -742,7 +694,7 @@ To configure a ``jsonp`` format: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ 'request' => [ 'formats' => [ @@ -771,7 +723,7 @@ type The type of the resource to hint the loaders about the format. This isn't needed when you use the default routers with the expected file extensions -(``.xml``, ``.yml`` / ``.yaml``, ``.php``). +(``.xml``, ``.yaml``, ``.php``). http_port ......... @@ -810,6 +762,19 @@ The value can be one of: ``true`` is recommended in the development environment, while ``false`` or ``null`` might be preferred in production. +utf8 +.... + +**type**: ``boolean`` **default**: ``false`` + +When this option is set to ``true``, route patterns can include UTF-8 characters. +If the charset of your application is UTF-8 (as defined in the +:ref:`getCharset() method ` of your kernel) it's +recommended to set it to ``true``. This will make non-UTF8 URLs to generate 404 +errors. + +.. _config-framework-session: + session ~~~~~~~ @@ -825,18 +790,15 @@ alias will be set to this service id. This class has to implement handler_id .......... -**type**: ``string`` **default**: ``'session.handler.native_file'`` - -The service id used for session storage. The ``session.handler`` service -alias will be set to this service id. - -You can also set it to ``null``, to default to the handler of your PHP -installation. +**type**: ``string`` **default**: ``null`` -.. seealso:: +The service id used for session storage. The default ``null`` value means to use +the native PHP session mechanism. Set it to ``'session.handler.native_file'`` to +let Symfony manage the sessions itself using files to store the session +metadata. - You can see an example of the usage of this in - :doc:`/doctrine/pdo_session_storage`. +If you prefer to make Symfony store sessions in a database read +:doc:`/doctrine/pdo_session_storage`. .. _name: @@ -876,12 +838,45 @@ This determines the domain to set in the session cookie. By default it's blank, meaning the host name of the server which generated the cookie according to the cookie specification. +cookie_samesite +............... + +**type**: ``string`` or ``null`` **default**: ``null`` + +It controls the way cookies are sent when the HTTP request was not originated +from the same domain the cookies are associated to. Setting this option is +recommended to mitigate `CSRF security attacks`_. + +By default, browsers send all cookies related to the domain of the HTTP request. +This may be a problem for example when you visit a forum and some malicious +comment includes a link like ``https://some-bank.com/?send_money_to=attacker&amount=1000``. +If you were previously logged into your bank website, the browser will send all +those cookies when making that HTTP request. + +The possible values for this option are: + +* ``null``, use it to disable this protection. Same behavior as in older Symfony + versions. +* ``'strict'`` (or the ``Cookie::SAMESITE_STRICT`` constant), use it to never + send any cookie when the HTTP request is not originated from the same domain. +* ``'lax'`` (or the ``Cookie::SAMESITE_LAX`` constant), use it to allow sending + cookies when the request originated from a different domain, but only when the + user consciously made the request (by clicking a link or submitting a form + with the ``GET`` method). + +.. note:: + + This option is available starting from PHP 7.3, but Symfony has a polyfill + so you can use it with any older PHP version as well. + cookie_secure ............. -**type**: ``boolean`` **default**: ``false`` +**type**: ``boolean`` or ``string`` **default**: ``'auto'`` -This determines whether cookies should only be sent over secure connections. +This determines whether cookies should only be sent over secure connections. The +default value is ``auto``, which means ``true`` for HTTPS requests and ``false`` +for HTTP requests. cookie_httponly ............... @@ -926,7 +921,6 @@ save_path This determines the argument to be passed to the save handler. If you choose the default file handler, this is the path where the session files are created. -For more information, see :doc:`/session/sessions_directory`. You can also set this value to the ``save_path`` of your ``php.ini`` by setting the value to ``null``: @@ -935,21 +929,21 @@ setting the value to ``null``: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: session: save_path: ~ .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -958,7 +952,7 @@ setting the value to ``null``: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ 'session' => [ 'save_path' => null, @@ -992,21 +986,21 @@ Whether to enable the session support in the framework. .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: session: enabled: true .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -1015,7 +1009,7 @@ Whether to enable the session support in the framework. .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ 'session' => [ 'enabled' => true, @@ -1047,7 +1041,7 @@ This option allows you to define a base path to be used for assets: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: # ... assets: @@ -1055,14 +1049,14 @@ This option allows you to define a base path to be used for assets: .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -1071,7 +1065,7 @@ This option allows you to define a base path to be used for assets: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ // ... 'assets' => [ @@ -1095,7 +1089,7 @@ collection each time it generates an asset's path: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: # ... assets: @@ -1104,14 +1098,14 @@ collection each time it generates an asset's path: .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -1120,7 +1114,7 @@ collection each time it generates an asset's path: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ // ... 'assets' => [ @@ -1139,7 +1133,7 @@ You can group assets into packages, to specify different base URLs for them: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: # ... assets: @@ -1149,14 +1143,14 @@ You can group assets into packages, to specify different base URLs for them: .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -1169,7 +1163,7 @@ You can group assets into packages, to specify different base URLs for them: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ // ... 'assets' => [ @@ -1222,7 +1216,7 @@ Now, activate the ``version`` option: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: # ... assets: @@ -1230,14 +1224,14 @@ Now, activate the ``version`` option: .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -1246,7 +1240,7 @@ Now, activate the ``version`` option: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ // ... 'assets' => [ @@ -1327,7 +1321,7 @@ individually for each asset package: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: assets: # this strategy is applied to every asset (including packages) @@ -1345,13 +1339,13 @@ individually for each asset package: .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -1373,7 +1367,7 @@ individually for each asset package: .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ 'assets' => [ 'version_strategy' => 'app.asset.my_versioning_strategy', @@ -1406,10 +1400,6 @@ json_manifest_path **type**: ``string`` **default**: ``null`` -.. versionadded:: 3.3 - - The ``json_manifest_path`` option was introduced in Symfony 3.3. - The file path to a ``manifest.json`` file containing an associative array of asset names and their respective compiled names. A common cache-busting technique using a "manifest" file works by writing out assets with a "hash" appended to their @@ -1429,36 +1419,36 @@ package: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: assets: # this manifest is applied to every asset (including packages) - json_manifest_path: "%kernel.project_dir%/web/assets/manifest.json" + json_manifest_path: "%kernel.project_dir%/public/build/manifest.json" packages: foo_package: # this package uses its own manifest (the default file is ignored) - json_manifest_path: "%kernel.project_dir%/web/assets/a_different_manifest.json" + json_manifest_path: "%kernel.project_dir%/public/build/a_different_manifest.json" bar_package: # this package uses the global manifest (the default file is used) base_path: '/images' .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + + json-manifest-path="%kernel.project_dir%/public/build/a_different_manifest.json"/> loadFromExtension('framework', [ 'assets' => [ // this manifest is applied to every asset (including packages) - 'json_manifest_path' => '%kernel.project_dir%/web/assets/manifest.json', + 'json_manifest_path' => '%kernel.project_dir%/public/build/manifest.json', 'packages' => [ 'foo_package' => [ // this package uses its own manifest (the default file is ignored) - 'json_manifest_path' => '%kernel.project_dir%/web/assets/a_different_manifest.json', + 'json_manifest_path' => '%kernel.project_dir%/public/build/a_different_manifest.json', ], 'bar_package' => [ // this package uses the global manifest (the default file is used) @@ -1524,57 +1514,50 @@ resources **type**: ``string[]`` **default**: ``['FrameworkBundle:Form']`` A list of all resources for form theming in PHP. This setting is not required -if you're using the Twig format for your templates, in that case refer to -:ref:`the form article `. +if you're :ref:`using the Twig format for your themes `. -Assume you have custom global form themes in -``src/WebsiteBundle/Resources/views/Form``, you can configure this like: +Assume you have custom global form themes in ``templates/form_themes/``, you can +configure this like: .. configuration-block:: .. code-block:: yaml - # app/config/config.yml + # config/packages/framework.yaml framework: templating: form: resources: - - 'WebsiteBundle:Form' + - 'form_themes' .. code-block:: xml - + + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - - - - WebsiteBundle:Form - + form_themes - - .. code-block:: php - // app/config/config.php + // config/packages/framework.php $container->loadFromExtension('framework', [ 'templating' => [ 'form' => [ 'resources' => [ - 'WebsiteBundle:Form', + 'form_themes', ], ], ], @@ -1632,7 +1615,7 @@ translator enabled ....... -**type**: ``boolean`` **default**: ``false`` +**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation Whether or not to enable the ``translator`` service in the service container. @@ -1677,10 +1660,6 @@ for translation files. default_path ............ -.. versionadded:: 3.4 - - The ``default_path`` option was introduced in Symfony 3.4. - **type**: ``string`` **default**: ``%kernel.project_dir%/translations`` This option allows to define the path where the application translations files @@ -1714,7 +1693,7 @@ property_info enabled ....... -**type**: ``boolean`` **default**: ``false`` +**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation validation ~~~~~~~~~~ @@ -1724,8 +1703,7 @@ validation enabled ....... -**type**: ``boolean`` **default**: ``true`` if :ref:`form support is enabled `, -``false`` otherwise +**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation Whether or not to enable validation support. @@ -1777,10 +1755,32 @@ strict_email **type**: ``Boolean`` **default**: ``false`` +.. deprecated:: 4.1 + + The ``strict_email`` option was deprecated in Symfony 4.1. Use the new + ``email_validation_mode`` option instead. + If this option is enabled, the `egulias/email-validator`_ library will be used by the :doc:`/reference/constraints/Email` constraint validator. Otherwise, the validator uses a simple regular expression to validate email addresses. +email_validation_mode +..................... + +**type**: ``string`` **default**: ``loose`` + +It controls the way email addresses are validated by the +:doc:`/reference/constraints/Email` validator. The possible values are: + +* ``loose``, it uses a simple regular expression to validate the address (it + checks that at least one ``@`` character is present, etc.). This validation is + too simple and it's recommended to use the ``html5`` validation instead; +* ``html5``, it validates email addresses using the same regular expression + defined in the HTML5 standard, making the backend validation consistent with + the one provided by browsers; +* ``strict``, it uses the `egulias/email-validator`_ library (which you must + install separately) to validate the addresses according to the `RFC 5322`_. + .. _reference-validation-mapping: mapping @@ -1844,24 +1844,10 @@ serializer enabled ....... -**type**: ``boolean`` **default**: ``false`` +**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation Whether to enable the ``serializer`` service or not in the service container. -.. _reference-serializer-cache: - -cache -..... - -**type**: ``string`` - -The service that is used to persist class metadata in a cache. The service -has to implement the ``Doctrine\Common\Cache\Cache`` interface. - -.. seealso:: - - For more information, see :ref:`serializer-enabling-metadata-cache`. - .. _reference-serializer-enable_annotations: enable_annotations @@ -1929,21 +1915,15 @@ php_errors log ... -.. versionadded:: 3.2 - - The ``log`` option was introduced in Symfony 3.2. - -**type**: ``boolean`` **default**: ``%kernel.debug%`` +**type**: ``boolean|int`` **default**: ``%kernel.debug%`` Use the application logger instead of the PHP logger for logging PHP errors. +When an integer value is used, it also sets the log level. Those integer +values must be the same used in the `error_reporting PHP option`_. throw ..... -.. versionadded:: 3.2 - - The ``throw`` option was introduced in Symfony 3.2. - **type**: ``boolean`` **default**: ``%kernel.debug%`` Throw PHP errors as ``\ErrorException`` instances. The parameter @@ -1964,7 +1944,11 @@ app The cache adapter used by the ``cache.app`` service. The FrameworkBundle ships with multiple adapters: ``cache.adapter.apcu``, ``cache.adapter.doctrine``, ``cache.adapter.system``, ``cache.adapter.filesystem``, ``cache.adapter.psr6``, -``cache.adapter.redis`` and ``cache.adapter.memcached``. +``cache.adapter.redis``, ``cache.adapter.memcached`` and ``cache.adapter.pdo``. + +There's also a special adapter called ``cache.adapter.array`` which stores +contents in memory using a PHP array and it's used to disable caching (mostly on +the ``dev`` environment). .. tip:: @@ -2018,15 +2002,19 @@ service. default_memcached_provider .......................... -.. versionadded:: 3.3 - - The ``default_memcached_provider`` option was introduced in Symfony 3.3. - **type**: ``string`` **default**: ``memcached://localhost`` The DSN to use by the Memcached provider. The provider is available as the ``cache.memcached`` service. +default_pdo_provider +.................... + +**type**: ``string`` **default**: ``doctrine.dbal.default_connection`` + +The service id of the database connection, which should be either a PDO or a +Doctrine DBAL instance. + pools ..... @@ -2061,7 +2049,7 @@ To configure a Redis cache pool with a default lifetime of 1 hour, do the follow xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd - http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -2123,6 +2111,14 @@ public Whether your service should be public or not. +tags +"""" + +**type**: ``boolean`` | ``string`` **default**: ``null`` + +Whether your service should be able to handle tags or not. +Can also be the service id of another cache pool where tags will be stored. + default_lifetime """""""""""""""" @@ -2154,10 +2150,6 @@ The cache clearer used to clear your PSR-6 cache. prefix_seed ........... -.. versionadded:: 3.2 - - The ``prefix_seed`` option was introduced in Symfony 3.2. - **type**: ``string`` **default**: ``null`` If defined, this value is used as part of the "namespace" generated for the @@ -2206,7 +2198,7 @@ A list of workflows to be created by the framework extension: xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd - http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> + http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -2313,8 +2305,11 @@ a :doc:`normal workflow ` or a :doc:`state machine `. -.. deprecated:: 3.3 - - The ``getRootDir()`` method is deprecated since Symfony 3.3. Use the new - ``getProjectDir()`` method instead. - -**type**: ``string`` **default**: the directory of ``AppKernel`` - -This returns the root directory of your kernel. If you use the Symfony Standard -edition, the root directory refers to the ``app`` directory. - -To change this setting, override the -:method:`Symfony\\Component\\HttpKernel\\Kernel::getRootDir` method:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - // ... - - public function getRootDir() - { - return realpath(parent::getRootDir().'/../'); - } - } +.. _configuration-kernel-project-directory: Project Directory ~~~~~~~~~~~~~~~~~ -.. versionadded:: 3.3 - - The ``getProjectDir()`` method was introduced in Symfony 3.3. - **type**: ``string`` **default**: the directory of the project ``composer.json`` -This returns the root directory of your Symfony project. It's calculated as -the directory where the main ``composer.json`` file is stored. +This returns the root directory of your Symfony project, which is used by +applications to perform operations with file paths relative to the project's +root directory. -If for some reason the ``composer.json`` file is not stored at the root of your -project, you can override the :method:`Symfony\\Component\\HttpKernel\\Kernel::getProjectDir` -method to return the right project directory:: - - // app/AppKernel.php +By default, its value is calculated automatically as the directory where the +main ``composer.json`` file is stored. If you don't use Composer, or have moved +the ``composer.json`` file location or have deleted it entirely (for example in +the production servers), you can override the +:method:`Symfony\\Component\\HttpKernel\\Kernel::getProjectDir` method to return +the right project directory:: + // src/Kernel.php + use Symfony\Component\HttpKernel\Kernel as BaseKernel; // ... - class AppKernel extends Kernel + + class Kernel extends BaseKernel { // ... - public function getProjectDir() + public function getProjectDir(): string { - return realpath(__DIR__.'/../'); + return \dirname(__DIR__); } } @@ -123,7 +105,7 @@ This returns the path to the cache directory. To change it, override the Log Directory ~~~~~~~~~~~~~ -**type**: ``string`` **default**: ``$this->rootDir/logs`` +**type**: ``string`` **default**: ``$this->rootDir/log`` This returns the path to the log directory. To change it, override the :method:`Symfony\\Component\\HttpKernel\\Kernel::getLogDir` method. Read diff --git a/reference/configuration/monolog.rst b/reference/configuration/monolog.rst index b231ae21bdd..cf6eb53e443 100644 --- a/reference/configuration/monolog.rst +++ b/reference/configuration/monolog.rst @@ -20,7 +20,7 @@ in your application configuration. When using XML, you must use the ``http://symfony.com/schema/dic/monolog`` namespace and the related XSD schema is available at: - ``http://symfony.com/schema/dic/monolog/monolog-1.0.xsd`` + ``https://symfony.com/schema/dic/monolog/monolog-1.0.xsd`` .. tip:: diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 671bc215e92..c7262db76d8 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -39,7 +39,6 @@ Some of these options define tens of sub-options and they are explained in separate articles: * `access_control`_ -* `acl`_ * `encoders`_ * `firewalls`_ * `providers`_ @@ -111,17 +110,6 @@ and to allow anonymous users to the login form page. This option is explained in detail in :doc:`/security/access_control`. -acl ---- - -This option is used to define `ACL (Access Control List)`_, which allow to -associate a list of permissions to an object. This option is deprecated since -Symfony 3.4 and will be removed in 4.0. - -Instead of using ACLs, Symfony recommends :doc:`security voters `, -which provide the same granular security access without the complication of ACLs. -If you still want to implement ACLs, check out the `Symfony ACL Bundle`_. - encoders -------- @@ -136,28 +124,35 @@ encoding algorithm. Also, each algorithm defines different config options: .. code-block:: yaml - # app/config/security.yml + # config/packages/security.yaml security: # ... encoders: # bcrypt encoder with default options - AppBundle\Entity\User: 'bcrypt' + App\Entity\User: 'bcrypt' # bcrypt encoder with custom options - AppBundle\Entity\User: + App\Entity\User: algorithm: 'bcrypt' cost: 15 # Argon2i encoder with default options - AppBundle\Entity\User: 'argon2i' + App\Entity\User: 'argon2i' + + # Argon2i encoder with custom options + App\Entity\User: + algorithm: 'argon2i' + memory_cost: 16384 # Amount in KiB. (16384 = 16 MiB) + time_cost: 2 # Number of iterations + threads: 4 # Number of parallel threads # PBKDF2 encoder using SHA512 hashing with default options - AppBundle\Entity\User: 'sha512' + App\Entity\User: 'sha512' .. code-block:: xml - + + + + + @@ -196,8 +203,8 @@ encoding algorithm. Also, each algorithm defines different config options: .. code-block:: php - // app/config/security.php - use AppBundle\Entity\User; + // config/packages/security.php + use App\Entity\User; $container->loadFromExtension('security', [ // ... @@ -218,6 +225,14 @@ encoding algorithm. Also, each algorithm defines different config options: 'algorithm' => 'argon2i', ], + // Argon2i encoder with custom options + User::class => [ + 'algorithm' => 'argon2i', + 'memory_cost' => 16384, // Amount in KiB. (16384 = 16 MiB) + 'time_cost' => 2, // Number of iterations + 'threads' => 4, // Number of parallel threads + ], + // PBKDF2 encoder using SHA512 hashing with default options User::class => [ 'algorithm' => 'sha512', @@ -293,7 +308,7 @@ application: .. code-block:: yaml - # app/config/security.yml + # config/packages/security.yaml security: # ... firewalls: @@ -307,7 +322,7 @@ application: .. code-block:: xml - + loadFromExtension('security', [ @@ -355,7 +370,7 @@ depend on the authentication mechanism, which can be any of these: .. code-block:: yaml - # app/config/security.yml + # config/packages/security.yaml security: # ... firewalls: @@ -403,8 +418,7 @@ is set to ``true``) when they try to access a protected resource but isn't fully authenticated. This path **must** be accessible by a normal, un-authenticated user, else -you may create a redirect loop. For details, see -":ref:`Avoid Common Pitfalls `". +you may create a redirect loop. check_path .......... @@ -512,15 +526,15 @@ the current firewall and not the other ones. logout_on_user_change ~~~~~~~~~~~~~~~~~~~~~ -**type**: ``boolean`` **default**: ``false`` +**type**: ``boolean`` **default**: ``true`` -.. versionadded:: 3.4 +.. deprecated:: 4.1 - The ``logout_on_user_change`` option was introduced in Symfony 3.4. + The ``logout_on_user_change`` option was deprecated in Symfony 4.1. -If ``true`` this option makes Symfony to trigger a logout when the user has -changed. Not doing that is deprecated, so this option should be set to ``true`` -to avoid getting deprecation messages. +If ``false`` this option makes Symfony to not trigger a logout when the user has +changed. Doing that is deprecated, so this option should set to ``true`` or +unset to avoid getting deprecation messages. The user is considered to have changed when the user class implements :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface` and the @@ -536,13 +550,38 @@ success_handler The service ID used for handling a successful logout. The service must implement :class:`Symfony\\Component\\Security\\Http\\Logout\\LogoutSuccessHandlerInterface`. +.. _reference-security-logout-csrf: + +csrf_parameter +~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``'_csrf_token'`` + +The name of the parameter that stores the CSRF token value. + +csrf_token_generator +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``null`` + +The ``id`` of the service used to generate the CSRF tokens. Symfony provides a +default service whose ID is ``security.csrf.token_manager``. + +csrf_token_id +~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``'logout'`` + +An arbitrary string used to generate the token value (and check its validity +afterwards). + .. _reference-security-ldap: LDAP Authentication ~~~~~~~~~~~~~~~~~~~ There are several options for connecting against an LDAP server, -using the ``form_login_ldap`` and ``http_basic_ldap`` authentication +using the ``form_login_ldap``, ``http_basic_ldap`` and ``json_login_ldap`` authentication providers or the ``ldap`` user provider. For even more details, see :doc:`/security/ldap`. @@ -550,8 +589,8 @@ For even more details, see :doc:`/security/ldap`. **Authentication** You can authenticate to an LDAP server using the LDAP variants of the -``form_login`` and ``http_basic`` authentication providers. Simply use -``form_login_ldap`` and ``http_basic_ldap``, which will attempt to +``form_login``, ``http_basic`` and ``json_login`` authentication providers. Simply use +``form_login_ldap``, ``http_basic_ldap`` and ``json_login_ldap``, which will attempt to ``bind`` against a LDAP server instead of using password comparison. Both authentication providers have the same arguments as their normal @@ -587,82 +626,10 @@ statically using the ``dn_string`` config option. **User provider** -Users will still be fetched from the configured user provider. If you -wish to fetch your users from a LDAP server, you will need to use the -``ldap`` user provider, in addition to one of the two authentication -providers (``form_login_ldap`` or ``http_basic_ldap``). - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - - providers: - my_ldap_users: - ldap: - service: ldap - base_dn: 'dc=symfony,dc=com' - search_dn: '%ldap.search_dn%' - search_password: '%ldap.search_password%' - default_roles: '' - uid_key: 'uid' - filter: '(&({uid_key}={username})(objectclass=person)(ou=Users))' - -HTTP-Digest Authentication -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 3.4 - - HTTP-Digest Authentication was deprecated in Symfony 3.4 and it will be - removed in Symfony 4.0. - -To use HTTP-Digest authentication you need to provide a realm and a secret: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - somename: - http_digest: - secret: '%secret%' - realm: 'secure-api' - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', [ - 'firewalls' => [ - 'somename' => [ - 'http_digest' => [ - 'secret' => '%secret%', - 'realm' => 'secure-api', - ], - ], - ], - ]); +Users will still be fetched from the configured user provider. If you wish to +fetch your users from a LDAP server, you will need to use the +:doc:`LDAP User Provider ` and any of these authentication +providers: ``form_login_ldap`` or ``http_basic_ldap`` or ``json_login_ldap``. .. _reference-security-firewall-context: @@ -684,7 +651,7 @@ multiple firewalls, the "context" could actually be shared: .. code-block:: yaml - # app/config/security.yml + # config/packages/security.yaml security: # ... @@ -698,7 +665,7 @@ multiple firewalls, the "context" could actually be shared: .. code-block:: xml - + loadFromExtension('security', [ 'firewalls' => [ 'somename' => [ @@ -755,10 +722,10 @@ This options defines how the application users are loaded (from a database, a LDAP server, a configuration file, etc.) Read the following articles to learn more about each of those providers: -* :doc:`Load users from a database ` -* :doc:`Load users from a LDAP server ` -* :ref:`Load users from a configuration file ` -* :doc:`Create your own user provider ` +* :ref:`Load users from a database ` +* :ref:`Load users from a LDAP server ` +* :ref:`Load users from a configuration file ` +* :ref:`Create your own user provider ` role_hierarchy -------------- @@ -771,8 +738,6 @@ role inheritance rules by creating a role hierarchy, as explained in .. _`ircmaxell/password-compat`: https://packagist.org/packages/ircmaxell/password-compat .. _`libsodium`: https://pecl.php.net/package/libsodium .. _`Session Fixation`: https://www.owasp.org/index.php/Session_fixation -.. _`ACL (Access Control List)`: https://en.wikipedia.org/wiki/Access_control_list -.. _`Symfony ACL Bundle`: https://github.com/symfony/acl-bundle .. _`Argon2 key derivation function`: https://en.wikipedia.org/wiki/Argon2 .. _`bcrypt password hashing function`: https://en.wikipedia.org/wiki/Bcrypt .. _`cryptographic salt`: https://en.wikipedia.org/wiki/Salt_(cryptography) diff --git a/reference/configuration/swiftmailer.rst b/reference/configuration/swiftmailer.rst index 6af38c14f99..7ed83e9133c 100644 --- a/reference/configuration/swiftmailer.rst +++ b/reference/configuration/swiftmailer.rst @@ -72,7 +72,7 @@ transport The exact transport method to use to deliver emails. Valid values are: * smtp -* gmail (see :doc:`/email/gmail`) +* gmail (see :ref:`email-using-gmail`) * mail (deprecated in SwiftMailer since version 5.4.5) * sendmail * null (same as setting `disable_delivery`_ to ``true``) @@ -215,10 +215,11 @@ delivery_addresses In previous versions, this option was called ``delivery_address``. -If set, all email messages will be sent to these addresses instead of being -sent to their actual recipients. This is often useful when developing. For -example, by setting this in the ``config_dev.yml`` file, you can guarantee -that all emails sent during development go to one or more some specific accounts. +If set, all email messages will be sent to these addresses instead of being sent +to their actual recipients. This is often useful when developing. For example, +by setting this in the ``config/packages/dev/swiftmailer.yaml`` file, you can +guarantee that all emails sent during development go to one or more some +specific accounts. This uses ``Swift_Plugins_RedirectingPlugin``. Original recipients are available on the ``X-Swift-To``, ``X-Swift-Cc`` and ``X-Swift-Bcc`` headers. @@ -252,11 +253,10 @@ the information will be available in the profiler. .. tip:: - The following options can be set via environment variables using the - ``%env()%`` syntax: ``url``, ``transport``, ``username``, ``password``, - ``host``, ``port``, ``timeout``, ``source_ip``, ``local_domain``, - ``encryption``, ``auth_mode``. - For details, see the :doc:`/configuration/external_parameters` article. + The following options can be set via environment variables: ``url``, + ``transport``, ``username``, ``password``, ``host``, ``port``, ``timeout``, + ``source_ip``, ``local_domain``, ``encryption``, ``auth_mode``. For details, + see: :doc:`/configuration/environment_variables`. Using Multiple Mailers ---------------------- @@ -307,7 +307,7 @@ key (the default mailer is identified by the ``default_mailer`` option): ], ]); -Each mailer is registered as a service:: +Each mailer is registered automatically as a service with these IDs:: // ... @@ -325,3 +325,70 @@ Each mailer is registered as a service:: When configuring multiple mailers, options must be placed under the appropriate mailer key of the configuration instead of directly under the ``swiftmailer`` key. + +When using :ref:`autowiring ` only the default mailer is +injected when type-hinting some argument with the ``\Swift_Mailer`` class. If +you need to inject a different mailer in some service, use any of these +alternatives based on the :ref:`service binding ` feature: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + _defaults: + bind: + # this injects the second mailer when type-hinting constructor arguments with \Swift_Mailer + \Swift_Mailer: '@swiftmailer.mailer.second_mailer' + # this injects the second mailer when a service constructor argument is called $specialMailer + $specialMailer: '@swiftmailer.mailer.second_mailer' + + App\Some\Service: + # this injects the second mailer only for this argument of this service + $differentMailer: '@swiftmailer.mailer.second_mailer' + + # ... + + .. code-block:: xml + + + + + + + + + @swiftmailer.mailer.second_mailer + + @swiftmailer.mailer.second_mailer + + + + + @swiftmailer.mailer.second_mailer + + + + + + + .. code-block:: php + + // config/services.php + use App\Some\Service; + use Symfony\Component\DependencyInjection\Reference; + use Psr\Log\LoggerInterface; + + $container->register(Service::class) + ->setPublic(true) + ->setBindings([ + // this injects the second mailer when this service type-hints constructor arguments with \Swift_Mailer + \Swift_Mailer => '@swiftmailer.mailer.second_mailer', + // this injects the second mailer when this service has a constructor argument called $specialMailer + '$specialMailer' => '@swiftmailer.mailer.second_mailer', + ]) + ; diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index d37ee5f10ce..04dbb249bf0 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -42,6 +42,7 @@ Configuration * `debug`_ * `exception_controller`_ +* `form_themes`_ * `number_format`_ * `decimals`_ @@ -137,8 +138,9 @@ charset **type**: ``string`` **default**: ``'%kernel.charset%'`` -The charset used by the template files. In the Symfony Standard edition this -defaults to the ``UTF-8`` charset. +The charset used by the template files. By default it's the same as the value of +the ``kernel.charset`` container parameter, which is ``UTF-8`` by default in +Symfony applications. date ~~~~ @@ -195,6 +197,62 @@ option is advanced. If you need to customize an error page you should use the previous link. If you need to perform some behavior on an exception, you should add a listener to the ``kernel.exception`` event (see :ref:`dic-tags-kernel-event-listener`). +.. _config-twig-form-themes: + +form_themes +~~~~~~~~~~~ + +**type**: ``array`` of ``string`` **default**: ``['form_div_layout.html.twig']`` + +Defines one or more :doc:`form themes ` which are applied to +all the forms of the application: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/twig.yaml + twig: + form_themes: ['bootstrap_4_layout.html.twig', 'form/my_theme.html.twig'] + # ... + + .. code-block:: xml + + + + + + + bootstrap_4_layout.html.twig + form/my_theme.html.twig + + + + + .. code-block:: php + + // config/packages/twig.php + $container->loadFromExtension('twig', [ + 'form_themes' => [ + 'bootstrap_4_layout.html.twig', + 'form/my_theme.html.twig', + ], + // ... + ]); + +The order in which themes are defined is important because each theme overrides +all the previous one. When rendering a form field whose block is not defined in +the form theme, Symfony falls back to the previous themes until the first one. + +These global themes are applied to all forms, even those which use the +:ref:`form_theme Twig tag `, but you can +:ref:`disable global themes for specific forms `. + number_format ~~~~~~~~~~~~~ @@ -248,10 +306,6 @@ default_path **type**: ``string`` **default**: ``'%kernel.project_dir%/templates'`` -.. versionadded:: 3.4 - - The ``default_path`` option was introduced in Symfony 3.4. - The default directory where Symfony will look for Twig templates. .. _config-twig-paths: @@ -261,6 +315,12 @@ paths **type**: ``array`` **default**: ``null`` +.. deprecated:: 4.2 + + Using the ``src/Resources/views/`` directory to store templates was + deprecated in Symfony 4.2. Use instead the directory defined in the + ``default_path`` option (which is ``templates/`` by default). + This option defines the directories where Symfony will look for Twig templates in addition to the default locations. Symfony looks for the templates in the following order: @@ -277,7 +337,7 @@ The values of the ``paths`` option are defined as ``key: value`` pairs where the .. code-block:: yaml - # app/config/config.yml + # config/packages/twig.yaml twig: # ... paths: @@ -285,7 +345,7 @@ The values of the ``paths`` option are defined as ``key: value`` pairs where the .. code-block:: xml - + loadFromExtension('twig', [ // ... 'paths' => [ @@ -321,7 +381,7 @@ for that directory: .. code-block:: yaml - # app/config/config.yml + # config/packages/twig.yaml twig: # ... paths: @@ -329,7 +389,7 @@ for that directory: .. code-block:: xml - + loadFromExtension('twig', [ // ... 'paths' => [ diff --git a/reference/configuration/web_profiler.rst b/reference/configuration/web_profiler.rst index 3eb59a965bf..313a82e5c7c 100644 --- a/reference/configuration/web_profiler.rst +++ b/reference/configuration/web_profiler.rst @@ -4,10 +4,10 @@ Profiler Configuration Reference (WebProfilerBundle) ==================================================== -The WebProfilerBundle provides detailed technical information about each request -execution and displays it in both the web debug toolbar and the -:doc:`profiler `. All these options are configured under the -``web_profiler`` key in your application configuration. +The WebProfilerBundle is a **development tool** that provides detailed technical +information about each request execution and displays it in both the web debug +toolbar and the :doc:`profiler `. All these options are configured +under the ``web_profiler`` key in your application configuration. .. code-block:: terminal @@ -21,7 +21,7 @@ execution and displays it in both the web debug toolbar and the When using XML, you must use the ``http://symfony.com/schema/dic/webprofiler`` namespace and the related XSD schema is available at: - ``http://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd`` + ``https://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd`` .. caution:: @@ -34,31 +34,19 @@ Configuration * `excluded_ajax_paths`_ * `intercept_redirects`_ -* `position`_ * `toolbar`_ -* `verbose`_ -toolbar -~~~~~~~ - -**type**: ``boolean`` **default**: ``false`` - -It enables and disables the toolbar entirely. Usually you set this to ``true`` -in the ``dev`` and ``test`` environments and to ``false`` in the ``prod`` -environment. - -position -~~~~~~~~ - -.. deprecated:: 3.4 +excluded_ajax_paths +~~~~~~~~~~~~~~~~~~~ - The ``position`` option was deprecated in Symfony 3.4 and it will be removed - in Symfony 4.0, where the toolbar is always displayed in the ``bottom`` position. +**type**: ``string`` **default**: ``'^/((index|app(_[\w]+)?)\.php/)?_wdt'`` -**type**: ``string`` **default**: ``bottom`` +When the toolbar logs Ajax requests, it matches their URLs against this regular +expression. If the URL matches, the request is not displayed in the toolbar. This +is useful when the application makes lots of Ajax requests or they are heavy and +you want to exclude some of them. -It defines the location of the browser window where the toolbar is displayed. -the only allowed values are ``bottom`` and ``top``. +.. _intercept_redirects: intercept_redirects ~~~~~~~~~~~~~~~~~~~ @@ -74,20 +62,11 @@ redirection and shows you the URL which is going to redirect to, its toolbar, and its profiler. Once you've inspected the toolbar/profiler data, you can click on the given link to perform the redirect. -excluded_ajax_paths -~~~~~~~~~~~~~~~~~~~ - -**type**: ``string`` **default**: ``'^/(app(_[\\w]+)?\\.php/)?_wdt'`` - -When the toolbar logs Ajax requests, it matches their URLs against this regular -expression. If the URL matches, the request is not displayed in the toolbar. This -is useful when the application makes lots of Ajax requests or they are heavy and -you want to exclude some of them. - -verbose +toolbar ~~~~~~~ -**type**: ``boolean`` **default**: ``true`` +**type**: ``boolean`` **default**: ``false`` -This option is **deprecated** and has no effect on the toolbar or the profiler, -so you can safely remove it from your configuration. +It enables and disables the toolbar entirely. Usually you set this to ``true`` +in the ``dev`` and ``test`` environments and to ``false`` in the ``prod`` +environment. diff --git a/reference/constraints.rst b/reference/constraints.rst index ef3b3db5876..b64c8fbdbaa 100644 --- a/reference/constraints.rst +++ b/reference/constraints.rst @@ -29,6 +29,7 @@ Validation Constraints Reference constraints/GreaterThan constraints/GreaterThanOrEqual constraints/Range + constraints/DivisibleBy constraints/Date constraints/DateTime diff --git a/reference/constraints/All.rst b/reference/constraints/All.rst index cef70cc3690..351617300c0 100644 --- a/reference/constraints/All.rst +++ b/reference/constraints/All.rst @@ -4,17 +4,14 @@ All When applied to an array (or Traversable object), this constraint allows you to apply a collection of constraints to each element of the array. -+----------------+-------------------------------------------------------------------+ -| Applies to | :ref:`property or method ` | -+----------------+-------------------------------------------------------------------+ -| Options | - `constraints`_ | -| | - `groups`_ | -| | - `payload`_ | -+----------------+-------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Validator\\Constraints\\All` | -+----------------+-------------------------------------------------------------------+ -| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\AllValidator` | -+----------------+-------------------------------------------------------------------+ +========== =================================================================== +Applies to :ref:`property or method ` +Options - `constraints`_ + - `groups`_ + - `payload`_ +Class :class:`Symfony\\Component\\Validator\\Constraints\\All` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\AllValidator` +========== =================================================================== Basic Usage ----------- @@ -26,8 +23,8 @@ entry in that array: .. code-block:: php-annotations - // src/AppBundle/Entity/User.php - namespace AppBundle\Entity; + // src/Entity/User.php + namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; @@ -44,8 +41,8 @@ entry in that array: .. code-block:: yaml - # src/AppBundle/Resources/config/validation.yml - AppBundle\Entity\User: + # config/validator/validation.yaml + App\Entity\User: properties: favoriteColors: - All: @@ -55,13 +52,13 @@ entry in that array: .. code-block:: xml - + - + .. code-block:: php - use AppBundle\Lock\MysqlLock; - use AppBundle\Lock\PostgresqlLock; - use AppBundle\Lock\SqliteLock; + use App\Lock\MysqlLock; + use App\Lock\PostgresqlLock; + use App\Lock\SqliteLock; $container->register('app.mysql_lock', MysqlLock::class)->setPublic(false); $container->register('app.postgresql_lock', PostgresqlLock::class)->setPublic(false); @@ -313,11 +131,11 @@ the generic ``app.lock`` service can be defined as follows: + class="App\Lock\MysqlLock"/> + class="App\Lock\PostgresqlLock"/> + class="App\Lock\SqliteLock"/> @@ -327,9 +145,9 @@ the generic ``app.lock`` service can be defined as follows: .. code-block:: php - use AppBundle\Lock\MysqlLock; - use AppBundle\Lock\PostgresqlLock; - use AppBundle\Lock\SqliteLock; + use App\Lock\MysqlLock; + use App\Lock\PostgresqlLock; + use App\Lock\SqliteLock; $container->register('app.mysql_lock', MysqlLock::class)->setPublic(false); $container->register('app.postgresql_lock', PostgresqlLock::class)->setPublic(false); @@ -444,8 +262,8 @@ files during the cache clearing process. In order to register your custom cache clearer, first you must create a service class:: - // src/AppBundle/Cache/MyClearer.php - namespace AppBundle\Cache; + // src/Cache/MyClearer.php + namespace App\Cache; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; @@ -457,7 +275,7 @@ service class:: } } -If you're using the :ref:`default services.yml configuration `, +If you're using the :ref:`default services.yaml configuration `, your service will be automatically tagged with ``kernel.cache_clearer``. But, you can also register it manually: @@ -466,7 +284,7 @@ can also register it manually: .. code-block:: yaml services: - AppBundle\Cache\MyClearer: + App\Cache\MyClearer: tags: [kernel.cache_clearer] .. code-block:: xml @@ -478,7 +296,7 @@ can also register it manually: https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -486,7 +304,7 @@ can also register it manually: .. code-block:: php - use AppBundle\Cache\MyClearer; + use App\Cache\MyClearer; $container ->register(MyClearer::class) @@ -510,8 +328,8 @@ is generated dynamically. To register your own cache warmer, first create a service that implements the :class:`Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface` interface:: - // src/Acme/MainBundle/Cache/MyCustomWarmer.php - namespace AppBundle\Cache; + // src/Cache/MyCustomWarmer.php + namespace App\Cache; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; @@ -533,7 +351,7 @@ application without calling this cache warmer. In Symfony, optional warmers are always executed by default (you can change this by using the ``--no-optional-warmers`` option when executing the command). -If you're using the :ref:`default services.yml configuration `, +If you're using the :ref:`default services.yaml configuration `, your service will be automatically tagged with ``kernel.cache_warmer``. But, you can also register it manually: @@ -542,7 +360,7 @@ can also register it manually: .. code-block:: yaml services: - AppBundle\Cache\MyCustomWarmer: + App\Cache\MyCustomWarmer: tags: - { name: kernel.cache_warmer, priority: 0 } @@ -555,7 +373,7 @@ can also register it manually: https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -563,7 +381,7 @@ can also register it manually: .. code-block:: php - use AppBundle\Cache\MyCustomWarmer; + use App\Cache\MyCustomWarmer; $container ->register(MyCustomWarmer::class) @@ -664,7 +482,7 @@ channel when injecting the logger in a service. .. code-block:: yaml services: - AppBundle\Log\CustomLogger: + App\Log\CustomLogger: arguments: ['@logger'] tags: - { name: monolog.logger, channel: app } @@ -678,7 +496,7 @@ channel when injecting the logger in a service. https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -687,7 +505,7 @@ channel when injecting the logger in a service. .. code-block:: php - use AppBundle\Log\CustomLogger; + use App\Log\CustomLogger; use Symfony\Component\DependencyInjection\Reference; $container->register(CustomLogger::class) @@ -845,7 +663,7 @@ of your configuration and tag it with ``routing.loader``: .. code-block:: yaml services: - AppBundle\Routing\CustomLoader: + App\Routing\CustomLoader: tags: [routing.loader] .. code-block:: xml @@ -857,7 +675,7 @@ of your configuration and tag it with ``routing.loader``: https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -865,7 +683,7 @@ of your configuration and tag it with ``routing.loader``: .. code-block:: php - use AppBundle\Routing\CustomLoader; + use App\Routing\CustomLoader; $container ->register(CustomLoader::class) @@ -986,7 +804,7 @@ templates): .. code-block:: yaml services: - AppBundle\Templating\AppHelper: + App\Templating\AppHelper: tags: - { name: templating.helper, alias: alias_name } @@ -999,7 +817,7 @@ templates): https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -1007,7 +825,7 @@ templates): .. code-block:: php - use AppBundle\Templating\AppHelper; + use App\Templating\AppHelper; $container->register(AppHelper::class) ->addTag('templating.helper', ['alias' => 'alias_name']) @@ -1035,7 +853,7 @@ Now, register your loader as a service and tag it with ``translation.loader``: .. code-block:: yaml services: - AppBundle\Translation\MyCustomLoader: + App\Translation\MyCustomLoader: tags: - { name: translation.loader, alias: bin } @@ -1048,7 +866,7 @@ Now, register your loader as a service and tag it with ``translation.loader``: https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -1056,7 +874,7 @@ Now, register your loader as a service and tag it with ``translation.loader``: .. code-block:: php - use AppBundle\Translation\MyCustomLoader; + use App\Translation\MyCustomLoader; $container ->register(MyCustomLoader::class) @@ -1067,8 +885,7 @@ The ``alias`` option is required and very important: it defines the file "suffix" that will be used for the resource files that use this loader. For example, suppose you have some custom ``bin`` format that you need to load. If you have a ``bin`` file that contains French translations for -the ``messages`` domain, then you might have a file -``app/Resources/translations/messages.fr.bin``. +the ``messages`` domain, then you might have a file ``translations/messages.fr.bin``. When Symfony tries to load the ``bin`` file, it passes the path to your custom loader as the ``$resource`` argument. You can then perform any logic @@ -1151,7 +968,7 @@ required option: ``alias``, which defines the name of the extractor:: .. code-block:: php - use AppBundle\Translation\CustomExtractor; + use App\Translation\CustomExtractor; $container->register(CustomExtractor::class) ->addTag('translation.extractor', ['alias' => 'foo']); @@ -1187,7 +1004,7 @@ This is the name that's used to determine which dumper should be used. .. code-block:: yaml services: - AppBundle\Translation\JsonFileDumper: + App\Translation\JsonFileDumper: tags: - { name: translation.dumper, alias: json } @@ -1200,7 +1017,7 @@ This is the name that's used to determine which dumper should be used. https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -1208,7 +1025,7 @@ This is the name that's used to determine which dumper should be used. .. code-block:: php - use AppBundle\Translation\JsonFileDumper; + use App\Translation\JsonFileDumper; $container->register(JsonFileDumper::class) ->addTag('translation.dumper', ['alias' => 'json']); @@ -1227,7 +1044,7 @@ twig.extension To enable a Twig extension, add it as a regular service in one of your configuration and tag it with ``twig.extension``. If you're using the -:ref:`default services.yml configuration `, +:ref:`default services.yaml configuration `, the service is auto-registered and auto-tagged. But, you can also register it manually: .. configuration-block:: @@ -1235,9 +1052,15 @@ the service is auto-registered and auto-tagged. But, you can also register it ma .. code-block:: yaml services: - AppBundle\Twig\AppExtension: + App\Twig\AppExtension: tags: [twig.extension] + # optionally you can define the priority of the extension (default = 0). + # Extensions with higher priorities are registered earlier. This is mostly + # useful to register late extensions that override other extensions. + App\Twig\AnotherExtension: + tags: [{ name: twig.extension, priority: -100 }] + .. code-block:: xml @@ -1247,20 +1070,29 @@ the service is auto-registered and auto-tagged. But, you can also register it ma https://symfony.com/schema/dic/services/services-1.0.xsd"> - + + + + + .. code-block:: php - use AppBundle\Twig\AppExtension; + use App\Twig\AppExtension; + use App\Twig\AnotherExtension; $container ->register(AppExtension::class) ->addTag('twig.extension') ; + $container + ->register(AnotherExtension::class) + ->addTag('twig.extension', ['priority' => -100]) + ; For information on how to create the actual Twig Extension class, see `Twig's documentation`_ on the topic or read the @@ -1312,7 +1144,7 @@ By default, Symfony uses only one `Twig Loader`_ - to load Twig templates from another resource, you can create a service for the new loader and tag it with ``twig.loader``. -If you use the :ref:`default services.yml configuration `, +If you use the :ref:`default services.yaml configuration `, the service will be automatically tagged thanks to autoconfiguration. But, you can also register it manually: @@ -1321,7 +1153,7 @@ also register it manually: .. code-block:: yaml services: - AppBundle\Twig\CustomLoader: + App\Twig\CustomLoader: tags: - { name: twig.loader, priority: 0 } @@ -1334,7 +1166,7 @@ also register it manually: https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -1342,7 +1174,7 @@ also register it manually: .. code-block:: php - use AppBundle\Twig\CustomLoader; + use App\Twig\CustomLoader; $container ->register(CustomLoader::class) @@ -1354,6 +1186,51 @@ also register it manually: The ``priority`` is optional and its value is a positive or negative integer that defaults to ``0``. Loaders with higher numbers are tried first. +.. _reference-dic-tags-twig-runtime: + +twig.runtime +------------ + +**Purpose**: To register a custom Lazy-Loaded Twig Extension + +:ref:`Lazy-Loaded Twig Extensions ` are defined as +regular services but the need to be tagged with ``twig.runtime``. If you're using the +:ref:`default services.yaml configuration `, +the service is auto-registered and auto-tagged. But, you can also register it manually: + +.. configuration-block:: + + .. code-block:: yaml + + services: + App\Twig\AppExtension: + tags: [twig.runtime] + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + use App\Twig\AppExtension; + use App\Twig\AnotherExtension; + + $container + ->register(AppExtension::class) + ->addTag('twig.runtime') + ; + validator.constraint_validator ------------------------------ diff --git a/reference/forms/twig_reference.rst b/reference/forms/twig_reference.rst deleted file mode 100644 index 408eea77f70..00000000000 --- a/reference/forms/twig_reference.rst +++ /dev/null @@ -1,384 +0,0 @@ -.. index:: - single: Forms; Twig form function reference - -Twig Template Form Function and Variable Reference -================================================== - -When working with forms in a template, there are two powerful things at -your disposal: - -* :ref:`Functions ` for rendering each part - of a form; -* :ref:`Variables ` for getting *any* information - about any field. - -You'll use functions often to render your fields. Variables, on the other -hand, are less commonly-used, but infinitely powerful since you can access -a fields label, id attribute, errors and anything else about the field. - -.. _reference-form-twig-functions: - -Form Rendering Functions ------------------------- - -This reference manual covers all the possible Twig functions available for -rendering forms. There are several different functions available and -each is responsible for rendering a different part of a form (e.g. labels, -errors, widgets, etc). - -.. _reference-forms-twig-form: - -form(view, variables) ---------------------- - -Renders the HTML of a complete form. - -.. code-block:: twig - - {# render the form and change the submission method #} - {{ form(form, {'method': 'GET'}) }} - -You will mostly use this helper for prototyping or if you use custom form -themes. If you need more flexibility in rendering the form, you should use -the other helpers to render individual parts of the form instead: - -.. code-block:: twig - - {{ form_start(form) }} - {{ form_errors(form) }} - - {{ form_row(form.name) }} - {{ form_row(form.dueDate) }} - - {{ form_row(form.submit, { 'label': 'Submit me' }) }} - {{ form_end(form) }} - -.. _reference-forms-twig-start: - -form_start(view, variables) ---------------------------- - -Renders the start tag of a form. This helper takes care of printing the -configured method and target action of the form. It will also include the -correct ``enctype`` property if the form contains upload fields. - -.. code-block:: twig - - {# render the start tag and change the submission method #} - {{ form_start(form, {'method': 'GET'}) }} - -.. _reference-forms-twig-end: - -form_end(view, variables) -------------------------- - -Renders the end tag of a form. - -.. code-block:: twig - - {{ form_end(form) }} - -This helper also outputs ``form_rest()`` unless you set ``render_rest`` -to false: - -.. code-block:: twig - - {# don't render unrendered fields #} - {{ form_end(form, {'render_rest': false}) }} - -.. _reference-forms-twig-label: - -form_label(view, label, variables) ----------------------------------- - -Renders the label for the given field. You can optionally pass the specific -label you want to display as the second argument. - -.. code-block:: twig - - {{ form_label(form.name) }} - - {# The two following syntaxes are equivalent #} - {{ form_label(form.name, 'Your Name', {'label_attr': {'class': 'foo'}}) }} - - {{ form_label(form.name, null, { - 'label': 'Your name', - 'label_attr': {'class': 'foo'} - }) }} - -See ":ref:`twig-reference-form-variables`" to learn about the ``variables`` -argument. - -.. _reference-forms-twig-errors: - -form_errors(view) ------------------ - -Renders any errors for the given field. - -.. code-block:: twig - - {{ form_errors(form.name) }} - - {# render any "global" errors #} - {{ form_errors(form) }} - -.. _reference-forms-twig-widget: - -form_widget(view, variables) ----------------------------- - -Renders the HTML widget of a given field. If you apply this to an entire -form or collection of fields, each underlying form row will be rendered. - -.. code-block:: twig - - {# render a widget, but add a "foo" class to it #} - {{ form_widget(form.name, {'attr': {'class': 'foo'}}) }} - -The second argument to ``form_widget()`` is an array of variables. The most -common variable is ``attr``, which is an array of HTML attributes to apply -to the HTML widget. In some cases, certain types also have other template-related -options that can be passed. These are discussed on a type-by-type basis. -The ``attributes`` are not applied recursively to child fields if you're -rendering many fields at once (e.g. ``form_widget(form)``). - -See ":ref:`twig-reference-form-variables`" to learn more about the ``variables`` -argument. - -.. _reference-forms-twig-row: - -form_row(view, variables) -------------------------- - -Renders the "row" of a given field, which is the combination of the field's -label, errors and widget. - -.. code-block:: twig - - {# render a field row, but display a label with text "foo" #} - {{ form_row(form.name, {'label': 'foo'}) }} - -The second argument to ``form_row()`` is an array of variables. The templates -provided in Symfony only allow to override the label as shown in the example -above. - -See ":ref:`twig-reference-form-variables`" to learn about the ``variables`` -argument. - -.. _reference-forms-twig-rest: - -form_rest(view, variables) --------------------------- - -This renders all fields that have not yet been rendered for the given form. -It's a good idea to always have this somewhere inside your form as it'll -render hidden fields for you and make any fields you forgot to render more -obvious (since it'll render the field for you). - -.. code-block:: twig - - {{ form_rest(form) }} - -Form Tests Reference --------------------- - -Tests can be executed by using the ``is`` operator in Twig to create a -condition. Read `the Twig documentation`_ for more information. - -.. _form-twig-selectedchoice: - -selectedchoice(selected_value) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This test will check if the current choice is equal to the ``selected_value`` -or if the current choice is in the array (when ``selected_value`` is an -array). - -.. code-block:: twig - -