From 55694003179b06fda1cab698113895e0fa7f19c6 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 17 Mar 2018 13:50:51 -0400 Subject: [PATCH] Document Updated make:entity command --- .github/PULL_REQUEST_TEMPLATE.md | 9 + _build/_theme/_templates/layout.html | 6 +- _build/_theme/assets/css/app.css | 3 + _build/_theme/assets/css/doc.css | 1 + _build/_theme/assets/js/app.js | 24 ++ _build/_theme/assets/js/doc.js | 1 + _build/_theme/assets/js/manifest.js | 1 + .../components/http_kernel/01-workflow.png | Bin 126061 -> 0 bytes .../http_kernel/02-kernel-request.png | Bin 122017 -> 0 bytes .../03-kernel-request-response.png | Bin 121853 -> 0 bytes .../http_kernel/04-resolve-controller.png | Bin 121205 -> 0 bytes .../http_kernel/06-kernel-controller.png | Bin 123653 -> 0 bytes .../http_kernel/07-controller-arguments.png | Bin 125140 -> 0 bytes .../http_kernel/08-call-controller.png | Bin 124607 -> 0 bytes .../09-controller-returns-response.png | Bin 126044 -> 0 bytes .../components/http_kernel/10-kernel-view.png | Bin 123647 -> 0 bytes .../http_kernel/11-kernel-exception.png | Bin 125527 -> 0 bytes .../http_kernel/http-workflow-exception.svg | 1 + .../http_kernel/http-workflow-subrequest.svg | 1 + .../components/http_kernel/http-workflow.svg | 1 + .../http_kernel/request-response-flow.png | Bin 334738 -> 0 bytes .../components/http_kernel/sub-request.png | Bin 128281 -> 0 bytes .../security/authentication-guard-methods.png | Bin 64323 -> 0 bytes .../security/authentication-guard-methods.svg | 1 + _images/sources/http_kernel/http-workflow.dia | Bin 0 -> 3793 bytes .../security/authentication-guard-methods.dia | Bin 0 -> 3381 bytes _includes/_annotation_loader_tip.rst.inc | 19 ++ best_practices/business-logic.rst | 24 +- best_practices/configuration.rst | 21 +- best_practices/controllers.rst | 16 +- best_practices/forms.rst | 14 +- best_practices/security.rst | 38 +-- best_practices/templates.rst | 4 +- best_practices/tests.rst | 4 +- bundles.rst | 21 +- bundles/best_practices.rst | 90 +++++- bundles/configuration.rst | 6 +- bundles/override.rst | 3 +- components/asset.rst | 45 +-- components/browser_kit.rst | 21 +- components/cache.rst | 47 +-- components/cache/adapters/apcu_adapter.rst | 26 +- components/cache/adapters/chain_adapter.rst | 38 ++- .../cache/adapters/doctrine_adapter.rst | 4 +- .../cache/adapters/filesystem_adapter.rst | 41 ++- .../cache/adapters/memcached_adapter.rst | 3 + .../adapters/pdo_doctrine_dbal_adapter.rst | 8 +- .../adapters/php_array_cache_adapter.rst | 8 +- .../cache/adapters/php_files_adapter.rst | 63 ++++ components/cache/adapters/redis_adapter.rst | 2 +- components/cache/cache_invalidation.rst | 10 + components/cache/cache_items.rst | 10 +- components/cache/cache_pools.rst | 69 +++- components/cache/psr6_psr16_adapters.rst | 2 +- components/config.rst | 8 +- components/config/definition.rst | 20 +- components/config/resources.rst | 6 +- components/console.rst | 7 +- components/console/events.rst | 26 +- components/console/helpers/processhelper.rst | 8 +- components/console/helpers/progressbar.rst | 60 ++-- components/console/helpers/questionhelper.rst | 8 +- components/console/helpers/table.rst | 16 +- components/console/logger.rst | 2 +- components/css_selector.rst | 7 +- components/debug.rst | 7 +- components/dependency_injection.rst | 54 ++-- .../dependency_injection/compilation.rst | 40 +-- components/dom_crawler.rst | 55 ++-- components/dotenv.rst | 7 +- components/event_dispatcher.rst | 18 +- .../event_dispatcher/traceable_dispatcher.rst | 8 +- components/expression_language.rst | 19 +- components/expression_language/caching.rst | 10 +- components/expression_language/extending.rst | 20 +- components/expression_language/syntax.rst | 28 +- components/filesystem.rst | 109 +++---- components/filesystem/lock_handler.rst | 2 +- components/finder.rst | 33 +- components/form.rst | 41 +-- components/http_foundation.rst | 15 +- .../http_foundation/session_configuration.rst | 16 +- components/http_kernel.rst | 65 ++-- components/intl.rst | 28 +- components/ldap.rst | 60 ++-- components/lock.rst | 246 +++++++++++++- components/options_resolver.rst | 13 +- components/phpunit_bridge.rst | 19 +- components/process.rst | 54 +++- components/property_access.rst | 81 ++--- components/property_info.rst | 62 +++- components/psr7.rst | 11 +- components/require_autoload.rst.inc | 9 +- components/routing.rst | 43 +-- components/security.rst | 7 +- components/security/authentication.rst | 8 +- components/security/authorization.rst | 7 +- components/security/firewall.rst | 18 +- components/serializer.rst | 82 +++-- components/stopwatch.rst | 25 +- components/templating.rst | 15 +- components/translation.rst | 14 +- components/translation/custom_formats.rst | 10 +- components/translation/usage.rst | 60 +++- components/validator.rst | 9 +- components/validator/resources.rst | 5 +- components/var_dumper.rst | 9 +- components/var_dumper/advanced.rst | 58 ++-- components/workflow.rst | 7 +- components/yaml.rst | 89 +++-- components/yaml/yaml_format.rst | 12 +- configuration/environments.rst | 4 +- configuration/external_parameters.rst | 7 + .../front_controllers_and_kernel.rst | 2 +- configuration/override_dir_structure.rst | 4 +- console.rst | 20 +- console/coloring.rst | 4 +- console/input.rst | 20 +- console/lazy_commands.rst | 12 +- console/lockable_trait.rst | 8 +- console/style.rst | 1 - contributing/code/bc.rst | 12 +- contributing/code/bugs.rst | 2 +- contributing/code/core_team.rst | 55 +++- contributing/code/license.rst | 2 +- contributing/code/patches.rst | 14 +- contributing/code/reproducer.rst | 15 +- contributing/code/standards.rst | 29 +- contributing/community/index.rst | 1 + contributing/community/releases.rst | 72 +++-- contributing/community/review-comments.rst | 178 ++++++++++ contributing/documentation/overview.rst | 2 +- contributing/documentation/standards.rst | 4 +- contributing/map.rst.inc | 1 + controller.rst | 77 +++-- controller/error_pages.rst | 12 +- controller/service.rst | 2 +- controller/soap_web_service.rst | 18 +- controller/upload_file.rst | 35 +- create_framework/dependency_injection.rst | 47 +-- create_framework/event_dispatcher.rst | 4 +- create_framework/front_controller.rst | 8 +- create_framework/http_foundation.rst | 30 +- .../http_kernel_controller_resolver.rst | 6 +- .../http_kernel_httpkernel_class.rst | 4 +- .../http_kernel_httpkernelinterface.rst | 9 +- create_framework/introduction.rst | 4 +- create_framework/routing.rst | 4 +- create_framework/separation_of_concerns.rst | 8 +- create_framework/templating.rst | 16 +- create_framework/unit_testing.rst | 2 +- deployment.rst | 10 +- doctrine.rst | 306 +++++++++--------- doctrine/associations.rst | 184 +++++++---- doctrine/dbal.rst | 14 +- doctrine/event_listeners_subscribers.rst | 2 +- doctrine/multiple_entity_managers.rst | 14 +- doctrine/pdo_session_storage.rst | 22 +- doctrine/registration_form.rst | 8 +- doctrine/reverse_engineering.rst | 14 +- email/dev_environment.rst | 6 +- email/spool.rst | 2 +- email/testing.rst | 14 +- event_dispatcher.rst | 4 +- event_dispatcher/method_behavior.rst | 10 +- form/bootstrap4.rst | 116 +++++++ form/create_custom_field_type.rst | 18 +- form/create_form_type_extension.rst | 2 +- form/data_transformers.rst | 8 +- form/dynamic_form_modification.rst | 7 +- form/events.rst | 9 +- form/form_collections.rst | 31 +- form/form_customization.rst | 8 +- form/form_dependencies.rst | 14 +- form/inherit_data_option.rst | 2 +- form/rendering.rst | 2 +- form/unit_testing.rst | 26 +- form/validation_group_service_resolver.rst | 8 +- form/without_class.rst | 4 +- forms.rst | 27 +- frontend/encore/advanced-config.rst | 56 ++++ frontend/encore/reactjs.rst | 3 +- frontend/encore/server-data.rst | 17 +- frontend/encore/versus-assetic.rst | 2 +- http_cache.rst | 13 +- http_cache/cache_invalidation.rst | 1 + http_cache/cache_vary.rst | 4 +- http_cache/esi.rst | 12 +- http_cache/expiration.rst | 2 +- http_cache/form_csrf_caching.rst | 3 +- http_cache/validation.rst | 4 +- introduction/from_flat_php_to_symfony2.rst | 46 +-- introduction/http_fundamentals.rst | 12 +- logging/formatter.rst | 2 +- logging/monolog_console.rst | 3 +- logging/processors.rst | 8 +- page_creation.rst | 16 +- performance.rst | 173 +++++----- profiler.rst | 2 +- profiler/matchers.rst | 32 ++ profiler/profiling_data.rst | 8 +- quick_tour/flex_recipes.rst | 33 +- quick_tour/the_architecture.rst | 49 ++- quick_tour/the_big_picture.rst | 53 ++- reference/configuration/doctrine.rst | 20 +- reference/configuration/framework.rst | 38 ++- reference/configuration/security.rst | 69 +++- reference/configuration/swiftmailer.rst | 69 +++- reference/configuration/twig.rst | 2 +- reference/configuration/web_profiler.rst | 6 +- reference/constraints/CardScheme.rst | 2 +- reference/constraints/Choice.rst | 6 +- reference/constraints/Collection.rst | 4 +- reference/constraints/GreaterThan.rst | 2 +- reference/constraints/GreaterThanOrEqual.rst | 2 +- reference/constraints/Isbn.rst | 2 +- reference/constraints/LessThan.rst | 2 +- reference/constraints/LessThanOrEqual.rst | 2 +- reference/constraints/Range.rst | 4 +- reference/constraints/Type.rst | 4 +- reference/constraints/Url.rst | 13 +- reference/constraints/Valid.rst | 4 +- reference/dic_tags.rst | 12 +- reference/events.rst | 41 ++- reference/forms/types/choice.rst | 8 +- reference/forms/types/collection.rst | 107 +++--- reference/forms/types/date.rst | 8 +- reference/forms/types/datetime.rst | 2 +- reference/forms/types/entity.rst | 4 +- reference/forms/types/file.rst | 6 +- .../types/options/_date_limitation.rst.inc | 2 +- .../forms/types/options/choice_value.rst.inc | 4 +- .../forms/types/options/compound.rst.inc | 22 +- .../forms/types/options/data_class.rst.inc | 4 +- .../forms/types/options/date_format.rst.inc | 4 +- .../forms/types/options/group_by.rst.inc | 6 +- .../types/options/model_timezone.rst.inc | 2 +- .../types/options/preferred_choices.rst.inc | 10 +- .../forms/types/options/view_timezone.rst.inc | 2 +- reference/requirements.rst | 4 +- routing.rst | 58 ++-- routing/conditions.rst | 6 +- routing/custom_route_loader.rst | 68 +++- routing/external_resources.rst | 156 +++++---- routing/extra_information.rst | 6 +- routing/hostname_pattern.rst | 42 ++- routing/optional_placeholders.rst | 18 +- routing/redirect_in_config.rst | 14 +- routing/redirect_trailing_slash.rst | 25 +- routing/requirements.rst | 22 +- routing/scheme.rst | 6 +- routing/service_container_parameters.rst | 12 +- routing/slash_in_parameter.rst | 15 +- security.rst | 48 +-- security/access_control.rst | 33 +- security/csrf.rst | 47 ++- security/custom_authentication_provider.rst | 22 +- security/custom_password_authenticator.rst | 8 +- security/entity_provider.rst | 37 ++- security/expressions.rst | 6 +- security/form_login.rst | 18 ++ security/form_login_setup.rst | 19 +- security/guard_authentication.rst | 11 +- security/impersonating_user.rst | 50 +-- security/json_login_setup.rst | 7 +- security/named_encoders.rst | 12 +- security/remember_me.rst | 12 +- security/securing_services.rst | 12 - security/voters.rst | 12 +- serializer.rst | 9 +- service_container.rst | 41 ++- service_container/3.3-di-changes.rst | 54 ---- service_container/alias_private.rst | 8 +- service_container/configurators.rst | 16 +- service_container/definitions.rst | 26 +- service_container/expression_language.rst | 4 +- service_container/factories.rst | 8 +- service_container/lazy_services.rst | 4 +- service_container/parameters.rst | 6 +- service_container/parent_services.rst | 6 +- service_container/service_decoration.rst | 134 ++++---- service_container/shared.rst | 2 +- session/locale_sticky_session.rst | 8 +- setup.rst | 14 +- setup/built_in_web_server.rst | 30 +- setup/file_permissions.rst | 2 +- setup/flex.rst | 44 ++- setup/homestead.rst | 8 +- setup/web_server_configuration.rst | 13 +- templating.rst | 12 +- templating/debug.rst | 15 +- templating/render_without_controller.rst | 12 +- templating/twig_extension.rst | 121 ++++++- testing.rst | 96 +++--- testing/database.rst | 4 +- testing/doctrine.rst | 10 +- testing/profiling.rst | 4 +- translation.rst | 44 ++- translation/locale.rst | 6 +- validation.rst | 22 +- validation/groups.rst | 2 +- validation/raw_values.rst | 6 +- validation/sequence_provider.rst | 26 +- validation/severity.rst | 4 +- workflow/usage.rst | 35 +- 305 files changed, 4421 insertions(+), 2419 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 _build/_theme/assets/css/app.css create mode 100644 _build/_theme/assets/css/doc.css create mode 100644 _build/_theme/assets/js/app.js create mode 100644 _build/_theme/assets/js/doc.js create mode 100644 _build/_theme/assets/js/manifest.js delete mode 100644 _images/components/http_kernel/01-workflow.png delete mode 100644 _images/components/http_kernel/02-kernel-request.png delete mode 100644 _images/components/http_kernel/03-kernel-request-response.png delete mode 100644 _images/components/http_kernel/04-resolve-controller.png delete mode 100644 _images/components/http_kernel/06-kernel-controller.png delete mode 100644 _images/components/http_kernel/07-controller-arguments.png delete mode 100644 _images/components/http_kernel/08-call-controller.png delete mode 100644 _images/components/http_kernel/09-controller-returns-response.png delete mode 100644 _images/components/http_kernel/10-kernel-view.png delete mode 100644 _images/components/http_kernel/11-kernel-exception.png create mode 100644 _images/components/http_kernel/http-workflow-exception.svg create mode 100644 _images/components/http_kernel/http-workflow-subrequest.svg create mode 100644 _images/components/http_kernel/http-workflow.svg delete mode 100644 _images/components/http_kernel/request-response-flow.png delete mode 100644 _images/components/http_kernel/sub-request.png delete mode 100644 _images/security/authentication-guard-methods.png create mode 100644 _images/security/authentication-guard-methods.svg create mode 100644 _images/sources/http_kernel/http-workflow.dia create mode 100644 _images/sources/security/authentication-guard-methods.dia create mode 100644 _includes/_annotation_loader_tip.rst.inc create mode 100644 components/cache/adapters/php_files_adapter.rst create mode 100644 contributing/community/review-comments.rst create mode 100644 form/bootstrap4.rst 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 %}

Page not found

- {% if is_granted('IS_AUTHENTICATED_FULLY') %} - {# ... #} - {% endif %} -

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 ``
`` element (the whole form is obviously a compound form). + +Non-compound forms are always leaves in a form tree, they cannot have children. + +A non-compound form is rendered as one of the html form elements: ```` +(``TextType``, ``FileType``, ``HiddenType``), ``