diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000..bc7d6a94182
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,9 @@
+
diff --git a/_build/_theme/_templates/layout.html b/_build/_theme/_templates/layout.html
index 3f0e8f50cdf..52715686172 100644
--- a/_build/_theme/_templates/layout.html
+++ b/_build/_theme/_templates/layout.html
@@ -1,13 +1,15 @@
{% extends '!layout.html' %}
-{% set css_files = ['https://symfony.com/css/compiled/v5/all.css?v=4'] %}
+{% set css_files = ['./assets/css/app.css', './assets/css/doc.css'] %}
{# make sure the Sphinx stylesheet isn't loaded #}
{% set style = '' %}
{% set isIndex = pagename is index %}
{% block extrahead %}
{# add JS to support tabs #}
-
+
+
+
{# pygment's styles are still loaded, undo some unwanted styles #}
');
$response->headers->set('Content-Type', 'text/css');
@@ -579,7 +594,7 @@ To return JSON from a controller, use the ``json()`` helper method. This returns
special ``JsonResponse`` object that encodes the data automatically::
// ...
- public function indexAction()
+ public function index()
{
// returns '{"username":"jane.doe"}' and sets the proper Content-Type header
return $this->json(array('username' => 'jane.doe'));
diff --git a/controller/error_pages.rst b/controller/error_pages.rst
index c8bcc65b6b2..90e12577f71 100644
--- a/controller/error_pages.rst
+++ b/controller/error_pages.rst
@@ -98,10 +98,6 @@ To override the 404 error template for HTML pages, create a new
{% block body %}
The requested page couldn't be located. Checkout for any URL
misspelling or return to the homepage.
@@ -169,13 +165,13 @@ automatically when installing Twig support):
// config/routes/dev/twig.php
use Symfony\Component\Routing\RouteCollection;
- $collection = new RouteCollection();
- $collection->addCollection(
+ $routes = new RouteCollection();
+ $routes->addCollection(
$loader->import('@TwigBundle/Resources/config/routing/errors.xml')
);
- $collection->addPrefix("/_error");
+ $routes->addPrefix("/_error");
- return $collection;
+ return $routes;
With this route added, you can use URLs like these to preview the *error* page
for a given status code as HTML or for a given status code and format.
diff --git a/controller/service.rst b/controller/service.rst
index 6fffddd1711..7989ec92bac 100644
--- a/controller/service.rst
+++ b/controller/service.rst
@@ -89,7 +89,7 @@ Alternatives to base Controller Methods
When using a controller defined as a service, you can still extend any of the
:ref:`normal base controller ` classes and
use their shortcuts. But, you don't need to! You can choose to extend *nothing*,
-and use dependency injection to access difference services.
+and use dependency injection to access different services.
The base `Controller class source code`_ is a great way to see how to accomplish
common tasks. For example, ``$this->render()`` is usually used to render a Twig
diff --git a/controller/soap_web_service.rst b/controller/soap_web_service.rst
index d1b9ec72c5b..d41576cb414 100644
--- a/controller/soap_web_service.rst
+++ b/controller/soap_web_service.rst
@@ -40,8 +40,8 @@ In this case, the SOAP service will allow the client to call a method called
{
$message = new \Swift_Message('Hello Service')
- ->setTo('me@example.com')
- ->setBody($name . ' says hi!');
+ ->setTo('me@example.com')
+ ->setBody($name.' says hi!');
$this->mailer->send($message);
@@ -71,14 +71,14 @@ can be retrieved via ``/soap?wsdl``::
*/
public function index(HelloService $helloService)
{
- $server = new \SoapServer('/path/to/hello.wsdl');
- $server->setObject($helloService);
+ $soapServer = new \SoapServer('/path/to/hello.wsdl');
+ $soapServer->setObject($helloService);
$response = new Response();
$response->headers->set('Content-Type', 'text/xml; charset=ISO-8859-1');
ob_start();
- $server->handle();
+ $soapServer->handle();
$response->setContent(ob_get_clean());
return $response;
@@ -99,9 +99,9 @@ Below is an example calling the service using a `NuSOAP`_ client. This example
assumes that the ``index()`` method in the controller above is accessible via
the route ``/soap``::
- $client = new \Soapclient('http://example.com/index.php/soap?wsdl');
+ $soapClient = new \SoapClient('http://example.com/index.php/soap?wsdl');
- $result = $client->call('hello', array('name' => 'Scott'));
+ $result = $soapClient->call('hello', array('name' => 'Scott'));
An example WSDL is below.
@@ -165,7 +165,7 @@ An example WSDL is below.
-.. _`PHP SOAP`: http://php.net/manual/en/book.soap.php
+.. _`PHP SOAP`: https://php.net/manual/en/book.soap.php
.. _`NuSOAP`: http://sourceforge.net/projects/nusoap
-.. _`output buffering`: http://php.net/manual/en/book.outcontrol.php
+.. _`output buffering`: https://php.net/manual/en/book.outcontrol.php
.. _`Zend SOAP`: http://framework.zend.com/manual/current/en/modules/zend.soap.server.html
diff --git a/controller/upload_file.rst b/controller/upload_file.rst
index dda7491124d..318175b226f 100644
--- a/controller/upload_file.rst
+++ b/controller/upload_file.rst
@@ -132,16 +132,15 @@ Finally, you need to update the code of the controller that handles the form::
/** @var Symfony\Component\HttpFoundation\File\UploadedFile $file */
$file = $product->getBrochure();
- // Generate a unique name for the file before saving it
- $fileName = md5(uniqid()).'.'.$file->guessExtension();
+ $fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();
- // Move the file to the directory where brochures are stored
+ // moves the file to the directory where brochures are stored
$file->move(
$this->getParameter('brochures_directory'),
$fileName
);
- // Update the 'brochure' property to store the PDF file name
+ // updates the 'brochure' property to store the PDF file name
// instead of its contents
$product->setBrochure($fileName);
@@ -154,6 +153,16 @@ Finally, you need to update the code of the controller that handles the form::
'form' => $form->createView(),
));
}
+
+ /**
+ * @return string
+ */
+ private function generateUniqueFileName()
+ {
+ // md5() reduces the similarity of the file names generated by
+ // uniqid(), which is based on timestamps
+ return md5(uniqid());
+ }
}
Now, create the ``brochures_directory`` parameter that was used in the
@@ -228,25 +237,25 @@ logic to a separate service::
class FileUploader
{
- private $targetDir;
+ private $targetDirectory;
- public function __construct($targetDir)
+ public function __construct($targetDirectory)
{
- $this->targetDir = $targetDir;
+ $this->targetDirectory = $targetDirectory;
}
public function upload(UploadedFile $file)
{
$fileName = md5(uniqid()).'.'.$file->guessExtension();
- $file->move($this->getTargetDir(), $fileName);
+ $file->move($this->getTargetDirectory(), $fileName);
return $fileName;
}
- public function getTargetDir()
+ public function getTargetDirectory()
{
- return $this->targetDir;
+ return $this->targetDirectory;
}
}
@@ -262,7 +271,7 @@ Then, define a service for this class:
App\Service\FileUploader:
arguments:
- $targetDir: '%brochures_directory%'
+ $targetDirectory: '%brochures_directory%'
.. code-block:: xml
@@ -285,7 +294,7 @@ Then, define a service for this class:
use App\Service\FileUploader;
$container->autowire(FileUploader::class)
- ->setArgument('$targetDir', '%brochures_directory%');
+ ->setArgument('$targetDirectory', '%brochures_directory%');
Now you're ready to use this service in the controller::
@@ -445,7 +454,7 @@ controller.
}
if ($fileName = $entity->getBrochure()) {
- $entity->setBrochure(new File($this->uploader->getTargetDir().'/'.$fileName));
+ $entity->setBrochure(new File($this->uploader->getTargetDirectory().'/'.$fileName));
}
}
}
diff --git a/create_framework/dependency_injection.rst b/create_framework/dependency_injection.rst
index 9237bbffbbd..b2b028f5ca2 100644
--- a/create_framework/dependency_injection.rst
+++ b/create_framework/dependency_injection.rst
@@ -10,9 +10,10 @@ to it::
namespace Simplex;
use Symfony\Component\EventDispatcher\EventDispatcher;
- use Symfony\Component\Routing;
use Symfony\Component\HttpFoundation;
+ use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel;
+ use Symfony\Component\Routing;
class Framework extends HttpKernel\HttpKernel
{
@@ -104,30 +105,30 @@ Create a new file to host the dependency injection container configuration::
use Symfony\Component\EventDispatcher;
use Simplex\Framework;
- $sc = new DependencyInjection\ContainerBuilder();
- $sc->register('context', Routing\RequestContext::class);
- $sc->register('matcher', Routing\Matcher\UrlMatcher::class)
+ $containerBuilder = new DependencyInjection\ContainerBuilder();
+ $containerBuilder->register('context', Routing\RequestContext::class);
+ $containerBuilder->register('matcher', Routing\Matcher\UrlMatcher::class)
->setArguments(array($routes, new Reference('context')))
;
- $sc->register('request_stack', HttpFoundation\RequestStack::class);
- $sc->register('controller_resolver', HttpKernel\Controller\ControllerResolver::class);
- $sc->register('argument_resolver', HttpKernel\Controller\ArgumentResolver::class);
+ $containerBuilder->register('request_stack', HttpFoundation\RequestStack::class);
+ $containerBuilder->register('controller_resolver', HttpKernel\Controller\ControllerResolver::class);
+ $containerBuilder->register('argument_resolver', HttpKernel\Controller\ArgumentResolver::class);
- $sc->register('listener.router', HttpKernel\EventListener\RouterListener::class)
+ $containerBuilder->register('listener.router', HttpKernel\EventListener\RouterListener::class)
->setArguments(array(new Reference('matcher'), new Reference('request_stack')))
;
- $sc->register('listener.response', HttpKernel\EventListener\ResponseListener::class)
+ $containerBuilder->register('listener.response', HttpKernel\EventListener\ResponseListener::class)
->setArguments(array('UTF-8'))
;
- $sc->register('listener.exception', HttpKernel\EventListener\ExceptionListener::class)
+ $containerBuilder->register('listener.exception', HttpKernel\EventListener\ExceptionListener::class)
->setArguments(array('Calendar\Controller\ErrorController::exceptionAction'))
;
- $sc->register('dispatcher', EventDispatcher\EventDispatcher::class)
+ $containerBuilder->register('dispatcher', EventDispatcher\EventDispatcher::class)
->addMethodCall('addSubscriber', array(new Reference('listener.router')))
->addMethodCall('addSubscriber', array(new Reference('listener.response')))
->addMethodCall('addSubscriber', array(new Reference('listener.exception')))
;
- $sc->register('framework', Framework::class)
+ $containerBuilder->register('framework', Framework::class)
->setArguments(array(
new Reference('dispatcher'),
new Reference('controller_resolver'),
@@ -136,7 +137,7 @@ Create a new file to host the dependency injection container configuration::
))
;
- return $sc;
+ return $containerBuilder;
The goal of this file is to configure your objects and their dependencies.
Nothing is instantiated during this configuration step. This is purely a
@@ -165,11 +166,11 @@ The front controller is now only about wiring everything together::
use Symfony\Component\HttpFoundation\Request;
$routes = include __DIR__.'/../src/app.php';
- $sc = include __DIR__.'/../src/container.php';
+ $container = include __DIR__.'/../src/container.php';
$request = Request::createFromGlobals();
- $response = $sc->get('framework')->handle($request);
+ $response = $container->get('framework')->handle($request);
$response->send();
@@ -195,8 +196,8 @@ Now, here is how you can register a custom listener in the front controller::
// ...
use Simplex\StringResponseListener;
- $sc->register('listener.string_response', StringResposeListener::class);
- $sc->getDefinition('dispatcher')
+ $container->register('listener.string_response', StringResponseListener::class);
+ $container->getDefinition('dispatcher')
->addMethodCall('addSubscriber', array(new Reference('listener.string_response')))
;
@@ -204,34 +205,34 @@ Beside describing your objects, the dependency injection container can also be
configured via parameters. Let's create one that defines if we are in debug
mode or not::
- $sc->setParameter('debug', true);
+ $container->setParameter('debug', true);
- echo $sc->getParameter('debug');
+ echo $container->getParameter('debug');
These parameters can be used when defining object definitions. Let's make the
charset configurable::
// ...
- $sc->register('listener.response', HttpKernel\EventListener\ResponseListener::class)
+ $container->register('listener.response', HttpKernel\EventListener\ResponseListener::class)
->setArguments(array('%charset%'))
;
After this change, you must set the charset before using the response listener
object::
- $sc->setParameter('charset', 'UTF-8');
+ $container->setParameter('charset', 'UTF-8');
Instead of relying on the convention that the routes are defined by the
``$routes`` variables, let's use a parameter again::
// ...
- $sc->register('matcher', Routing\Matcher\UrlMatcher::class)
+ $container->register('matcher', Routing\Matcher\UrlMatcher::class)
->setArguments(array('%routes%', new Reference('context')))
;
And the related change in the front controller::
- $sc->setParameter('routes', include __DIR__.'/../src/app.php');
+ $container->setParameter('routes', include __DIR__.'/../src/app.php');
We have obviously barely scratched the surface of what you can do with the
container: from class names as parameters, to overriding existing object
diff --git a/create_framework/event_dispatcher.rst b/create_framework/event_dispatcher.rst
index b20bfde96a4..8939659bd4f 100644
--- a/create_framework/event_dispatcher.rst
+++ b/create_framework/event_dispatcher.rst
@@ -70,9 +70,9 @@ the Response instance::
$arguments = $this->argumentResolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
- } catch (ResourceNotFoundException $e) {
+ } catch (ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
- } catch (\Exception $e) {
+ } catch (\Exception $exception) {
$response = new Response('An error occurred', 500);
}
diff --git a/create_framework/front_controller.rst b/create_framework/front_controller.rst
index 333af3bd011..8698865aa46 100644
--- a/create_framework/front_controller.rst
+++ b/create_framework/front_controller.rst
@@ -38,9 +38,9 @@ Let's see it in action::
// framework/index.php
require_once __DIR__.'/init.php';
- $input = $request->get('name', 'World');
+ $name = $request->get('name', 'World');
- $response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
+ $response->setContent(sprintf('Hello %s', htmlspecialchars($name, ENT_QUOTES, 'UTF-8')));
$response->send();
And for the "Goodbye" page::
@@ -98,8 +98,8 @@ Such a script might look like the following::
And here is for instance the new ``hello.php`` script::
// framework/hello.php
- $input = $request->get('name', 'World');
- $response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
+ $name = $request->get('name', 'World');
+ $response->setContent(sprintf('Hello %s', htmlspecialchars($name, ENT_QUOTES, 'UTF-8')));
In the ``front.php`` script, ``$map`` associates URL paths with their
corresponding PHP script paths.
diff --git a/create_framework/http_foundation.rst b/create_framework/http_foundation.rst
index 48a827e35d4..a48cbfebaa3 100644
--- a/create_framework/http_foundation.rst
+++ b/create_framework/http_foundation.rst
@@ -18,27 +18,27 @@ Even if the "application" we wrote in the previous chapter was simple enough,
it suffers from a few problems::
// framework/index.php
- $input = $_GET['name'];
+ $name = $_GET['name'];
- printf('Hello %s', $input);
+ printf('Hello %s', $name);
First, if the ``name`` query parameter is not defined in the URL query string,
you will get a PHP warning; so let's fix it::
// framework/index.php
- $input = isset($_GET['name']) ? $_GET['name'] : 'World';
+ $name = isset($_GET['name']) ? $_GET['name'] : 'World';
- printf('Hello %s', $input);
+ printf('Hello %s', $name);
Then, this *application is not secure*. Can you believe it? Even this simple
snippet of PHP code is vulnerable to one of the most widespread Internet
security issue, XSS (Cross-Site Scripting). Here is a more secure version::
- $input = isset($_GET['name']) ? $_GET['name'] : 'World';
+ $name = isset($_GET['name']) ? $_GET['name'] : 'World';
header('Content-Type: text/html; charset=utf-8');
- printf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'));
+ printf('Hello %s', htmlspecialchars($name, ENT_QUOTES, 'UTF-8'));
.. note::
@@ -142,9 +142,9 @@ Now, let's rewrite our application by using the ``Request`` and the
$request = Request::createFromGlobals();
- $input = $request->get('name', 'World');
+ $name = $request->get('name', 'World');
- $response = new Response(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
+ $response = new Response(sprintf('Hello %s', htmlspecialchars($name, ENT_QUOTES, 'UTF-8')));
$response->send();
@@ -289,16 +289,16 @@ applications using it (like `Symfony`_, `Drupal 8`_, `phpBB 3`_, `ezPublish
5`_, `Laravel`_, `Silex`_ and `more`_).
.. _`Twig`: http://twig.sensiolabs.org/
-.. _`HTTP specification`: http://tools.ietf.org/wg/httpbis/
+.. _`HTTP specification`: https://tools.ietf.org/wg/httpbis/
.. _`audited`: https://symfony.com/blog/symfony2-security-audit
.. _`Symfony`: https://symfony.com/
.. _`Drupal 8`: https://drupal.org/
.. _`phpBB 3`: https://www.phpbb.com/
-.. _`ezPublish 5`: http://ez.no/
-.. _`Laravel`: http://laravel.com/
-.. _`Silex`: http://silex.sensiolabs.org/
+.. _`ezPublish 5`: https://ez.no/
+.. _`Laravel`: https://laravel.com/
+.. _`Silex`: https://silex.sensiolabs.org/
.. _`Midgard CMS`: http://www.midgard-project.org/
-.. _`Zikula`: http://zikula.org/
-.. _`autoloaded`: http://php.net/autoload
-.. _`PSR-4`: http://www.php-fig.org/psr/psr-4/
+.. _`Zikula`: https://zikula.org/
+.. _`autoloaded`: https://php.net/autoload
+.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
.. _`more`: https://symfony.com/components/HttpFoundation
diff --git a/create_framework/http_kernel_controller_resolver.rst b/create_framework/http_kernel_controller_resolver.rst
index 180e9262c69..ff1408355ea 100644
--- a/create_framework/http_kernel_controller_resolver.rst
+++ b/create_framework/http_kernel_controller_resolver.rst
@@ -191,9 +191,9 @@ Let's conclude with the new version of our framework::
$arguments = $argumentResolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
- } catch (Routing\Exception\ResourceNotFoundException $e) {
+ } catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
- } catch (Exception $e) {
+ } catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
@@ -202,5 +202,5 @@ Let's conclude with the new version of our framework::
Think about it once more: our framework is more robust and more flexible than
ever and it still has less than 50 lines of code.
-.. _`reflection`: http://php.net/reflection
+.. _`reflection`: https://php.net/reflection
.. _`FrameworkExtraBundle`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
diff --git a/create_framework/http_kernel_httpkernel_class.rst b/create_framework/http_kernel_httpkernel_class.rst
index 1e5f28ff649..b8865d3a143 100644
--- a/create_framework/http_kernel_httpkernel_class.rst
+++ b/create_framework/http_kernel_httpkernel_class.rst
@@ -136,8 +136,8 @@ instead of a full Response object::
{
public function indexAction(Request $request, $year)
{
- $leapyear = new LeapYear();
- if ($leapyear->isLeapYear($year)) {
+ $leapYear = new LeapYear();
+ if ($leapYear->isLeapYear($year)) {
return 'Yep, this is a leap year! ';
}
diff --git a/create_framework/http_kernel_httpkernelinterface.rst b/create_framework/http_kernel_httpkernelinterface.rst
index 99c49a1a273..13f98119eb2 100644
--- a/create_framework/http_kernel_httpkernelinterface.rst
+++ b/create_framework/http_kernel_httpkernelinterface.rst
@@ -54,7 +54,10 @@ PHP; it implements ``HttpKernelInterface`` and wraps another
``HttpKernelInterface`` instance::
// example.com/web/front.php
- $framework = new Simplex\Framework($dispatcher, $matcher, $resolver);
+
+ // ..
+
+ $framework = new Simplex\Framework($dispatcher, $matcher, $controllerResolver, $argumentResolver);
$framework = new HttpKernel\HttpCache\HttpCache(
$framework,
new HttpKernel\HttpCache\Store(__DIR__.'/../cache')
@@ -73,8 +76,8 @@ to cache a response for 10 seconds, use the ``Response::setTtl()`` method::
// ...
public function indexAction(Request $request, $year)
{
- $leapyear = new LeapYear();
- if ($leapyear->isLeapYear($year)) {
+ $leapYear = new LeapYear();
+ if ($leapYear->isLeapYear($year)) {
$response = new Response('Yep, this is a leap year!');
} else {
$response = new Response('Nope, this is not a leap year.');
diff --git a/create_framework/introduction.rst b/create_framework/introduction.rst
index 95f41c9a807..f2fad8a6f2a 100644
--- a/create_framework/introduction.rst
+++ b/create_framework/introduction.rst
@@ -104,9 +104,9 @@ Instead of creating our framework from scratch, we are going to write the same
start with the simplest web application we can think of in PHP::
// framework/index.php
- $input = $_GET['name'];
+ $name = $_GET['name'];
- printf('Hello %s', $input);
+ printf('Hello %s', $name);
You can use the PHP built-in server to test this great application in a browser
(``http://localhost:4321/index.php?name=Fabien``):
diff --git a/create_framework/routing.rst b/create_framework/routing.rst
index 2aed62006e4..deaf9a02420 100644
--- a/create_framework/routing.rst
+++ b/create_framework/routing.rst
@@ -149,9 +149,9 @@ With this knowledge in mind, let's write the new version of our framework::
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
$response = new Response(ob_get_clean());
- } catch (Routing\Exception\ResourceNotFoundException $e) {
+ } catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
- } catch (Exception $e) {
+ } catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
diff --git a/create_framework/separation_of_concerns.rst b/create_framework/separation_of_concerns.rst
index cc2060a2fc1..84db81611e0 100644
--- a/create_framework/separation_of_concerns.rst
+++ b/create_framework/separation_of_concerns.rst
@@ -49,9 +49,9 @@ request handling logic into its own ``Simplex\\Framework`` class::
$arguments = $this->argumentResolver->getArguments($request, $controller);
return call_user_func_array($controller, $arguments);
- } catch (ResourceNotFoundException $e) {
+ } catch (ResourceNotFoundException $exception) {
return new Response('Not Found', 404);
- } catch (\Exception $e) {
+ } catch (\Exception $exception) {
return new Response('An error occurred', 500);
}
}
@@ -108,8 +108,8 @@ Move the controller to ``Calendar\Controller\LeapYearController``::
{
public function indexAction(Request $request, $year)
{
- $leapyear = new LeapYear();
- if ($leapyear->isLeapYear($year)) {
+ $leapYear = new LeapYear();
+ if ($leapYear->isLeapYear($year)) {
return new Response('Yep, this is a leap year!');
}
diff --git a/create_framework/templating.rst b/create_framework/templating.rst
index 21dd890979e..a00f5b352fb 100644
--- a/create_framework/templating.rst
+++ b/create_framework/templating.rst
@@ -20,9 +20,9 @@ Change the template rendering part of the framework to read as follows::
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func('render_template', $request);
- } catch (Routing\Exception\ResourceNotFoundException $e) {
+ } catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
- } catch (Exception $e) {
+ } catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
@@ -63,9 +63,9 @@ the ``_controller`` route attribute::
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func($request->attributes->get('_controller'), $request);
- } catch (Routing\Exception\ResourceNotFoundException $e) {
+ } catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
- } catch (Exception $e) {
+ } catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
@@ -125,9 +125,9 @@ Here is the updated and improved version of our framework::
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func($request->attributes->get('_controller'), $request);
- } catch (Routing\Exception\ResourceNotFoundException $e) {
+ } catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
- } catch (Exception $e) {
+ } catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
@@ -177,5 +177,5 @@ As always, you can decide to stop here and use the framework as is; it's
probably all you need to create simple websites like those fancy one-page
`websites`_ and hopefully a few others.
-.. _`callbacks`: http://php.net/callback#language.types.callback
-.. _`websites`: http://kottke.org/08/02/single-serving-sites
+.. _`callbacks`: https://php.net/callback#language.types.callback
+.. _`websites`: https://kottke.org/08/02/single-serving-sites
diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst
index a7be59438b4..d3bb5f623a4 100644
--- a/create_framework/unit_testing.rst
+++ b/create_framework/unit_testing.rst
@@ -219,4 +219,4 @@ safely think about the next batch of features we want to add to our framework.
.. _`PHPUnit`: https://phpunit.de/manual/current/en/index.html
.. _`test doubles`: https://phpunit.de/manual/current/en/test-doubles.html
-.. _`XDebug`: http://xdebug.org/
+.. _`XDebug`: https://xdebug.org/
diff --git a/deployment.rst b/deployment.rst
index 0866e503814..34ca62782bb 100644
--- a/deployment.rst
+++ b/deployment.rst
@@ -135,6 +135,11 @@ How you set environment variables, depends on your setup: they can be set at the
command line, in your Nginx configuration, or via other methods provided by your
hosting service.
+At the very least you need to define the ``SYMFONY_ENV=prod`` (or
+``APP_ENV=prod`` if you're using :doc:`Symfony Flex `) to run the
+application in ``prod`` mode, but depending on your application you may need to
+define other env vars too.
+
C) Install/Update your Vendors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -167,8 +172,7 @@ Make sure you clear and warm-up your Symfony cache:
.. code-block:: terminal
- $ php bin/console cache:clear --env=prod --no-debug --no-warmup
- $ php bin/console cache:warmup --env=prod
+ $ php bin/console cache:clear --env=prod --no-debug
E) Dump your Assetic Assets
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -215,7 +219,7 @@ Deployments not Using the ``composer.json`` File
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Symfony applications provide a ``kernel.project_dir`` parameter and a related
-:method:`Symfony\\Component\\HttpKernel\\Kernel\\Kernel::getProjectDir>` method.
+:method:`Symfony\\Component\\HttpKernel\\Kernel::getProjectDir` method.
You can use this method to perform operations with file paths relative to your
project's root directory. The logic to find that project root directory is based
on the location of the main ``composer.json`` file.
diff --git a/doctrine.rst b/doctrine.rst
index 00d6d0692aa..39c9e6c22de 100644
--- a/doctrine.rst
+++ b/doctrine.rst
@@ -23,7 +23,8 @@ code:
.. code-block:: terminal
- composer require doctrine maker
+ composer require doctrine
+ composer require maker --dev
Configuring the Database
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -41,6 +42,13 @@ The database connection information is stored as an environment variable called
# to use sqlite:
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
+.. caution::
+
+ If the username, password or database name 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.
+
Now that your connection parameters are setup, Doctrine can create the ``db_name``
database for you:
@@ -62,14 +70,50 @@ Creating an Entity Class
Suppose you're building an application where products need to be displayed.
Without even thinking about Doctrine or databases, you already know that
-you need a ``Product`` object to represent those products. Use the ``make:entity``
-command to create this class for you:
+you need a ``Product`` object to represent those products.
+
+.. _doctrine-adding-mapping:
+
+You can use the ``make:entity`` command to create this class and any fields you
+need. The command will ask you some questions - answer them like done below:
.. code-block:: terminal
- $ php bin/console make:entity Product
+ $ php bin/console make:entity
+
+ Class name of the entity to create or update:
+ > Product
+
+ New property name (press to stop adding fields):
+ > name
+
+ Field type (enter ? to see all types) [string]:
+ > string
+
+ Field length [255]:
+ > 255
-You now have a new ``src/Entity/Product.php`` file::
+ Can this field be null in the database (nullable) (yes/no) [no]:
+ > no
+
+ New property name (press to stop adding fields):
+ > price
+
+ Field type (enter ? to see all types) [string]:
+ > integer
+
+ Can this field be null in the database (nullable) (yes/no) [no]:
+ > no
+
+ New property name (press to stop adding fields):
+ >
+ (press enter again to finish)
+
+.. versionadded::
+ The interactive behavior of the ``make:entity`` command was introduced
+ in MakerBundle 1.3.
+
+Woh! You now have a new ``src/Entity/Product.php`` file::
// src/Entity/Product.php
namespace App\Entity;
@@ -88,105 +132,51 @@ You now have a new ``src/Entity/Product.php`` file::
*/
private $id;
- // add your own fields
- }
-
-This class is called an "entity". And soon, you will be able to save and query Product
-objects to a ``product`` table in your database.
-
-.. _doctrine-adding-mapping:
+ /**
+ * @ORM\Column(type="string", length=255)
+ */
+ private $name;
-Mapping More Fields / Columns
------------------------------
+ /**
+ * @ORM\Column(type="integer")
+ */
+ private $price;
-Each property in the ``Product`` entity can be mapped to a column in the ``product``
-table. By adding some mapping configuration, Doctrine will be able to save a Product
-object to the ``product`` table *and* query from the ``product`` table and turn
-that data into ``Product`` objects:
+ public function getId()
+ {
+ return $this->id;
+ }
-.. image:: /_images/doctrine/mapping_single_entity.png
- :align: center
+ // ... getter and setter methods
+ }
-Let's give the ``Product`` entity class three more properties and map them to columns
-in the database. This is usually done with annotations:
+.. note::
-.. configuration-block::
+ Confused why the price is an integer? Don't worry: this is just an example.
+ But, storing prices as integers (e.g. 100 = $1 USD) can avoid rounding issues.
- .. code-block:: php-annotations
+This class is called an "entity". And soon, you'll be able to save and query Product
+objects to a ``product`` table in your database. Each property in the ``Product``
+entity can be mapped to a column in that table. This is usually done with annotations:
+the ``@ORM\...`` comments that you see above each property:
- // src/Entity/Product.php
- // ...
+.. image:: /_images/doctrine/mapping_single_entity.png
+ :align: center
- // this use statement is needed for the annotations
- use Doctrine\ORM\Mapping as ORM;
+The ``make:entity`` command is a tool to make life easier. But this is *your* code:
+add/remove fields, add/remove methods or update configuration.
- class Product
- {
- /**
- * @ORM\Id
- * @ORM\GeneratedValue
- * @ORM\Column(type="integer")
- */
- private $id;
-
- /**
- * @ORM\Column(type="string", length=100)
- */
- private $name;
-
- /**
- * @ORM\Column(type="decimal", scale=2, nullable=true)
- */
- private $price;
- }
-
- .. code-block:: yaml
-
- # config/doctrine/Product.orm.yml
- App\Entity\Product:
- type: entity
- id:
- id:
- type: integer
- generator: { strategy: AUTO }
- fields:
- name:
- type: string
- length: 100
- price:
- type: decimal
- scale: 2
- nullable: true
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Doctrine supports a wide variety of different field types, each with their own options.
-To see a full list of types and options, see `Doctrine's Mapping Types documentation`_.
+Doctrine supports a wide variety of field types, each with their own options.
+To see a full list, check out `Doctrine's Mapping Types documentation`_.
If you want to use XML instead of annotations, add ``type: xml`` and
-``dir: '%kernel.project_dir%/config/doctrine`` to the entity mappings in your
+``dir: '%kernel.project_dir%/config/doctrine'`` to the entity mappings in your
``config/packages/doctrine.yaml`` file.
.. caution::
Be careful not to use reserved SQL keywords as your table or column names
(e.g. ``GROUP`` or ``USER``). See Doctrine's `Reserved SQL keywords documentation`_
- for details on how to escape these. Or, configure the table name with
+ for details on how to escape these. Or, change the table name with
``@ORM\Table(name="groups")`` above the class or configure the column name with
the ``name="group_name"`` option.
@@ -197,17 +187,18 @@ Migrations: Creating the Database Tables/Schema
The ``Product`` class is fully-configured and ready to save to a ``product`` table.
Of course, your database doesn't actually have the ``product`` table yet. To add
-the table, you can leverage the `DoctrineMigrationsBundle`_, which is already installed:
+it, you can leverage the `DoctrineMigrationsBundle`_, which is already installed:
.. code-block:: terminal
- $ php bin/console doctrine:migrations:diff
+ $ php bin/console make:migration
If everything worked, you should see something like this:
- Generated new migration class to
- "/path/to/project/doctrine/src/Migrations/Version20171122151511.php"
- from schema differences.
+ SUCCESS!
+
+ Next: Review the new migration "src/Migrations/Version20180207231217.php"
+ Then: Run the migration with php bin/console doctrine:migrations:migrate
If you open this file, it contains the SQL needed to update your database! To run
that SQL, execute your migrations:
@@ -217,12 +208,38 @@ that SQL, execute your migrations:
$ php bin/console doctrine:migrations:migrate
This command executes all migration files that have not already been run against
-your database.
+your database. You should run this command on production when you deploy to keep
+your production database up-to-date.
Migrations & Adding more Fields
-------------------------------
But what if you need to add a new field property to ``Product``, like a ``description``?
+It's easy to add the new property by hand. But, you can also use ``make:entity``
+again:
+
+.. code-block:: terminal
+
+ $ php bin/console make:entity
+
+ Class name of the entity to create or update
+ > Product
+
+ New property name (press to stop adding fields):
+ > description
+
+ Field type (enter ? to see all types) [string]:
+ > text
+
+ Can this field be null in the database (nullable) (yes/no) [no]:
+ > no
+
+ New property name (press to stop adding fields):
+ >
+ (press enter again to finish)
+
+This adds the new ``description`` property and ``getDescription()`` and ``setDescription()``
+methods:
.. code-block:: diff
@@ -237,6 +254,8 @@ But what if you need to add a new field property to ``Product``, like a ``descri
+ * @ORM\Column(type="text")
+ */
+ private $description;
+
+ // getDescription() & setDescription() were also added
}
The new property is mapped, but it doesn't exist yet in the ``product`` table. No
@@ -244,7 +263,7 @@ problem! Just generate a new migration:
.. code-block:: terminal
- $ php bin/console doctrine:migrations:diff
+ $ php bin/console make:migration
This time, the SQL in the generated file will look like this:
@@ -262,55 +281,25 @@ before, execute your migrations:
This will only execute the *one* new migration file, because DoctrineMigrationsBundle
knows that the first migration was already executed earlier. Behind the scenes, it
-automatically manages a ``migration_versions`` table to track this.
+manages a ``migration_versions`` table to track this.
Each time you make a change to your schema, run these two commands to generate the
-migration and then execute it. Be sure to commit the migration files and run execute
+migration and then execute it. Be sure to commit the migration files and execute
them when you deploy.
.. _doctrine-generating-getters-and-setters:
-Generating Getters and Setters
-------------------------------
-
-Doctrine now knows how to persist a ``Product`` object to the database. But the class
-itself isn't useful yet. All of the properties are ``private``, so there's no way
-to set data on them!
-
-For that reason, you should create public getters and setters for all the fields
-you need to modify from outside of the class. If you use an IDE like PhpStorm, it
-can generate these for you. In PhpStorm, put your cursor anywhere in the class,
-then go to the Code -> Generate menu and select "Getters and Setters"::
-
- // src/Entity/Product
- // ...
-
- class Product
- {
- // all of your properties
-
- public function getId()
- {
- return $this->id;
- }
-
- public function getName()
- {
- return $this->name;
- }
+.. tip::
- public function setName($name)
- {
- $this->name = $name;
- }
+ If you prefer to add new properties manually, the ``make:entity`` command can
+ generate the getter & setter methods for you:
- // ... getters & setters for price & description
- }
+ .. code-block:: terminal
-.. tip::
+ $ php bin/console make:entity --regenerate
- Typically you won't need a ``setId()`` method: Doctrine will set this for you
- automatically.
+ If you make some changes and want to regenerate *all* getter/setter methods,
+ also pass ``--overwrite``.
Persisting Objects to the Database
----------------------------------
@@ -341,8 +330,8 @@ and save it!
public function index()
{
// you can fetch the EntityManager via $this->getDoctrine()
- // or you can add an argument to your action: index(EntityManagerInterface $em)
- $em = $this->getDoctrine()->getManager();
+ // or you can add an argument to your action: index(EntityManagerInterface $entityManager)
+ $entityManager = $this->getDoctrine()->getManager();
$product = new Product();
$product->setName('Keyboard');
@@ -350,10 +339,10 @@ and save it!
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
- $em->persist($product);
+ $entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
- $em->flush();
+ $entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
@@ -370,21 +359,24 @@ you can query the database directly:
$ php bin/console doctrine:query:sql 'SELECT * FROM product'
+ # on Windows systems not using Powershell, run this command instead:
+ # php bin/console doctrine:query:sql "SELECT * FROM product"
+
Take a look at the previous example in more detail:
.. _doctrine-entity-manager:
-* **line 17** The ``$this->getDoctrine()->getManager()`` method gets Doctrine's
+* **line 16** The ``$this->getDoctrine()->getManager()`` method gets Doctrine's
*entity manager* object, which is the most important object in Doctrine. It's
responsible for saving objects to, and fetching objects from, the database.
-* **lines 19-22** In this section, you instantiate and work with the ``$product``
+* **lines 18-21** In this section, you instantiate and work with the ``$product``
object like any other normal PHP object.
-* **line 25** The ``persist($product)`` call tells Doctrine to "manage" the
+* **line 24** The ``persist($product)`` call tells Doctrine to "manage" the
``$product`` object. This does **not** cause a query to be made to the database.
-* **line 28** When the ``flush()`` method is called, Doctrine looks through
+* **line 27** When the ``flush()`` method is called, Doctrine looks through
all of the objects that it's managing to see if they need to be persisted
to the database. In this example, the ``$product`` object's data doesn't
exist in the database, so the entity manager executes an ``INSERT`` query,
@@ -441,10 +433,10 @@ Once you have a repository object, you have many helper methods::
$repository = $this->getDoctrine()->getRepository(Product::class);
- // query for a single Product by its primary key (usually "id")
+ // look for a single Product by its primary key (usually "id")
$product = $repository->find($id);
- // query for a single Product by name
+ // look for a single Product by name
$product = $repository->findOneBy(['name' => 'Keyboard']);
// or find by name and price
$product = $repository->findOneBy([
@@ -452,13 +444,13 @@ Once you have a repository object, you have many helper methods::
'price' => 19.99,
]);
- // query for multiple Product objects matching the name, ordered by price
+ // look for multiple Product objects matching the name, ordered by price
$products = $repository->findBy(
['name' => 'Keyboard'],
['price' => 'ASC']
);
- // find *all* Product objects
+ // look for *all* Product objects
$products = $repository->findAll();
You can also add *custom* methods for more complex queries! More on that later in
@@ -520,8 +512,8 @@ Once you've fetched an object from Doctrine, updating it is easy::
*/
public function updateAction($id)
{
- $em = $this->getDoctrine()->getManager();
- $product = $em->getRepository(Product::class)->find($id);
+ $entityManager = $this->getDoctrine()->getManager();
+ $product = $entityManager->getRepository(Product::class)->find($id);
if (!$product) {
throw $this->createNotFoundException(
@@ -530,7 +522,7 @@ Once you've fetched an object from Doctrine, updating it is easy::
}
$product->setName('New product name!');
- $em->flush();
+ $entityManager->flush();
return $this->redirectToRoute('product_show', [
'id' => $product->getId()
@@ -543,8 +535,8 @@ Updating an object involves just three steps:
#. modifying the object;
#. calling ``flush()`` on the entity manager.
-You *can* call ``$em->persist($product)``, but it isn't necessary: Doctrine is already
-"watching" your object for changes.
+You *can* call ``$entityManager->persist($product)``, but it isn't necessary:
+Doctrine is already "watching" your object for changes.
Deleting an Object
------------------
@@ -552,8 +544,8 @@ Deleting an Object
Deleting an object is very similar, but requires a call to the ``remove()``
method of the entity manager::
- $em->remove($product);
- $em->flush();
+ $entityManager->remove($product);
+ $entityManager->flush();
As you might expect, the ``remove()`` method notifies Doctrine that you'd
like to remove the given object from the database. The ``DELETE`` query isn't
@@ -580,6 +572,7 @@ But what if you need a more complex query? When you generated your entity with
use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+ use Symfony\Bridge\Doctrine\RegistryInterface;
class ProductRepository extends ServiceEntityRepository
{
@@ -654,9 +647,9 @@ In addition to the query builder, you can also query with `Doctrine Query Langua
public function findAllGreaterThanPrice($price): array
{
- $em = $this->getEntityManager();
-
- $query = $em->createQuery(
+ $entityManager = $this->getEntityManager();
+
+ $query = $entityManager->createQuery(
'SELECT p
FROM App\Entity\Product p
WHERE p.price > :price
@@ -734,6 +727,7 @@ Learn more
* `DoctrineFixturesBundle`_
.. _`Doctrine`: http://www.doctrine-project.org/
+.. _`RFC 3986`: https://www.ietf.org/rfc/rfc3986.txt
.. _`MongoDB`: https://www.mongodb.org/
.. _`Doctrine's Mapping Types documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html
.. _`Query Builder`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html
diff --git a/doctrine/associations.rst b/doctrine/associations.rst
index 979dee35045..8c1c2e815c4 100644
--- a/doctrine/associations.rst
+++ b/doctrine/associations.rst
@@ -33,15 +33,31 @@ Suppose that each product in your application belongs to exactly one category.
In this case, you'll need a ``Category`` class, and a way to relate a
``Product`` object to a ``Category`` object.
-Start by creating a ``Category`` entity:
+Start by creating a ``Category`` entity with a ``name`` field:
.. code-block:: terminal
$ php bin/console make:entity Category
-Then, add a ``name`` field to that new ``Category`` class::
+ New property name (press to stop adding fields):
+ > name
- // src/Entity/Category
+ Field type (enter ? to see all types) [string]:
+ > string
+
+ Field length [255]:
+ > 255
+
+ Can this field be null in the database (nullable) (yes/no) [no]:
+ > no
+
+ New property name (press to stop adding fields):
+ >
+ (press enter again to finish)
+
+This will generate your new entity class::
+
+ // src/Entity/Category.php
// ...
class Category
@@ -73,7 +89,49 @@ From the perspective of the ``Product`` entity, this is a many-to-one relationsh
From the perspective of the ``Category`` entity, this is a one-to-many relationship.
To map this, first create a ``category`` property on the ``Product`` class with
-the ``ManyToOne`` annotation:
+the ``ManyToOne`` annotation. You can do this by hand, or by using the ``make:entity``
+command, which will ask you several questions about your relationship. If you're
+not sure of the answer, don't worry! You can always change the settings later:
+
+.. code-block:: terminal
+
+ $ php bin/console make:entity
+
+ Class name of the entity to create or update (e.g. BraveChef):
+ > Product
+
+ New property name (press to stop adding fields):
+ > category
+
+ Field type (enter ? to see all types) [string]:
+ > relation
+
+ What class should this entity be related to?:
+ > Category
+
+ Relation type? [ManyToOne, OneToMany, ManyToMany, OneToOne]:
+ > ManyToOne
+
+ Is the Product.category property allowed to be null (nullable)? (yes/no) [yes]:
+ > no
+
+ Do you want to add a new property to Category so that you can access/update
+ Product objects from it - e.g. $category->getProducts()? (yes/no) [yes]:
+ > yes
+
+ New field name inside Category [products]:
+ > products
+
+ Do you want to automatically delete orphaned App\Entity\Product objects
+ (orphanRemoval)? (yes/no) [no]:
+ > no
+
+ New property name (press to stop adding fields):
+ >
+ (press enter again to finish)
+
+This made changes to *two* changes. First, added a new ``category`` property to
+the ``Product`` entity (and getter & setter methods):
.. configuration-block::
@@ -88,18 +146,20 @@ the ``ManyToOne`` annotation:
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="products")
- * @ORM\JoinColumn(nullable=true)
+ * @ORM\JoinColumn(nullable=false)
*/
private $category;
- public function getCategory(): Category
+ public function getCategory(): ?Category
{
return $this->category;
}
- public function setCategory(Category $category)
+ public function setCategory(?Category $category): self
{
$this->category = $category;
+
+ return $this;
}
}
@@ -114,7 +174,7 @@ the ``ManyToOne`` annotation:
targetEntity: App\Entity\Category
inversedBy: products
joinColumn:
- nullable: true
+ nullable: false
.. code-block:: xml
@@ -131,17 +191,18 @@ the ``ManyToOne`` annotation:
field="category"
target-entity="App\Entity\Category"
inversed-by="products">
-
+
-This many-to-one mapping is required. It tells Doctrine to use the ``category_id``
+This ``ManyToOne`` mapping is required. It tells Doctrine to use the ``category_id``
column on the ``product`` table to relate each record in that table with
a record in the ``category`` table.
-Next, since a *one* ``Category`` object will relate to *many* ``Product``
-objects, add a ``products`` property to ``Category`` that will hold those objects:
+Next, since a *one* ``Category`` object will relate to *many* ``Product`` objects,
+the ``make:entity`` command *also* added a ``products`` property to the ``Category``
+class that will hold these objects::
.. configuration-block::
@@ -170,10 +231,12 @@ objects, add a ``products`` property to ``Category`` that will hold those object
/**
* @return Collection|Product[]
*/
- public function getProducts()
+ public function getProducts(): Collection
{
return $this->products;
}
+
+ // addProduct() and removeProduct() were also added
}
.. code-block:: yaml
@@ -214,9 +277,10 @@ objects, add a ``products`` property to ``Category`` that will hold those object
The ``ManyToOne`` mapping shown earlier is *required*, But, this ``OneToMany``
is optional: only add it *if* you want to be able to access the products that are
-related to a category. In this example, it *will* be useful to be able to call
-``$category->getProducts()``. If you don't want it, then you also don't need the
-``inversedBy`` or ``mappedBy`` config.
+related to a category (this is one of the questions ``make:entity`` asks you). In
+this example, it *will* be useful to be able to call ``$category->getProducts()``.
+If you don't want it, then you also don't need the ``inversedBy`` or ``mappedBy``
+config.
.. sidebar:: What is the ArrayCollection Stuff?
@@ -262,13 +326,13 @@ Now you can see this new code in action! Imagine you're inside a controller::
$product->setPrice(19.99);
$product->setDescription('Ergonomic and stylish!');
- // relate this product to the category
+ // relates this product to the category
$product->setCategory($category);
- $em = $this->getDoctrine()->getManager();
- $em->persist($category);
- $em->persist($product);
- $em->flush();
+ $entityManager = $this->getDoctrine()->getManager();
+ $entityManager->persist($category);
+ $entityManager->persist($product);
+ $entityManager->flush();
return new Response(
'Saved new product with id: '.$product->getId()
@@ -292,11 +356,9 @@ Doctrine takes care of the rest when saving.
.. sidebar:: Updating the Relationship from the Inverse Side
- Could you also call ``$category->setProducts()`` to set the relationship? Actually,
- no! Earlier, you did *not* add a ``setProducts()`` method on ``Category``. That's
- on purpose: you can *only* set data on the *owning* side of the relationship. In
- other words, if you call ``$category->setProducts()`` only, that is *completely*
- ignored when saving. For more details, see: `associations-inverse-side`_.
+ Could you also call ``$category->addProduct()`` to change the relationship? Yes,
+ but, only because the ``make:entity`` command helped us. For more details,
+ see: `associations-inverse-side`_.
Fetching Related Objects
------------------------
@@ -436,13 +498,16 @@ Setting Information from the Inverse Side
-----------------------------------------
So far, you've updated the relationship by calling ``$product->setCategory($category)``.
-This is no accident: you *must* set the relationship on the *owning* side. The owning
-side is always where the ``ManyToOne`` mapping is set (for a ``ManyToMany`` relation,
-you can choose which side is the owning side).
+This is no accident! Each relationship has two sides: in this example, ``Product.category``
+is the *owning* side and ``Category.products`` is the *inverse* side.
-Does this means it's not possible to call ``$category->setProducts()``? Actually,
-it *is* possible, by writing clever methods. First, instead of a ``setProducts()``
-method, create a ``addProduct()`` method::
+To update a relationship in the database, you *must* set the relationship on the
+*owning* side. The owning side is always where the ``ManyToOne`` mapping is set
+(for a ``ManyToMany`` relation, you can choose which side is the owning side).
+
+Does this means it's not possible to call ``$category->addProduct()`` or
+``$category->removeProduct()`` to update the database? Actually, it *is* possible,
+thanks to some clever code that the ``make:entity`` command generated::
// src/Entity/Category.php
@@ -451,23 +516,22 @@ method, create a ``addProduct()`` method::
{
// ...
- public function addProduct(Product $product)
+ public function addProduct(Product $product): self
{
- if ($this->products->contains($product)) {
- return;
+ if (!$this->products->contains($product)) {
+ $this->products[] = $product;
+ $product->setCategory($this);
}
- $this->products[] = $product;
- // set the *owning* side!
- $product->setCategory($this);
+ return $this;
}
}
-That's it! The *key* is ``$product->setCategory($this)``, which sets the *owning*
-side. Now, when you save, the relationship *will* update in the database.
+The *key* is ``$product->setCategory($this)``, which sets the *owning* side. Thanks,
+to this, when you save, the relationship *will* update in the database.
-What about *removing* a ``Product`` from a ``Category``? Add a ``removeProduct()``
-method::
+What about *removing* a ``Product`` from a ``Category``? The ``make:entity`` command
+also generated a ``removeProduct()`` method::
// src/Entity/Category.php
@@ -476,37 +540,21 @@ method::
{
// ...
- public function removeProduct(Product $product)
+ public function removeProduct(Product $product): self
{
- $this->products->removeElement($product);
- // set the owning side to null
- $product->setCategory(null);
- }
- }
-
-To make this work, you *now* need to allow ``null`` to be passed to ``Product::setCategory()``:
-
-.. code-block:: diff
-
- // src/Entity/Product.php
-
- // ...
- class Product
- {
- // ...
-
- - public function getCategory(): Category
- + public function getCategory(): ?Category
- // ...
+ if ($this->products->contains($product)) {
+ $this->products->removeElement($product);
+ // set the owning side to null (unless already changed)
+ if ($product->getCategory() === $this) {
+ $product->setCategory(null);
+ }
+ }
- - public function setCategory(Category $category)
- + public function setCategory(Category $category = null)
- {
- $this->category = $category;
+ return $this;
}
}
-And that's it! Now, if you call ``$category->removeProduct($product)``, the ``category_id``
+Thanks to this, if you call ``$category->removeProduct($product)``, the ``category_id``
on that ``Product`` will be set to ``null`` in the database.
But, instead of setting the ``category_id`` to null, what if you want the ``Product``
diff --git a/doctrine/dbal.rst b/doctrine/dbal.rst
index 4620f52a147..269cd529691 100644
--- a/doctrine/dbal.rst
+++ b/doctrine/dbal.rst
@@ -21,7 +21,13 @@ makes it easy to execute queries and perform other database actions.
Read the official Doctrine `DBAL Documentation`_ to learn all the details
and capabilities of Doctrine's DBAL library.
-To get started, configure the ``DATABASE_URL`` environment variable in ``.env``:
+First, install the Doctrine bundle:
+
+.. code-block:: terminal
+
+ composer require doctrine/doctrine-bundle
+
+Then configure the ``DATABASE_URL`` environment variable in ``.env``:
.. code-block:: text
@@ -41,9 +47,9 @@ object::
class UserController extends Controller
{
- public function indexAction(Connection $conn)
+ public function indexAction(Connection $connection)
{
- $users = $conn->fetchAll('SELECT * FROM users');
+ $users = $connection->fetchAll('SELECT * FROM users');
// ...
}
@@ -152,7 +158,7 @@ mapping type:
),
));
-.. _`PDO`: http://www.php.net/pdo
+.. _`PDO`: https://php.net/pdo
.. _`Doctrine`: http://www.doctrine-project.org
.. _`DBAL Documentation`: http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html
.. _`Custom Mapping Types`: http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types
diff --git a/doctrine/event_listeners_subscribers.rst b/doctrine/event_listeners_subscribers.rst
index 208cce897a0..694c9dbd1f8 100644
--- a/doctrine/event_listeners_subscribers.rst
+++ b/doctrine/event_listeners_subscribers.rst
@@ -78,7 +78,7 @@ managers that use this connection.
$container->autowire(SearchIndexer2::class)
->addTag('doctrine.event_listener', array(
'event' => 'postPersist',
- 'connection' => 'default'
+ 'connection' => 'default',
))
;
$container->autowire(SearchIndexerSubscriber::class)
diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst
index bc958e0d0f9..4b09346871b 100644
--- a/doctrine/multiple_entity_managers.rst
+++ b/doctrine/multiple_entity_managers.rst
@@ -235,17 +235,17 @@ the default entity manager (i.e. ``default``) is returned::
class UserController extends Controller
{
- public function indexAction(EntityManagerInterface $em)
+ public function indexAction(EntityManagerInterface $entityManager)
{
// These methods also return the default entity manager, but it's preferred
- // to get it by inyecting EntityManagerInterface in the action method
- $em = $this->getDoctrine()->getManager();
- $em = $this->getDoctrine()->getManager('default');
- $em = $this->get('doctrine.orm.default_entity_manager');
+ // to get it by injecting EntityManagerInterface in the action method
+ $entityManager = $this->getDoctrine()->getManager();
+ $entityManager = $this->getDoctrine()->getManager('default');
+ $entityManager = $this->get('doctrine.orm.default_entity_manager');
// Both of these return the "customer" entity manager
- $customerEm = $this->getDoctrine()->getManager('customer');
- $customerEm = $this->get('doctrine.orm.customer_entity_manager');
+ $customerEntityManager = $this->getDoctrine()->getManager('customer');
+ $customerEntityManager = $this->get('doctrine.orm.customer_entity_manager');
}
}
diff --git a/doctrine/pdo_session_storage.rst b/doctrine/pdo_session_storage.rst
index 6ce018432bd..cb2c96ddec3 100644
--- a/doctrine/pdo_session_storage.rst
+++ b/doctrine/pdo_session_storage.rst
@@ -70,7 +70,7 @@ Next, tell Symfony to use your service as the session handler:
.. code-block:: yaml
- # config/packages/doctrine.yaml
+ # config/packages/framework.yaml
framework:
session:
# ...
@@ -78,7 +78,7 @@ Next, tell Symfony to use your service as the session handler:
.. code-block:: xml
-
+
@@ -86,7 +86,7 @@ Next, tell Symfony to use your service as the session handler:
.. code-block:: php
- // config/packages/doctrine.php
+ // config/packages/framework.php
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
// ...
@@ -153,7 +153,7 @@ a second array argument to ``PdoSessionHandler``:
))
;
-These are parameters that you must configure:
+These are parameters that you can configure:
``db_table`` (default ``sessions``):
The name of the session table in your database;
@@ -176,8 +176,18 @@ Preparing the Database to Store Sessions
----------------------------------------
Before storing sessions in the database, you must create the table that stores
-the information. The following sections contain some examples of the SQL statements
-you may use for your specific database engine.
+the information. The session handler provides a method called
+:method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler::createTable`
+to set up this table for you according to the database engine used::
+
+ try {
+ $sessionHandlerService->createTable();
+ } catch (\PDOException $exception) {
+ // the table could not be created for some reason
+ }
+
+If you prefer to set up the table yourself, these are some examples of the SQL
+statements you may use according to your specific database engine.
A great way to run this on production is to generate an empty migration, and then
add this SQL inside:
diff --git a/doctrine/registration_form.rst b/doctrine/registration_form.rst
index 917c48754dd..49b5d33f3c1 100644
--- a/doctrine/registration_form.rst
+++ b/doctrine/registration_form.rst
@@ -138,7 +138,7 @@ With some validation added, your class may look something like this::
public function getSalt()
{
- // The bcrypt algorithm doesn't require a separate salt.
+ // The bcrypt and argon2i algorithms don't require a separate salt.
// You *may* need a real salt if you choose a different encoder.
return null;
}
@@ -253,9 +253,9 @@ into the database::
$user->setPassword($password);
// 4) save the User!
- $em = $this->getDoctrine()->getManager();
- $em->persist($user);
- $em->flush();
+ $entityManager = $this->getDoctrine()->getManager();
+ $entityManager->persist($user);
+ $entityManager->flush();
// ... do any other work - like sending them an email, etc
// maybe set a "flash" success message for the user
diff --git a/doctrine/reverse_engineering.rst b/doctrine/reverse_engineering.rst
index a77b8c48b34..e0f147c5b58 100644
--- a/doctrine/reverse_engineering.rst
+++ b/doctrine/reverse_engineering.rst
@@ -4,6 +4,14 @@
How to Generate Entities from an Existing Database
==================================================
+.. caution::
+
+ The feature explained in this article doesn't work in modern Symfony
+ applications that have no bundles. The workaround is to temporarily create
+ a bundle. See `doctrine/doctrine#729`_ for details. Moreover, this feature
+ to generate entities from existing databases will be completely removed in
+ the next Doctrine version.
+
When starting work on a brand new project that uses a database, two different
situations comes naturally. In most cases, the database model is designed
and built from scratch. Sometimes, however, you'll start with an existing and
@@ -54,12 +62,6 @@ is to ask Doctrine to introspect the database and generate the corresponding
metadata files. Metadata files describe the entity class to generate based on
table fields.
-.. caution::
-
- If your application has *no* bundles, then this command will currently fail!
- The workaround, is to temporarily create a bundle. See `doctrine/doctrine#729`_
- for details.
-
.. code-block:: terminal
$ php bin/console doctrine:mapping:import --force AppBundle xml
diff --git a/email/dev_environment.rst b/email/dev_environment.rst
index ce659a533aa..9938b976b84 100644
--- a/email/dev_environment.rst
+++ b/email/dev_environment.rst
@@ -207,13 +207,13 @@ the report with details of the sent emails.
.. code-block:: yaml
- # config/packages/dev/swiftmailer.yaml
+ # config/packages/dev/web_profiler.yaml
web_profiler:
intercept_redirects: true
.. code-block:: xml
-
+
loadFromExtension('web_profiler', array(
'intercept_redirects' => 'true',
));
diff --git a/email/spool.rst b/email/spool.rst
index 9c6ad04843e..e3fc88fe8e4 100644
--- a/email/spool.rst
+++ b/email/spool.rst
@@ -54,7 +54,7 @@ this spool, use the following configuration:
// config/packages/swiftmailer.php
$container->loadFromExtension('swiftmailer', array(
// ...
- 'spool' => array('type' => 'memory')
+ 'spool' => array('type' => 'memory'),
));
.. _spool-using-a-file:
diff --git a/email/testing.rst b/email/testing.rst
index 477db8f5474..77d65540eff 100644
--- a/email/testing.rst
+++ b/email/testing.rst
@@ -39,25 +39,25 @@ to get information about the messages sent on the previous request::
{
$client = static::createClient();
- // Enable the profiler for the next request (it does nothing if the profiler is not available)
+ // enables the profiler for the next request (it does nothing if the profiler is not available)
$client->enableProfiler();
$crawler = $client->request('POST', '/path/to/above/action');
$mailCollector = $client->getProfile()->getCollector('swiftmailer');
- // Check that an email was sent
- $this->assertEquals(1, $mailCollector->getMessageCount());
+ // checks that an email was sent
+ $this->assertSame(1, $mailCollector->getMessageCount());
$collectedMessages = $mailCollector->getMessages();
$message = $collectedMessages[0];
// Asserting email data
$this->assertInstanceOf('Swift_Message', $message);
- $this->assertEquals('Hello Email', $message->getSubject());
- $this->assertEquals('send@example.com', key($message->getFrom()));
- $this->assertEquals('recipient@example.com', key($message->getTo()));
- $this->assertEquals(
+ $this->assertSame('Hello Email', $message->getSubject());
+ $this->assertSame('send@example.com', key($message->getFrom()));
+ $this->assertSame('recipient@example.com', key($message->getTo()));
+ $this->assertSame(
'You should see me from the profiler!',
$message->getBody()
);
diff --git a/event_dispatcher.rst b/event_dispatcher.rst
index 1c59e6711a5..a75e072f6d7 100644
--- a/event_dispatcher.rst
+++ b/event_dispatcher.rst
@@ -55,7 +55,7 @@ The most common way to listen to an event is to register an **event listener**::
$response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
}
- // Send the modified response object to the event
+ // sends the modified response object to the event
$event->setResponse($response);
}
}
@@ -91,7 +91,7 @@ using a special "tag":
http://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
diff --git a/event_dispatcher/method_behavior.rst b/event_dispatcher/method_behavior.rst
index 0577989350e..b99dbb748d2 100644
--- a/event_dispatcher/method_behavior.rst
+++ b/event_dispatcher/method_behavior.rst
@@ -26,10 +26,10 @@ method::
$message = $event->getMessage();
// the real method implementation is here
- $ret = ...;
+ $returnValue = ...;
// do something after the method
- $event = new AfterSendMailEvent($ret);
+ $event = new AfterSendMailEvent($returnValue);
$this->dispatcher->dispatch('mailer.post_send', $event);
return $event->getReturnValue();
@@ -121,10 +121,10 @@ could listen to the ``mailer.post_send`` event and change the method's return va
{
public function onMailerPostSend(AfterSendMailEvent $event)
{
- $ret = $event->getReturnValue();
- // modify the original ``$ret`` value
+ $returnValue = $event->getReturnValue();
+ // modify the original ``$returnValue`` value
- $event->setReturnValue($ret);
+ $event->setReturnValue($returnValue);
}
public static function getSubscribedEvents()
diff --git a/form/bootstrap4.rst b/form/bootstrap4.rst
new file mode 100644
index 00000000000..6831ef775b2
--- /dev/null
+++ b/form/bootstrap4.rst
@@ -0,0 +1,116 @@
+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 ``
+
.. tip::
diff --git a/reference/forms/types/date.rst b/reference/forms/types/date.rst
index a4af74f8ce5..feca602bac1 100644
--- a/reference/forms/types/date.rst
+++ b/reference/forms/types/date.rst
@@ -76,7 +76,7 @@ use the ``single_text`` widget::
// ...
$builder->add('publishedAt', DateType::class, array(
- // render as a single text box
+ // renders it as a single text box
'widget' => 'single_text',
));
@@ -94,10 +94,10 @@ make the following changes::
$builder->add('publishedAt', DateType::class, array(
'widget' => 'single_text',
- // do not render as type="date", to avoid HTML5 date pickers
+ // prevents rendering it as type="date", to avoid HTML5 date pickers
'html5' => false,
- // add a class that can be selected in JavaScript
+ // adds a class that can be selected in JavaScript
'attr' => ['class' => 'js-datepicker'],
));
@@ -152,7 +152,7 @@ values for the year, month and day fields::
$builder->add('dueDate', DateType::class, array(
'placeholder' => array(
- 'year' => 'Year', 'month' => 'Month', 'day' => 'Day'
+ 'year' => 'Year', 'month' => 'Month', 'day' => 'Day',
)
));
diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst
index 4345d5fe9a8..d802bb39f85 100644
--- a/reference/forms/types/datetime.rst
+++ b/reference/forms/types/datetime.rst
@@ -213,4 +213,4 @@ Field Variables
| | | contains the input type to use (``datetime``, ``date`` or ``time``). |
+----------+------------+----------------------------------------------------------------------+
-.. _`RFC 3339`: http://tools.ietf.org/html/rfc3339
+.. _`RFC 3339`: https://tools.ietf.org/html/rfc3339
diff --git a/reference/forms/types/entity.rst b/reference/forms/types/entity.rst
index 75d6597ffed..c323d4a2b6b 100644
--- a/reference/forms/types/entity.rst
+++ b/reference/forms/types/entity.rst
@@ -62,10 +62,10 @@ be listed inside the choice field::
// ...
$builder->add('users', EntityType::class, array(
- // query choices from this entity
+ // looks for choices from this entity
'class' => User::class,
- // use the User.username property as the visible option string
+ // uses the User.username property as the visible option string
'choice_label' => 'username',
// used to render a select box, check boxes or radios
diff --git a/reference/forms/types/file.rst b/reference/forms/types/file.rst
index 9a62063eaf1..85e8c20f275 100644
--- a/reference/forms/types/file.rst
+++ b/reference/forms/types/file.rst
@@ -53,7 +53,7 @@ be used to move the ``attachment`` file to a permanent location::
$someNewFilename = ...
$file = $form['attachment']->getData();
- $file->move($dir, $someNewFilename);
+ $file->move($directory, $someNewFilename);
// ...
}
@@ -65,7 +65,7 @@ The ``move()`` method takes a directory and a file name as its arguments.
You might calculate the filename in one of the following ways::
// use the original file name
- $file->move($dir, $file->getClientOriginalName());
+ $file->move($directory, $file->getClientOriginalName());
// compute a random name and try to guess the extension (more secure)
$extension = $file->guessExtension();
@@ -73,7 +73,7 @@ You might calculate the filename in one of the following ways::
// extension cannot be guessed
$extension = 'bin';
}
- $file->move($dir, rand(1, 99999).'.'.$extension);
+ $file->move($directory, rand(1, 99999).'.'.$extension);
Using the original name via ``getClientOriginalName()`` is not safe as it
could have been manipulated by the end-user. Moreover, it can contain
diff --git a/reference/forms/types/options/_date_limitation.rst.inc b/reference/forms/types/options/_date_limitation.rst.inc
index 4178e4dbc51..e715a5f5430 100644
--- a/reference/forms/types/options/_date_limitation.rst.inc
+++ b/reference/forms/types/options/_date_limitation.rst.inc
@@ -2,4 +2,4 @@
If ``timestamp`` is used, ``DateType`` is limited to dates between
Fri, 13 Dec 1901 20:45:54 GMT and Tue, 19 Jan 2038 03:14:07 GMT on 32bit
- systems. This is due to a `limitation in PHP itself `_.
+ systems. This is due to a `limitation in PHP itself `_.
diff --git a/reference/forms/types/options/choice_value.rst.inc b/reference/forms/types/options/choice_value.rst.inc
index f06844c0f6a..39a9dcc43f8 100644
--- a/reference/forms/types/options/choice_value.rst.inc
+++ b/reference/forms/types/options/choice_value.rst.inc
@@ -13,9 +13,7 @@ integer is used as the value.
If you pass a callable, it will receive one argument: the choice itself. When using
the :doc:`/reference/forms/types/entity`, the argument will be the entity object
-for each choice or ``null`` in some cases, which you need to handle:
-
-.. code-block:: php
+for each choice or ``null`` in some cases, which you need to handle::
'choice_value' => function (MyOptionEntity $entity = null) {
return $entity ? $entity->getId() : '';
diff --git a/reference/forms/types/options/compound.rst.inc b/reference/forms/types/options/compound.rst.inc
index 7736a77567c..10fe4d35a9e 100644
--- a/reference/forms/types/options/compound.rst.inc
+++ b/reference/forms/types/options/compound.rst.inc
@@ -3,6 +3,22 @@ compound
**type**: ``boolean`` **default**: ``true``
-This option specifies if a form is compound. This is independent of whether
-the form actually has children. A form can be compound but not have any
-children at all (e.g. an empty collection form).
+If ``true`` this option creates the form as "compound", meaning that it
+can contain children and be a parent of other forms.
+
+Most of the time you won't need to override this option.
+You might want to control for it when creating a custom form type
+with advanced rendering logic.
+
+In a view a compound form is rendered as a ``
`` container or
+a ``
+
+Redirecting to the Last Accessed Page with ``TargetPathTrait``
+--------------------------------------------------------------
+
+The last request URI is stored in a session variable named
+``_security..target_path`` (e.g. ``_security.main.target_path``
+if the name of your firewall is ``main``). Most of the times you don't have to
+deal with this low level session variable. However, if you ever need to get or
+remove this variable, it's better to use the
+:class:`Symfony\\Component\\Security\\Http\\Util\\TargetPathTrait` utility::
+
+ // ...
+ use Symfony\Component\Security\Http\Util\TargetPathTrait;
+
+ $targetPath = $this->getTargetPath($request->getSession(), $providerKey);
+
+ // equivalent to:
+ // $targetPath = $request->getSession()->get('_security.'.$providerKey.'.target_path');
diff --git a/security/form_login_setup.rst b/security/form_login_setup.rst
index cbb88155066..ad76bef4033 100644
--- a/security/form_login_setup.rst
+++ b/security/form_login_setup.rst
@@ -132,25 +132,25 @@ configuration (``login``):
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
- $collection = new RouteCollection();
- $collection->add('login', new Route('/login', array(
+ $routes = new RouteCollection();
+ $routes->add('login', new Route('/login', array(
'_controller' => array(SecurityController::class, 'login'),
)));
- return $collection;
+ return $routes;
Great! Next, add the logic to ``login()`` that displays the login form::
// src/Controller/SecurityController.php
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
- public function login(Request $request, AuthenticationUtils $authUtils)
+ public function login(Request $request, AuthenticationUtils $authenticationUtils)
{
// get the login error if there is one
- $error = $authUtils->getLastAuthenticationError();
+ $error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
- $lastUsername = $authUtils->getLastUsername();
+ $lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', array(
'last_username' => $lastUsername,
@@ -158,6 +158,13 @@ Great! Next, add the logic to ``login()`` that displays the login form::
));
}
+.. note::
+
+ If you get an error that the ``$authenticationUtils`` argument is missing,
+ it's probably because the controllers of your application are not defined as
+ services and tagged with the ``controller.service_arguments`` tag, as done
+ in the :ref:`default services.yaml configuration `.
+
Don't let this controller confuse you. As you'll see in a moment, when the
user submits the form, the security system automatically handles the form
submission for you. If the user submits an invalid username or password,
diff --git a/security/guard_authentication.rst b/security/guard_authentication.rst
index b0f8342be71..a5b1f86a752 100644
--- a/security/guard_authentication.rst
+++ b/security/guard_authentication.rst
@@ -404,7 +404,7 @@ Each authenticator needs the following methods:
**supportsRememberMe()**
If you want to support "remember me" functionality, return true from this method.
- You will still need to active ``remember_me`` under your firewall for it to work.
+ You will still need to activate ``remember_me`` under your firewall for it to work.
Since this is a stateless API, you do not want to support "remember me"
functionality in this example.
@@ -417,8 +417,9 @@ Each authenticator needs the following methods:
The picture below shows how Symfony calls Guard Authenticator methods:
-.. image:: /_images/security/authentication-guard-methods.png
- :align: center
+.. raw:: html
+
+
.. _guard-customize-error:
@@ -426,8 +427,8 @@ Customizing Error Messages
--------------------------
When ``onAuthenticationFailure()`` is called, it is passed an ``AuthenticationException``
-that describes *how* authentication failed via its ``$e->getMessageKey()`` (and
-``$e->getMessageData()``) method. The message will be different based on *where*
+that describes *how* authentication failed via its ``$exception->getMessageKey()`` (and
+``$exception->getMessageData()``) method. The message will be different based on *where*
authentication fails (i.e. ``getUser()`` versus ``checkCredentials()``).
But, you can easily return a custom message by throwing a
diff --git a/security/impersonating_user.rst b/security/impersonating_user.rst
index 4baa75aeb13..b4b70e8d9cd 100644
--- a/security/impersonating_user.rst
+++ b/security/impersonating_user.rst
@@ -109,12 +109,12 @@ over the user's roles until you find one that a ``SwitchUserRole`` object::
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Role\SwitchUserRole;
- private $authChecker;
+ private $authorizationChecker;
private $tokenStorage;
- public function __construct(AuthorizationCheckerInterface $authChecker, TokenStorageInterface $tokenStorage)
+ public function __construct(AuthorizationCheckerInterface $authorizationChecker, TokenStorageInterface $tokenStorage)
{
- $this->authChecker = $authChecker;
+ $this->authorizationChecker = $authorizationChecker;
$this->tokenStorage = $tokenStorage;
}
@@ -122,7 +122,7 @@ over the user's roles until you find one that a ``SwitchUserRole`` object::
{
// ...
- if ($authChecker->isGranted('ROLE_PREVIOUS_ADMIN')) {
+ if ($authorizationChecker->isGranted('ROLE_PREVIOUS_ADMIN')) {
foreach ($tokenStorage->getToken()->getRoles() as $role) {
if ($role instanceof SwitchUserRole) {
$impersonatorUser = $role->getSource()->getUser();
@@ -198,32 +198,32 @@ The :doc:`/session/locale_sticky_session` article does not update the locale
when you impersonate a user. If you *do* want to be sure to update the locale when
you switch users, add an event subscriber on this event::
- // src/EventListener/SwitchUserListener.php
- namespace App\EventListener;
+ // src/EventListener/SwitchUserListener.php
+ namespace App\EventListener;
- use Symfony\Component\Security\Http\Event\SwitchUserEvent;
- use Symfony\Component\EventDispatcher\EventSubscriberInterface;
- use Symfony\Component\Security\Http\SecurityEvents;
+ use Symfony\Component\Security\Http\Event\SwitchUserEvent;
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+ use Symfony\Component\Security\Http\SecurityEvents;
- class SwitchUserSubscriber implements EventSubscriberInterface
+ class SwitchUserSubscriber implements EventSubscriberInterface
+ {
+ public function onSwitchUser(SwitchUserEvent $event)
{
- public function onSwitchUser(SwitchUserEvent $event)
- {
- $event->getRequest()->getSession()->set(
- '_locale',
- // assuming your User has some getLocale() method
- $event->getTargetUser()->getLocale()
- );
- }
+ $event->getRequest()->getSession()->set(
+ '_locale',
+ // assuming your User has some getLocale() method
+ $event->getTargetUser()->getLocale()
+ );
+ }
- public static function getSubscribedEvents()
- {
- return array(
- // constant for security.switch_user
- SecurityEvents::SWITCH_USER => 'onSwitchUser',
- );
- }
+ public static function getSubscribedEvents()
+ {
+ return array(
+ // constant for security.switch_user
+ SecurityEvents::SWITCH_USER => 'onSwitchUser',
+ );
}
+ }
That's it! If you're using the :ref:`default services.yaml configuration `,
Symfony will automatically discover your service and call ``onSwitchUser`` whenever
diff --git a/security/json_login_setup.rst b/security/json_login_setup.rst
index 07a295f3194..ee2ebad7a46 100644
--- a/security/json_login_setup.rst
+++ b/security/json_login_setup.rst
@@ -69,6 +69,7 @@ path:
// src/Controller/SecurityController.php
// ...
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
@@ -109,12 +110,12 @@ path:
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
- $collection = new RouteCollection();
- $collection->add('login', new Route('/login', array(
+ $routes = new RouteCollection();
+ $routes->add('login', new Route('/login', array(
'_controller' => 'App\Controller\SecurityController::login',
)));
- return $collection;
+ return $routes;
Don't let this empty controller confuse you. When you submit a ``POST`` request
to the ``/login`` URL with the following JSON document as the body, the security
diff --git a/security/named_encoders.rst b/security/named_encoders.rst
index 052dea4db5b..f9137037b94 100644
--- a/security/named_encoders.rst
+++ b/security/named_encoders.rst
@@ -96,11 +96,17 @@ named encoders:
'encoders' => array(
'harsh' => array(
'algorithm' => 'bcrypt',
- 'cost' => '15'
+ 'cost' => '15',
),
),
));
+.. note::
+
+ If you are running PHP 7.2+ or have the `libsodium`_ extension installed,
+ then the recommended hashing algorithm to use is
+ :ref:`Argon2i `.
+
This creates an encoder named ``harsh``. In order for a ``User`` instance
to use it, the class must implement
:class:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderAwareInterface`.
@@ -168,10 +174,12 @@ you must register a service for it in order to use it as a named encoder:
// ...
'encoders' => array(
'app_encoder' => array(
- 'id' => MyCustomPasswordEncoder::class
+ 'id' => MyCustomPasswordEncoder::class,
),
),
));
This creates an encoder named ``app_encoder`` from a service with the ID
``App\Security\Encoder\MyCustomPasswordEncoder``.
+
+.. _`libsodium`: https://pecl.php.net/package/libsodium
diff --git a/security/remember_me.rst b/security/remember_me.rst
index 1021168cdd2..4ad73df4c64 100644
--- a/security/remember_me.rst
+++ b/security/remember_me.rst
@@ -236,9 +236,7 @@ by securing specific controller actions using these roles. The edit action
in the controller could be secured using the service context.
In the following example, the action is only allowed if the user has the
-``IS_AUTHENTICATED_FULLY`` role.
-
-.. code-block:: php
+``IS_AUTHENTICATED_FULLY`` role::
// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -251,10 +249,8 @@ In the following example, the action is only allowed if the user has the
// ...
}
-If your application is based on the Symfony Standard Edition, you can also secure
-your controller using annotations:
-
-.. code-block:: php
+If you have installed `SensioFrameworkExtraBundle`_ in your application, you can also secure
+your controller using annotations::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
@@ -288,3 +284,5 @@ your controller using annotations:
For more information on securing services or methods in this way,
see :doc:`/security/securing_services`.
+
+.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
diff --git a/security/securing_services.rst b/security/securing_services.rst
index 0b24092a7d6..3c2c07131f6 100644
--- a/security/securing_services.rst
+++ b/security/securing_services.rst
@@ -81,15 +81,3 @@ thanks to autowiring and the ``AuthorizationCheckerInterface`` type-hint.
If the current user does not have the ``ROLE_NEWSLETTER_ADMIN``, they will
be prompted to log in.
-
-Securing Methods Using Annotations
-----------------------------------
-
-You can also secure method calls in any service with annotations by using the
-optional `JMSSecurityExtraBundle`_ bundle. This bundle is not included in the
-Symfony Standard Distribution, but you can choose to install it.
-
-See the `JMSSecurityExtraBundle Documentation`_ for more details.
-
-.. _`JMSSecurityExtraBundle`: https://github.com/schmittjoh/JMSSecurityExtraBundle
-.. _`JMSSecurityExtraBundle Documentation`: http://jmsyst.com/bundles/JMSSecurityExtraBundle
diff --git a/security/voters.rst b/security/voters.rst
index 5f4b7f8b654..ec817f5b6c2 100644
--- a/security/voters.rst
+++ b/security/voters.rst
@@ -34,9 +34,7 @@ The Voter Interface
A custom voter needs to implement
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`
or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\Voter`,
-which makes creating a voter even easier.
-
-.. code-block:: php
+which makes creating a voter even easier::
abstract class Voter implements VoterInterface
{
@@ -278,7 +276,9 @@ strategies available:
This grants access if there are more voters granting access than denying;
``unanimous``
- This only grants access once *all* voters grant access.
+ This only grants access if there is no voter denying access. If all voters
+ abstained from voting, the decision is based on the ``allow_if_all_abstain``
+ config option (which defaults to ``false``).
In the above scenario, both voters should grant access in order to grant access
to the user to read the post. In this case, the default strategy is no longer
@@ -293,6 +293,7 @@ security configuration:
security:
access_decision_manager:
strategy: unanimous
+ allow_if_all_abstain: false
.. code-block:: xml
@@ -306,7 +307,7 @@ security configuration:
>
-
+
@@ -316,5 +317,6 @@ security configuration:
$container->loadFromExtension('security', array(
'access_decision_manager' => array(
'strategy' => 'unanimous',
+ 'allow_if_all_abstain' => false,
),
));
diff --git a/serializer.rst b/serializer.rst
index 62cc471d12a..4fa6470227a 100644
--- a/serializer.rst
+++ b/serializer.rst
@@ -58,11 +58,11 @@ As well as the following normalizers:
* :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` to
handle typical data objects
* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer` for
- objects implementing the :class:`DateTimeInterface` interface
+ objects implementing the :phpclass:`DateTimeInterface` interface
* :class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` to
- transform :class:`SplFileInfo` objects in `Data URIs`_
+ transform :phpclass:`SplFileInfo` objects in `Data URIs`_
* :class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer`
- to deal with objects implementing the :class:`JsonSerializable` interface
+ to deal with objects implementing the :phpclass:`JsonSerializable` interface
* :class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer` to
denormalize arrays of objects using a format like `MyObject[]` (note the `[]` suffix)
@@ -74,7 +74,8 @@ possible to set the priority of the tag in order to decide the matching order.
Here is an example on how to load the
:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`, a
faster alternative to the `ObjectNormalizer` when data objects always use
-getters and setters:
+getters (``getXxx()``), issers (``isXxx()``) or hassers (``hasXxx()``) to read
+properties and setters (``setXxx()``) to change properties:
.. configuration-block::
diff --git a/service_container.rst b/service_container.rst
index 589c9484455..149481792eb 100644
--- a/service_container.rst
+++ b/service_container.rst
@@ -148,7 +148,7 @@ each time you ask for it.
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
- exclude: '../src/{Entity,Migrations,Tests}'
+ exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
# ...
@@ -289,14 +289,13 @@ made. To do that, you create a new class::
{
$happyMessage = $this->messageGenerator->getHappyMessage();
- $message = \Swift_Message::newInstance()
- ->setSubject('Site update just happened!')
+ $message = (new \Swift_Message('Site update just happened!'))
->setFrom('admin@example.com')
->setTo('manager@example.com')
->addPart(
'Someone just updated the site. We told them: '.$happyMessage
);
-
+
return $this->mailer->send($message) > 0;
}
}
@@ -317,7 +316,7 @@ you can type-hint the new ``SiteUpdateManager`` class and use it::
if ($siteUpdateManager->notifyOfSiteUpdate()) {
$this->addFlash('success', 'Notification mail was sent successfully.');
}
-
+
// ...
}
@@ -863,6 +862,38 @@ them will not cause the container to be rebuilt.
means that all classes are "available to be *used* as services" without needing
to be manually configured.
+Multiple Service Definitions Using the Same Namespace
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you define services using the YAML config format, the PHP namespace is used
+as the key of each configuration, so you can't define different service configs
+for classes under the same namespace:
+
+.. code-block:: yaml
+
+ # app/config/services.yml
+ services:
+ App\Domain\:
+ resource: '../../src/Domain/*'
+ # ...
+
+In order to have multiple definitions, add the ``namespace`` option and use any
+unique string as the key of each service config:
+
+.. code-block:: yaml
+
+ # app/config/services.yml
+ services:
+ command_handlers:
+ namespace: App\Domain\
+ resource: '../../src/Domain/*/CommandHandler'
+ tags: [command_handler]
+
+ event_subscribers:
+ namespace: App\Domain\
+ resource: '../../src/Domain/*/EventSubscriber'
+ tags: [event_subscriber]
+
.. _services-explicitly-configure-wire-services:
Explicitly Configuring Services and Arguments
diff --git a/service_container/3.3-di-changes.rst b/service_container/3.3-di-changes.rst
index 29d5f65cfb2..10529d5fa0f 100644
--- a/service_container/3.3-di-changes.rst
+++ b/service_container/3.3-di-changes.rst
@@ -81,32 +81,6 @@ what the file looks like in Symfony 4):
- .. code-block:: php
-
- // config/services.php
- use Symfony\Component\DependencyInjection\Definition;
-
- // To use as default template
- $definition = new Definition();
-
- $definition
- ->setAutowired(true)
- ->setAutoconfigured(true)
- ->setPublic(false)
- ;
-
- $this->registerClasses($definition, 'App\\', '../src/*', '../src/{Entity,Migrations,Tests}');
-
- // Changes default config
- $definition
- ->addTag('controller.service_arguments')
- ;
-
- // $this is a reference to the current loader
- $this->registerClasses($definition, 'App\\Controller\\', '../src/Controller/*');
-
- // add more services, or override services that need manual wiring
-
This small bit of configuration contains a paradigm shift of how services
are configured in Symfony.
@@ -152,22 +126,6 @@ thanks to the following config:
- .. code-block:: php
-
- // config/services.php
- use Symfony\Component\DependencyInjection\Definition;
-
- // To use as default template
- $definition = new Definition();
-
- $definition
- ->setAutowired(true)
- ->setAutoconfigured(true)
- ->setPublic(false)
- ;
-
- $this->registerClasses($definition, 'App\\', '../src/*', '../src/{Entity,Migrations,Tests}');
-
This means that every class in ``src/`` is *available* to be used as a
service. And thanks to the ``_defaults`` section at the top of the file, all of
these services are **autowired** and **private** (i.e. ``public: false``).
@@ -504,18 +462,6 @@ inherited from an abstract definition:
- .. code-block:: php
-
- // config/services.php
- use App\Domain\LoaderInterface;
-
- // ...
-
- /* This method returns a child definition to define the default
- configuration of the given class or interface */
- $container->registerForAutoconfiguration(LoaderInterface::class)
- ->addTag('app.domain_loader');
-
What about Performance
----------------------
diff --git a/service_container/alias_private.rst b/service_container/alias_private.rst
index e41229c56f3..8c87b31f793 100644
--- a/service_container/alias_private.rst
+++ b/service_container/alias_private.rst
@@ -68,17 +68,15 @@ gives you better errors: if you try to reference a non-existent service, you wil
get a clear error when you refresh *any* page, even if the problematic code would
not have run on that page.
-Now that the service is private, you *should not* fetch the service directly
+Now that the service is private, you *must not* fetch the service directly
from the container::
use App\Service\Foo;
$container->get(Foo::class);
-This *may or may not work*, depending on how the container has optimized the
-service instantiation and, even in the cases where it works, this possibility is
-deprecated. Simply said: A service should be marked as private if you do not want
-to access it directly from your code.
+Simply said: A service can be marked as private if you do not want to access
+it directly from your code.
However, if a service has been marked as private, you can still alias it
(see below) to access this service (via the alias).
diff --git a/service_container/configurators.rst b/service_container/configurators.rst
index 7f2817e0a56..9892e7aa377 100644
--- a/service_container/configurators.rst
+++ b/service_container/configurators.rst
@@ -184,16 +184,16 @@ all the classes are already loaded as services. All you need to do is specify th
$container->getDefinition(GreetingCardManager::class)
->setConfigurator(array(new Reference(EmailConfigurator::class), 'configure'));
- The traditional configurator syntax in YAML files used an array to define
- the service id and the method name:
+The traditional configurator syntax in YAML files used an array to define
+the service id and the method name:
- .. code-block:: yaml
+.. code-block:: yaml
- app.newsletter_manager:
- # new syntax
- configurator: 'App\Mail\EmailConfigurator:configure'
- # old syntax
- configurator: ['@App\Mail\EmailConfigurator', configure]
+ app.newsletter_manager:
+ # new syntax
+ configurator: 'App\Mail\EmailConfigurator:configure'
+ # old syntax
+ configurator: ['@App\Mail\EmailConfigurator', configure]
That's it! When requesting the ``App\Mail\NewsletterManager`` or
``App\Mail\GreetingCardManager`` service, the created instance will first be
diff --git a/service_container/definitions.rst b/service_container/definitions.rst
index af4b90fc04c..7462f6639f9 100644
--- a/service_container/definitions.rst
+++ b/service_container/definitions.rst
@@ -21,17 +21,17 @@ Getting and Setting Service Definitions
There are some helpful methods for working with the service definitions::
- // find out if there is an "app.mailer" definition
+ // finds out if there is an "app.mailer" definition
$container->hasDefinition('app.mailer');
- // find out if there is an "app.mailer" definition or alias
+ // finds out if there is an "app.mailer" definition or alias
$container->has('app.mailer');
- // get the "app.user_config_manager" definition
+ // gets the "app.user_config_manager" definition
$definition = $container->getDefinition('app.user_config_manager');
- // get the definition with the "app.user_config_manager" ID or alias
+ // gets the definition with the "app.user_config_manager" ID or alias
$definition = $container->findDefinition('app.user_config_manager');
- // add a new "app.number_generator" definition
+ // adds a new "app.number_generator" definition
$definition = new Definition(\App\NumberGenerator::class);
$container->setDefinition('app.number_generator', $definition);
@@ -82,19 +82,19 @@ fetched from the container::
$definition = new Definition(DoctrineConfigManager::class, array(
new Reference('doctrine'), // a reference to another service
- '%app.config_table_name%' // will be resolved to the value of a container parameter
+ '%app.config_table_name%', // will be resolved to the value of a container parameter
));
- // get all arguments configured for this definition
+ // gets all arguments configured for this definition
$constructorArguments = $definition->getArguments();
- // get a specific argument
+ // gets a specific argument
$firstArgument = $definition->getArgument(0);
- // add a new argument
+ // adds a new argument
$definition->addArgument($argument);
- // replace argument on a specific index (0 = first argument)
+ // replaces argument on a specific index (0 = first argument)
$definition->replaceArgument($index, $argument);
// replace all previously configured arguments with the passed array
@@ -112,13 +112,13 @@ Method Calls
If the service you are working with uses setter injection then you can manipulate
any method calls in the definitions as well::
- // get all configured method calls
+ // gets all configured method calls
$methodCalls = $definition->getMethodCalls();
- // configure a new method call
+ // configures a new method call
$definition->addMethodCall('setLogger', array(new Reference('logger')));
- // replace all previously configured method calls with the passed array
+ // replaces all previously configured method calls with the passed array
$definition->setMethodCalls($methodCalls);
.. tip::
diff --git a/service_container/expression_language.rst b/service_container/expression_language.rst
index cbab6c2ffde..4aa57599c34 100644
--- a/service_container/expression_language.rst
+++ b/service_container/expression_language.rst
@@ -45,7 +45,7 @@ to another service: ``App\Mailer``. One way to do this is with an expression:
- service('App\Mail\MailerConfiguration').getMailerMethod()
+ service('App\\Mail\\MailerConfiguration').getMailerMethod()
@@ -60,7 +60,7 @@ to another service: ``App\Mailer``. One way to do this is with an expression:
$container->autowire(MailerConfiguration::class);
$container->autowire(Mailer::class)
- ->addArgument(new Expression('service("App\Mail\MailerConfiguration").getMailerMethod()'));
+ ->addArgument(new Expression('service("App\\\\Mail\\\\MailerConfiguration").getMailerMethod()'));
To learn more about the expression language syntax, see :doc:`/components/expression_language/syntax`.
diff --git a/service_container/factories.rst b/service_container/factories.rst
index 915239f886f..cfca74e26df 100644
--- a/service_container/factories.rst
+++ b/service_container/factories.rst
@@ -56,6 +56,12 @@ configure the service container to use the
+
+
@@ -146,7 +152,7 @@ Configuration of the service container then looks like this:
.. code-block:: yaml
# config/services.yaml
- AppBundle\Email\NewsletterManager:
+ App\Email\NewsletterManager:
# new syntax
factory: 'App\Email\NewsletterManagerFactory:createNewsletterManager'
# old syntax
diff --git a/service_container/lazy_services.rst b/service_container/lazy_services.rst
index fe6ad427249..57d8e780549 100644
--- a/service_container/lazy_services.rst
+++ b/service_container/lazy_services.rst
@@ -53,8 +53,8 @@ You can mark the service as ``lazy`` by manipulating its definition:
# config/services.yaml
services:
- App\Twig\AppExtension:
- lazy: true
+ App\Twig\AppExtension:
+ lazy: true
.. code-block:: xml
diff --git a/service_container/parameters.rst b/service_container/parameters.rst
index 1d6e95e48de..730a928f510 100644
--- a/service_container/parameters.rst
+++ b/service_container/parameters.rst
@@ -156,13 +156,13 @@ Getting and Setting Container Parameters in PHP
Working with container parameters is straightforward using the container's
accessor methods for parameters::
- // check if a parameter is defined (parameter names are case-sensitive)
+ // checks if a parameter is defined (parameter names are case-sensitive)
$container->hasParameter('mailer.transport');
- // get value of a parameter
+ // gets value of a parameter
$container->getParameter('mailer.transport');
- // add a new parameter
+ // adds a new parameter
$container->setParameter('mailer.transport', 'sendmail');
.. caution::
diff --git a/service_container/parent_services.rst b/service_container/parent_services.rst
index ea23e2f6dff..43b1d7827a5 100644
--- a/service_container/parent_services.rst
+++ b/service_container/parent_services.rst
@@ -15,12 +15,12 @@ you may have multiple repository classes which need the
// ...
abstract class BaseDoctrineRepository
{
- protected $entityManager;
+ protected $objectManager;
protected $logger;
- public function __construct(EntityManagerInterface $manager)
+ public function __construct(ObjectManager $objectManager)
{
- $this->entityManager = $manager;
+ $this->objectManager = $objectManager;
}
public function setLogger(LoggerInterface $logger)
diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst
index 68071051e47..b178ae1b545 100644
--- a/service_container/service_decoration.rst
+++ b/service_container/service_decoration.rst
@@ -13,12 +13,11 @@ the original service is lost:
# config/services.yaml
services:
- app.mailer:
- class: App\Mailer
+ App\Mailer: ~
- # this replaces the old app.mailer definition with the new one, the
+ # this replaces the old App\Mailer definition with the new one, the
# old definition is lost
- app.mailer:
+ App\Mailer:
class: App\DecoratingMailer
.. code-block:: xml
@@ -30,11 +29,11 @@ the original service is lost:
xsd:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
-
-
+
@@ -44,11 +43,11 @@ the original service is lost:
use App\Mailer;
use App\DecoratingMailer;
- $container->register('app.mailer', Mailer::class);
+ $container->register(Mailer::class);
- // this replaces the old app.mailer definition with the new one, the
+ // this replaces the old AppBundle\Mailer definition with the new one, the
// old definition is lost
- $container->register('app.mailer', DecoratingMailer::class);
+ $container->register(Mailer::class, DecoratingMailer::class);
Most of the time, that's exactly what you want to do. But sometimes,
you might want to decorate the old service instead and keep the old service so
@@ -60,19 +59,17 @@ that you can reference it:
# config/services.yaml
services:
- app.mailer:
- class: App\Mailer
+ App\Mailer: ~
- app.decorating_mailer:
- class: App\DecoratingMailer
- # overrides the app.mailer service
- # but that service is still available as app.decorating_mailer.inner
- decorates: app.mailer
+ App\DecoratingMailer:
+ # overrides the App\Mailer service
+ # but that service is still available as App\DecoratingMailer.inner
+ decorates: App\Mailer
# pass the old service as an argument
- arguments: ['@app.decorating_mailer.inner']
+ arguments: ['@App\DecoratingMailer.inner']
- # private, because usually you do not need to fetch app.decorating_mailer directly
+ # private, because usually you do not need to fetch App\DecoratingMailer directly
public: false
.. code-block:: xml
@@ -84,14 +81,13 @@ that you can reference it:
xsd:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
-
-
+
@@ -104,28 +100,28 @@ that you can reference it:
use App\Mailer;
use Symfony\Component\DependencyInjection\Reference;
- $container->register('app.mailer', Mailer::class);
+ $container->register(Mailer::class);
- $container->register('app.decorating_mailer', DecoratingMailer::class)
- ->setDecoratedService('app.mailer')
- ->addArgument(new Reference('app.decorating_mailer.inner'))
+ $container->register(DecoratingMailer::class)
+ ->setDecoratedService(Mailer::class)
+ ->addArgument(new Reference(DecoratingMailer::class.'.inner'))
->setPublic(false)
;
-The ``decorates`` option tells the container that the ``app.decorating_mailer`` service
-replaces the ``app.mailer`` service. The old ``app.mailer`` service is renamed to
-``app.decorating_mailer.inner`` so you can inject it into your new service.
+The ``decorates`` option tells the container that the ``App\DecoratingMailer`` service
+replaces the ``App\Mailer`` service. The old ``App\Mailer`` service is renamed to
+``App\DecoratingMailer.inner`` so you can inject it into your new service.
.. tip::
- The visibility (public) of the decorated ``app.mailer`` service (which is an alias
- for the new service) will still be the same as the original ``app.mailer``
+ The visibility (public) of the decorated ``App\Mailer`` service (which is an alias
+ for the new service) will still be the same as the original ``App\Mailer``
visibility.
.. note::
The generated inner id is based on the id of the decorator service
- (``app.decorating_mailer`` here), not of the decorated service (``app.mailer``
+ (``App\DecoratingMailer`` here), not of the decorated service (``App\Mailer``
here). You can control the inner service name via the ``decoration_inner_name``
option:
@@ -135,10 +131,10 @@ replaces the ``app.mailer`` service. The old ``app.mailer`` service is renamed t
# config/services.yaml
services:
- app.decorating_mailer:
+ App\DecoratingMailer:
# ...
- decoration_inner_name: app.decorating_mailer.wooz
- arguments: ['@app.decorating_mailer.wooz']
+ decoration_inner_name: App\DecoratingMailer.wooz
+ arguments: ['@App\DecoratingMailer.wooz']
.. code-block:: xml
@@ -152,13 +148,12 @@ replaces the ``app.mailer`` service. The old ``app.mailer`` service is renamed t
-
+
@@ -170,40 +165,37 @@ replaces the ``app.mailer`` service. The old ``app.mailer`` service is renamed t
use App\DecoratingMailer;
use Symfony\Component\DependencyInjection\Reference;
- $container->register('app.decorating_mailer', DecoratingMailer::class)
- ->setDecoratedService('app.mailer', 'app.decorating_mailer.wooz')
- ->addArgument(new Reference('app.decorating_mailer.wooz'))
+ $container->register(DecoratingMailer::class)
+ ->setDecoratedService(App\Mailer, DecoratingMailer::class.'.wooz')
+ ->addArgument(new Reference(DecoratingMailer::class.'.wooz'))
// ...
;
Decoration Priority
-------------------
-If you want to apply more than one decorator to a service, you can control their
-order by configuring the priority of decoration, this can be any integer number
-(decorators with higher priorities will be applied first).
+When applying multiple decorators to a service, you can control their order with
+the ``decoration_priority`` option. Its value is an integer that defaults to
+``0`` and higher priorities mean that decorators will be applied earlier.
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
- foo:
- class: Foo
+ Foo: ~
- bar:
- class: Bar
+ Bar:
public: false
- decorates: foo
+ decorates: Foo
decoration_priority: 5
- arguments: ['@bar.inner']
+ arguments: ['@Bar.inner']
- baz:
- class: Baz
+ Baz:
public: false
- decorates: foo
+ decorates: Foo
decoration_priority: 1
- arguments: ['@baz.inner']
+ arguments: ['@Baz.inner']
.. code-block:: xml
@@ -215,14 +207,14 @@ order by configuring the priority of decoration, this can be any integer number
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
-
-
+
+
-
-
+
+
@@ -232,20 +224,20 @@ order by configuring the priority of decoration, this can be any integer number
// config/services.php
use Symfony\Component\DependencyInjection\Reference;
- $container->register('foo', 'Foo')
+ $container->register(Foo:class)
- $container->register('bar', 'Bar')
- ->addArgument(new Reference('bar.inner'))
+ $container->register(Bar:class)
+ ->addArgument(new Reference(Bar:class.'inner'))
->setPublic(false)
- ->setDecoratedService('foo', null, 5);
+ ->setDecoratedService(Foo:class, null, 5);
- $container->register('baz', 'Baz')
- ->addArgument(new Reference('baz.inner'))
+ $container->register(Baz:class)
+ ->addArgument(new Reference(Baz:class.'inner'))
->setPublic(false)
- ->setDecoratedService('foo', null, 1);
+ ->setDecoratedService(Foo:class, null, 1);
The generated code will be the following::
- $this->services['foo'] = new Baz(new Bar(new Foo()));
+ $this->services[Foo:class] = new Baz(new Bar(new Foo()));
.. _decorator pattern: https://en.wikipedia.org/wiki/Decorator_pattern
diff --git a/service_container/shared.rst b/service_container/shared.rst
index ad36a924719..decb9d09cf2 100644
--- a/service_container/shared.rst
+++ b/service_container/shared.rst
@@ -37,5 +37,5 @@ in your service definition:
$container->register(SomeNonSharedService::class)
->setShared(false);
-Now, whenever you request an the ``App\SomeNonSharedService`` from the container,
+Now, whenever you request the ``App\SomeNonSharedService`` from the container,
you will be passed a new instance.
diff --git a/session/locale_sticky_session.rst b/session/locale_sticky_session.rst
index e56a656a578..079bf637de2 100644
--- a/session/locale_sticky_session.rst
+++ b/session/locale_sticky_session.rst
@@ -52,8 +52,8 @@ correct locale however you want::
public static function getSubscribedEvents()
{
return array(
- // must be registered after the default Locale listener
- KernelEvents::REQUEST => array(array('onKernelRequest', 15)),
+ // must be registered before (i.e. with a higher priority than) the default Locale listener
+ KernelEvents::REQUEST => array(array('onKernelRequest', 20)),
);
}
}
@@ -140,9 +140,7 @@ you can hook into the login process and update the user's session with this
locale value before they are redirected to their first page.
To do this, you need an event subscriber on the ``security.interactive_login``
-event:
-
-.. code-block:: php
+event::
// src/EventSubscriber/UserLocaleSubscriber.php
namespace App\EventSubscriber;
diff --git a/setup.rst b/setup.rst
index 1a66f76bee2..eb478070fb3 100644
--- a/setup.rst
+++ b/setup.rst
@@ -17,12 +17,22 @@ Create your new project by running:
.. code-block:: terminal
- $ composer create-project symfony/skeleton my-project
+ $ composer create-project symfony/website-skeleton my-project
This will create a new ``my-project`` directory, download some dependencies into
it and even generate the basic directories and files you'll need to get started.
In other words, your new app is ready!
+.. tip::
+
+ The ``website-skeleton`` is optimized for traditional web applications. If
+ you are building microservices, console applications or APIs, consider
+ using the much simpler ``skeleton`` project:
+
+ .. code-block:: terminal
+
+ $ composer create-project symfony/skeleton my-project
+
Running your Symfony Application
--------------------------------
@@ -112,7 +122,7 @@ vulnerability. Run this command to install it in your application:
.. code-block:: terminal
$ cd my-project/
- $ composer require sec-checker
+ $ composer require sec-checker --dev
From now on, this utility will be run automatically whenever you install or
update any dependency in the application. If a dependency contains a vulnerability,
diff --git a/setup/built_in_web_server.rst b/setup/built_in_web_server.rst
index 2b5e467be8b..37626e7cc04 100644
--- a/setup/built_in_web_server.rst
+++ b/setup/built_in_web_server.rst
@@ -22,34 +22,12 @@ and enable the server bundle.
Installing the Web Server Bundle
--------------------------------
-First, execute this command:
+Move into your project directory and run this command:
.. code-block:: terminal
$ cd your-project/
- $ composer require --dev symfony/web-server-bundle
-
-Then, enable the bundle in the kernel of the application::
-
- // src/Kernel.php
- class Kernel extends Kernel
- {
- public function registerBundles()
- {
- $bundles = array(
- // ...
- );
-
- if ('dev' === $this->getEnvironment()) {
- // ...
- $bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
- }
-
- // ...
- }
-
- // ...
- }
+ $ composer require server --dev
Starting the Web Server
-----------------------
@@ -151,5 +129,5 @@ when the web server listens to another IP address or to another port:
$ php bin/console server:stop 192.168.0.1:8080
-.. _`built-in web server`: http://www.php.net/manual/en/features.commandline.webserver.php
-.. _`php.net`: http://php.net/manual/en/features.commandline.webserver.php#example-411
+.. _`built-in web server`: https://php.net/manual/en/features.commandline.webserver.php
+.. _`php.net`: https://php.net/manual/en/features.commandline.webserver.php#example-411
diff --git a/setup/file_permissions.rst b/setup/file_permissions.rst
index 4f68d91a730..5c8593313a5 100644
--- a/setup/file_permissions.rst
+++ b/setup/file_permissions.rst
@@ -15,5 +15,5 @@ was writable. But that is no longer true! In Symfony 4, everything works automat
If you decide to store log files on disk, you *will* need to make sure your
logs directory (e.g. ``var/log/``) is writable by your web server user and
- terminal user. One way this can be done is by using ``chmod 777 -R var/log/``.
+ terminal user. One way this can be done is by using ``chmod -R 777 var/log/``.
Just be aware that your logs are readable by any user on your production system.
diff --git a/setup/flex.rst b/setup/flex.rst
index a719d5569f9..d0dad01f4fb 100644
--- a/setup/flex.rst
+++ b/setup/flex.rst
@@ -177,6 +177,21 @@ manual steps:
$ composer remove symfony/symfony
+ Now add the ``symfony/symfony`` package to the ``conflict`` section of the project's
+ ``composer.json`` file as `shown in this example of the skeleton-project`_ so that
+ it will not be installed again:
+
+ .. code-block:: diff
+
+ {
+ "require": {
+ "symfony/flex": "^1.0",
+ + },
+ + "conflict": {
+ + "symfony/symfony": "*"
+ }
+ }
+
Now you must add in ``composer.json`` all the Symfony dependencies required
by your project. A quick way to do that is to add all the components that
were included in the previous ``symfony/symfony`` dependency and later you
@@ -184,8 +199,9 @@ manual steps:
.. code-block:: terminal
- $ composer require annotations assets doctrine twig profiler \
- logger mailer form fixtures security translation validator
+ $ composer require annotations asset orm-pack twig \
+ logger mailer form security translation validator
+ $ composer require --dev dotenv maker-bundle orm-fixtures profiler
#. If the project's ``composer.json`` file doesn't contain ``symfony/symfony``
dependency, it already defines its dependencies explicitly, as required by
@@ -195,7 +211,7 @@ manual steps:
.. code-block:: terminal
- $ rm -fr /vendor/*
+ $ rm -fr vendor/*
$ composer install
#. No matter which of the previous steps you followed. At this point, you'll have
@@ -212,16 +228,25 @@ manual steps:
:doc:`autowiring feature ` you can remove
most of the service configuration.
+ .. note::
+
+ Make sure that your previous configuration files don't have ``imports``
+ declarations pointing to resources already loaded by ``Kernel::configureContainer()``
+ or ``Kernel::configureRoutes()`` methods.
+
#. Move the rest of the ``app/`` contents as follows (and after that, remove the
``app/`` directory):
* ``app/Resources/views/`` -> ``templates/``
* ``app/Resources/translations/`` -> ``translations/``
- * ``app/Resources//views/`` -> ``templates//``
+ * ``app/Resources//views/`` -> ``templates/bundles//``
* rest of ``app/Resources/`` files -> ``src/Resources/``
-#. Move the original PHP source code from ``src/AppBundle/*`` to ``src/``. In
- addition to moving the files, update the ``autoload`` and ``autoload-dev``
+#. Move the original PHP source code from ``src/AppBundle/*``, except bundle
+ specific files (like ``AppBundle.php`` and ``DependencyInjection/``), to
+ ``src/``. Remove ``src/AppBundle/``.
+
+ In addition to moving the files, update the ``autoload`` and ``autoload-dev``
values of the ``composer.json`` file as `shown in this example`_ to use
``App\`` and ``App\Tests\`` as the application namespaces (advanced IDEs can
do this automatically).
@@ -245,6 +270,9 @@ manual steps:
#. Update the ``bin/console`` script `copying Symfony's bin/console source`_
and changing anything according to your original console script.
+#. Remove the ``bin/symfony_requirements`` script and if you need a replacement
+ for it, use the new `Symfony Requirements Checker`_.
+
.. _`Symfony Flex`: https://github.com/symfony/flex
.. _`Symfony Installer`: https://github.com/symfony/symfony-installer
.. _`Symfony Standard Edition`: https://github.com/symfony/symfony-standard
@@ -253,5 +281,7 @@ manual steps:
.. _`Symfony Recipes documentation`: https://github.com/symfony/recipes/blob/master/README.rst
.. _`default services.yaml file`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.3/config/services.yaml
.. _`shown in this example`: https://github.com/symfony/skeleton/blob/8e33fe617629f283a12bbe0a6578bd6e6af417af/composer.json#L24-L33
+.. _`shown in this example of the skeleton-project`: https://github.com/symfony/skeleton/blob/8e33fe617629f283a12bbe0a6578bd6e6af417af/composer.json#L44-L46
.. _`copying Symfony's index.php source`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.3/public/index.php
-.. _`copying Symfony's bin/console source`: https://github.com/symfony/recipes/tree/master/symfony/console/3.3
+.. _`copying Symfony's bin/console source`: https://github.com/symfony/recipes/blob/master/symfony/console/3.3/bin/console
+.. _`Symfony Requirements Checker`: https://github.com/symfony/requirements-checker
diff --git a/setup/homestead.rst b/setup/homestead.rst
index 664f0bc613b..e28e11f8418 100644
--- a/setup/homestead.rst
+++ b/setup/homestead.rst
@@ -72,8 +72,8 @@ developing your Symfony application!
integration, automatic creation of MySQL databases and more, read the
`Daily Usage`_ section of the Homestead documentation.
-.. _Homestead: http://laravel.com/docs/homestead
+.. _Homestead: https://laravel.com/docs/homestead
.. _Vagrant: https://www.vagrantup.com/
-.. _the Homestead documentation: http://laravel.com/docs/homestead#installation-and-setup
-.. _Daily Usage: http://laravel.com/docs/5.1/homestead#daily-usage
-.. _this blog post: http://www.whitewashing.de/2013/08/19/speedup_symfony2_on_vagrant_boxes.html
+.. _the Homestead documentation: https://laravel.com/docs/homestead#installation-and-setup
+.. _Daily Usage: https://laravel.com/docs/5.1/homestead#daily-usage
+.. _this blog post: https://www.whitewashing.de/2013/08/19/speedup_symfony2_on_vagrant_boxes.html
diff --git a/setup/web_server_configuration.rst b/setup/web_server_configuration.rst
index 52939aae9bc..3499818b046 100644
--- a/setup/web_server_configuration.rst
+++ b/setup/web_server_configuration.rst
@@ -121,6 +121,11 @@ and increase web server performance:
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
+
+ # optionally set the value of the environment variables used in the application
+ #SetEnv APP_ENV prod
+ #SetEnv APP_SECRET
+ #SetEnv DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name"
.. tip::
@@ -293,6 +298,12 @@ The **minimum configuration** to get your application running under Nginx is:
fastcgi_pass unix:/var/run/php7.1-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
+
+ # optionally set the value of the environment variables used in the application
+ # fastcgi_param APP_ENV prod;
+ # fastcgi_param APP_SECRET ;
+ # fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name";
+
# When you are using symlinks to link the document root to the
# current version of your application, you should pass the real
# application path instead of the path to the symlink to PHP
@@ -345,6 +356,6 @@ The **minimum configuration** to get your application running under Nginx is:
For advanced Nginx configuration options, read the official `Nginx documentation`_.
-.. _`Apache documentation`: http://httpd.apache.org/docs/
+.. _`Apache documentation`: https://httpd.apache.org/docs/
.. _`FastCgiExternalServer`: https://docs.oracle.com/cd/B31017_01/web.1013/q20204/mod_fastcgi.html#FastCgiExternalServer
.. _`Nginx documentation`: https://www.nginx.com/resources/wiki/start/topics/recipes/symfony/
diff --git a/templating.rst b/templating.rst
index d7ecc781007..7b9acc43515 100644
--- a/templating.rst
+++ b/templating.rst
@@ -608,12 +608,12 @@ configuration:
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
- $collection = new RouteCollection();
- $collection->add('welcome', new Route('/', array(
+ $routes = new RouteCollection();
+ $routes->add('welcome', new Route('/', array(
'_controller' => 'App\Controller\WelcomeController::index',
)));
- return $collection;
+ return $routes;
To link to the page, just use the ``path()`` Twig function and refer to the route:
@@ -677,12 +677,12 @@ route:
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
- $collection = new RouteCollection();
- $collection->add('article_show', new Route('/article/{slug}', array(
+ $routes = new RouteCollection();
+ $routes->add('article_show', new Route('/article/{slug}', array(
'_controller' => 'App\Controller\ArticleController::show',
)));
- return $collection;
+ return $routes;
In this case, you need to specify both the route name (``article_show``) and
a value for the ``{slug}`` parameter. Using this route, revisit the
diff --git a/templating/debug.rst b/templating/debug.rst
index 78bfe6f4b01..91913843324 100644
--- a/templating/debug.rst
+++ b/templating/debug.rst
@@ -39,14 +39,25 @@ This is useful, for example, inside your controller::
The output of the ``dump()`` function is then rendered in the web developer
toolbar.
-The same mechanism can be used in Twig templates thanks to ``dump()`` function:
+In a Twig template, you can use the ``dump`` utility as a function or a tag:
+
+* ``{% dump foo.bar %}`` is the way to go when the original template output
+ shall not be modified: variables are not dumped inline, but in the web
+ debug toolbar;
+* on the contrary, ``{{ dump(foo.bar) }}`` dumps inline and thus may or not
+ be suited to your use case (e.g. you shouldn't use it in an HTML
+ attribute or a ``