diff --git a/best_practices/business-logic.rst b/best_practices/business-logic.rst index 3f1022a10d1..55305165436 100644 --- a/best_practices/business-logic.rst +++ b/best_practices/business-logic.rst @@ -21,6 +21,8 @@ Inside here, you can create whatever directories you want to organize things: │ └─ AppBundle/ │ └─ Utils/ │ └─ MyClass.php + ├─ tests/ + ├─ var/ ├─ vendor/ └─ web/ @@ -40,6 +42,8 @@ and put things there: │ │ └─ Utils/ │ │ └─ MyClass.php │ └─ AppBundle/ + ├─ tests/ + ├─ var/ ├─ vendor/ └─ web/ @@ -318,7 +322,7 @@ command: .. code-block:: bash - $ php app/console doctrine:fixtures:load + $ php bin/console doctrine:fixtures:load Careful, database will be purged. Do you want to continue Y/N ? Y > purging database diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst index 76aac62de31..1bfa758b8a8 100644 --- a/best_practices/configuration.rst +++ b/best_practices/configuration.rst @@ -52,8 +52,8 @@ Canonical Parameters Define all your application's parameters in the ``app/config/parameters.yml.dist`` file. -Since version 2.3, Symfony includes a configuration file called ``parameters.yml.dist``, -which stores the canonical list of configuration parameters for the application. +Symfony includes a configuration file called ``parameters.yml.dist``, which +stores the canonical list of configuration parameters for the application. Whenever a new configuration parameter is defined for the application, you should also add it to this file and submit the changes to your version control diff --git a/best_practices/creating-the-project.rst b/best_practices/creating-the-project.rst index 45dcc987220..06e277b8cb6 100644 --- a/best_practices/creating-the-project.rst +++ b/best_practices/creating-the-project.rst @@ -27,7 +27,6 @@ to create files and execute the following commands: .. code-block:: bash - # Linux, Mac OS X $ cd projects/ $ symfony new blog @@ -63,13 +62,18 @@ number of files and directories generated automatically: blog/ ├─ app/ - │ ├─ console - │ ├─ cache/ │ ├─ config/ - │ ├─ logs/ │ └─ Resources/ + ├─ bin + │ └─ console ├─ src/ │ └─ AppBundle/ + ├─ var/ + │ ├─ cache/ + │ ├─ logs/ + │ └─ sessions/ + ├─ tests/ + │ └─ AppBundle/ ├─ vendor/ └─ web/ @@ -77,13 +81,16 @@ This file and directory hierarchy is the convention proposed by Symfony to structure your applications. The recommended purpose of each directory is the following: -* ``app/cache/``, stores all the cache files generated by the application; * ``app/config/``, stores all the configuration defined for any environment; -* ``app/logs/``, stores all the log files generated by the application; * ``app/Resources/``, stores all the templates and the translation files for the application; * ``src/AppBundle/``, stores the Symfony specific code (controllers and routes), your domain code (e.g. Doctrine classes) and all your business logic; +* ``var/cache/``, stores all the cache files generated by the application; +* ``var/logs/``, stores all the log files generated by the application; +* ``var/sessions/``, stores all the session files generated by the application; +* ``tests/AppBundle/``, stores the automatic tests (e.g. Unit tests) of the + application. * ``vendor/``, this is the directory where Composer installs the application's dependencies and you should never modify any of its contents; * ``web/``, stores all the front controller files and all the web assets, such @@ -107,8 +114,7 @@ ProductBundle, then there's no advantage to having two separate bundles. Create only one bundle called AppBundle for your application logic. Implementing a single AppBundle bundle in your projects will make your code -more concise and easier to understand. Starting in Symfony 2.6, the official -Symfony documentation uses the AppBundle name. +more concise and easier to understand. .. note:: @@ -128,13 +134,18 @@ that follows these best practices: blog/ ├─ app/ - │ ├─ console - │ ├─ cache/ │ ├─ config/ - │ ├─ logs/ │ └─ Resources/ + ├─ bin/ + │ └─ console ├─ src/ │ └─ AppBundle/ + ├─ tests/ + │ └─ AppBundle/ + ├─ var/ + │ ├─ cache/ + │ ├─ logs/ + └─ sessions/ ├─ vendor/ └─ web/ ├─ app.php @@ -147,7 +158,7 @@ that follows these best practices: .. code-block:: bash - $ php app/console generate:bundle --namespace=AppBundle --dir=src --format=annotation --no-interaction + $ php bin/console generate:bundle --namespace=AppBundle --dir=src --format=annotation --no-interaction Extending the Directory Structure --------------------------------- @@ -157,27 +168,6 @@ structure of Symfony, you can :doc:`override the location of the main directories `: ``cache/``, ``logs/`` and ``web/``. -In addition, Symfony3 will use a slightly different directory structure when -it's released: - -.. code-block:: text - - blog-symfony3/ - ├─ app/ - │ ├─ config/ - │ └─ Resources/ - ├─ bin/ - │ └─ console - ├─ src/ - ├─ var/ - │ ├─ cache/ - │ └─ logs/ - ├─ vendor/ - └─ web/ - -The changes are pretty superficial, but for now, we recommend that you use -the Symfony directory structure. - .. _`Composer`: https://getcomposer.org/ .. _`Phar extension`: http://php.net/manual/en/intro.phar.php .. _`public checksums repository`: https://github.com/sensiolabs/checksums diff --git a/best_practices/forms.rst b/best_practices/forms.rst index 968fb078bfc..5c5f25b2d96 100644 --- a/best_practices/forms.rst +++ b/best_practices/forms.rst @@ -22,6 +22,9 @@ form in its own PHP class:: use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; + use Symfony\Component\Form\Extension\Core\Type\TextareaType; + use Symfony\Component\Form\Extension\Core\Type\EmailType; + use Symfony\Component\Form\Extension\Core\Type\DateTimeType; class PostType extends AbstractType { @@ -29,10 +32,10 @@ form in its own PHP class:: { $builder ->add('title') - ->add('summary', 'textarea') - ->add('content', 'textarea') - ->add('authorEmail', 'email') - ->add('publishedAt', 'datetime') + ->add('summary', TextareaType::class) + ->add('content', TextareaType::class) + ->add('authorEmail', EmailType::class) + ->add('publishedAt', DateTimeType::class) ; } @@ -42,11 +45,6 @@ form in its own PHP class:: 'data_class' => 'AppBundle\Entity\Post' )); } - - public function getName() - { - return 'post'; - } } .. best-practice:: @@ -54,7 +52,7 @@ form in its own PHP class:: Put the form type classes in the ``AppBundle\Form`` namespace, unless you use other custom form classes like data transformers. -To use the class, use ``createForm()`` and instantiate the new class:: +To use the class, use ``createForm()`` and pass the fully qualified class name:: // ... use AppBundle\Form\PostType; @@ -63,7 +61,7 @@ To use the class, use ``createForm()`` and instantiate the new class:: public function newAction(Request $request) { $post = new Post(); - $form = $this->createForm(new PostType(), $post); + $form = $this->createForm(PostType::class, $post); // ... } @@ -73,13 +71,9 @@ Registering Forms as Services You can also :ref:`register your form type as a service `. -But this is *not* recommended unless you plan to reuse the new form type in many -places or embed it in other forms directly or via the -:doc:`collection type `. - -For most forms that are used only to edit or create something, registering -the form as a service is over-kill, and makes it more difficult to figure -out exactly which form class is being used in a controller. +This is only needed if your form type requires some dependencies to be injected +by the container, otherwise it is unnecessary overhead and therefore *not* +recommended to do this for all form type classes. Form Button Configuration ------------------------- @@ -91,9 +85,10 @@ makes them easier to re-use later. Add buttons in the templates, not in the form classes or the controllers. -Since Symfony 2.3, you can add buttons as fields on your form. This is a nice -way to simplify the template that renders your form. But if you add the buttons -directly in your form class, this would effectively limit the scope of that form: +The Symfony Form component allows you to add buttons as fields on your form. +This is a nice way to simplify the template that renders your form. But if you +add the buttons directly in your form class, this would effectively limit the +scope of that form: .. code-block:: php @@ -103,7 +98,7 @@ directly in your form class, this would effectively limit the scope of that form { $builder // ... - ->add('save', 'submit', array('label' => 'Create Post')) + ->add('save', SubmitType::class, array('label' => 'Create Post')) ; } @@ -118,6 +113,7 @@ some developers configure form buttons in the controller:: use Symfony\Component\HttpFoundation\Request; use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; use AppBundle\Entity\Post; use AppBundle\Form\PostType; @@ -128,8 +124,8 @@ some developers configure form buttons in the controller:: public function newAction(Request $request) { $post = new Post(); - $form = $this->createForm(new PostType(), $post); - $form->add('submit', 'submit', array( + $form = $this->createForm(PostType::class, $post); + $form->add('submit', SubmitType::class, array( 'label' => 'Create', 'attr' => array('class' => 'btn btn-default pull-right') )); @@ -213,21 +209,3 @@ Second, we recommend using ``$form->isSubmitted()`` in the ``if`` statement for clarity. This isn't technically needed, since ``isValid()`` first calls ``isSubmitted()``. But without this, the flow doesn't read well as it *looks* like the form is *always* processed (even on the GET request). - -Custom Form Field Types ------------------------ - -.. best-practice:: - - Add the ``app_`` prefix to your custom form field types to avoid collisions. - -Custom form field types inherit from the ``AbstractType`` class, which defines the -``getName()`` method to configure the name of that form type. These names must -be unique in the application. - -If a custom form type uses the same name as any of the Symfony's built-in form -types, it will override it. The same happens when the custom form type matches -any of the types defined by the third-party bundles installed in your application. - -Add the ``app_`` prefix to your custom form field types to avoid name collisions -that can lead to hard to debug errors. diff --git a/best_practices/i18n.rst b/best_practices/i18n.rst index cfbd5fb47e2..39473373df1 100644 --- a/best_practices/i18n.rst +++ b/best_practices/i18n.rst @@ -32,9 +32,9 @@ Of all the available translation formats, only XLIFF and gettext have broad support in the tools used by professional translators. And since it's based on XML, you can validate XLIFF file contents as you write them. -Symfony 2.6 added support for notes inside XLIFF files, making them more -user-friendly for translators. At the end, good translations are all about -context, and these XLIFF notes allow you to define that context. +Symfony supports notes in XLIFF files, making them more user-friendly for +translators. At the end, good translations are all about context, and these +XLIFF notes allow you to define that context. .. tip:: diff --git a/best_practices/introduction.rst b/best_practices/introduction.rst index 2c5661c6671..dad135249e6 100644 --- a/best_practices/introduction.rst +++ b/best_practices/introduction.rst @@ -76,12 +76,8 @@ installer and then execute this command to download the demo application: .. code-block:: bash - # Linux and Mac OS X $ symfony demo - # Windows - c:\> php symfony demo - **The demo application is a simple blog engine**, because that will allow us to focus on the Symfony concepts and features without getting buried in difficult implementation details. Instead of developing the application step by step in diff --git a/best_practices/security.rst b/best_practices/security.rst index eb3367da146..37f3bfd254a 100644 --- a/best_practices/security.rst +++ b/best_practices/security.rst @@ -264,37 +264,65 @@ the same ``getAuthorEmail`` logic you used above: namespace AppBundle\Security; - use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; + use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; + use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; + use AppBundle\Entity\Post; - // AbstractVoter class requires Symfony 2.6 or higher version - class PostVoter extends AbstractVoter + class PostVoter extends Voter { const CREATE = 'create'; const EDIT = 'edit'; - protected function getSupportedAttributes() + /** + * @var AccessDecisionManagerInterface + */ + private $decisionManager; + + public function __construct(AccessDecisionManagerInterface $decisionManager) { - return array(self::CREATE, self::EDIT); + $this->decisionManager = $decisionManager; } - protected function getSupportedClasses() + protected function supports($attribute, $subject) { - return array('AppBundle\Entity\Post'); + if (!in_array($attribute, array(self::CREATE, self::EDIT))) { + return false; + } + + if (!$subject instanceof Post) { + return false; + } + + return true; } - protected function isGranted($attribute, $post, $user = null) + protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { + $user = $token->getUser(); + /** @var Post */ + $post = $subject; // $subject must be a Post instance, thanks to the supports method + if (!$user instanceof UserInterface) { return false; } - if ($attribute === self::CREATE && in_array('ROLE_ADMIN', $user->getRoles(), true)) { - return true; - } - - if ($attribute === self::EDIT && $user->getEmail() === $post->getAuthorEmail()) { - return true; + switch ($attribute) { + case self::CREATE: + // if the user is an admin, allow them to create new posts + if ($this->decisionManager->decide($token, array('ROLE_ADMIN'))) { + return true; + } + + break; + case self::EDIT: + // if the user is the author of the post, allow them to edit the posts + if ($user->getEmail() === $post->getAuthorEmail()) { + return true; + } + + break; } return false; @@ -310,6 +338,7 @@ To enable the security voter in the application, define a new service: # ... post_voter: class: AppBundle\Security\PostVoter + arguments: ['@security.access.decision_manager'] public: false tags: - { name: security.voter } @@ -337,7 +366,7 @@ via the even easier shortcut in a controller: */ public function editAction($id) { - $post = // query for the post ... + $post = ...; // query for the post $this->denyAccessUnlessGranted('edit', $post); diff --git a/best_practices/tests.rst b/best_practices/tests.rst index 1f41afb9de7..1e9490d7aac 100644 --- a/best_practices/tests.rst +++ b/best_practices/tests.rst @@ -28,8 +28,8 @@ functional tests, you can quickly spot any big errors before you deploy them: A functional test can be as easy as this:: - // src/AppBundle/Tests/ApplicationAvailabilityFunctionalTest.php - namespace AppBundle\Tests; + // tests/AppBundle/ApplicationAvailabilityFunctionalTest.php + namespace Tests\AppBundle; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; diff --git a/best_practices/web-assets.rst b/best_practices/web-assets.rst index a4160d62e2e..f569c6df53a 100644 --- a/best_practices/web-assets.rst +++ b/best_practices/web-assets.rst @@ -35,6 +35,8 @@ much more concise: Using Assetic ------------- +.. include:: /cookbook/assetic/_standard_edition_warning.inc + These days, you probably can't simply create static CSS and JavaScript files and include them in your template. Instead, you'll probably want to combine and minify these to improve client-side performance. You may also want to diff --git a/book/bundles.rst b/book/bundles.rst index ff09cc6fc3e..db7994795c2 100644 --- a/book/bundles.rst +++ b/book/bundles.rst @@ -107,6 +107,7 @@ Now that you've created the bundle, enable it via the ``AppKernel`` class:: { $bundles = array( // ... + // register your bundle new Acme\TestBundle\AcmeTestBundle(), ); @@ -122,7 +123,7 @@ generating a basic bundle skeleton: .. code-block:: bash - $ php app/console generate:bundle --namespace=Acme/TestBundle + $ php bin/console generate:bundle --namespace=Acme/TestBundle The bundle skeleton generates a basic controller, template and routing resource that can be customized. You'll learn more about Symfony's command-line diff --git a/book/configuration.rst b/book/configuration.rst index 6562832ef09..073a658cda1 100644 --- a/book/configuration.rst +++ b/book/configuration.rst @@ -122,13 +122,13 @@ FrameworkBundle configuration: .. code-block:: bash - $ php app/console config:dump-reference FrameworkBundle + $ php bin/console config:dump-reference FrameworkBundle The extension alias (configuration key) can also be used: .. code-block:: bash - $ php app/console config:dump-reference framework + $ php bin/console config:dump-reference framework .. note:: @@ -177,7 +177,7 @@ cached files and allow them to rebuild: .. code-block:: bash - $ php app/console cache:clear --env=prod --no-debug + $ php bin/console cache:clear --env=prod --no-debug .. note:: diff --git a/book/controller.rst b/book/controller.rst index 4515304d27c..c768b33407d 100644 --- a/book/controller.rst +++ b/book/controller.rst @@ -533,10 +533,7 @@ console command: .. code-block:: bash - $ php app/console debug:container - -.. versionadded:: 2.6 - Prior to Symfony 2.6, this command was called ``container:debug``. + $ php bin/console debug:container For more information, see the :doc:`/book/service_container` chapter. @@ -843,16 +840,10 @@ method to check the CSRF token:: // ... do something, like deleting an object } -.. versionadded:: 2.6 - The ``isCsrfTokenValid()`` shortcut method was introduced in Symfony 2.6. - It is equivalent to executing the following code: - - .. code-block:: php - - use Symfony\Component\Security\Csrf\CsrfToken; - - $this->get('security.csrf.token_manager') - ->isTokenValid(new CsrfToken('token_id', 'TOKEN')); + // isCsrfTokenValid() is equivalent to: + // $this->get('security.csrf.token_manager')->isTokenValid( + // new \Symfony\Component\Security\Csrf\CsrfToken\CsrfToken('token_id', $token) + // ); Final Thoughts -------------- diff --git a/book/doctrine.rst b/book/doctrine.rst index 4023183990c..cdb6025adc5 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -116,7 +116,7 @@ can automatically generate an empty ``test_project`` database for you: .. code-block:: bash - $ php app/console doctrine:database:create + $ php bin/console doctrine:database:create .. sidebar:: Setting up the Database to be UTF8 @@ -128,12 +128,8 @@ can automatically generate an empty ``test_project`` database for you: .. code-block:: bash - $ php app/console doctrine:database:drop --force - $ php app/console doctrine:database:create - - There's no way to configure these defaults inside Doctrine, as it tries to be - as agnostic as possible in terms of environment configuration. One way to solve - this problem is to configure server-level defaults. + $ php bin/console doctrine:database:drop --force + $ php bin/console doctrine:database:create Setting UTF8 defaults for MySQL is as simple as adding a few lines to your configuration file (typically ``my.cnf``): @@ -145,6 +141,55 @@ can automatically generate an empty ``test_project`` database for you: collation-server = utf8mb4_general_ci # Replaces utf8_general_ci character-set-server = utf8mb4 # Replaces utf8 + You can also change the defaults for Doctrine so that the generated SQL + uses the correct character set. + + .. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + doctrine: + dbal: + charset: utf8mb4 + default_table_options: + charset: utf8mb4 + collate: utf8mb4_unicode_ci + + .. code-block:: xml + + + + + + + + utf8mb4 + utf8mb4_unicode_ci + + + + + .. code-block:: php + + // app/config/config.php + $configuration->loadFromExtension('doctrine', array( + 'dbal' => array( + 'charset' => 'utf8mb4', + 'default_table_options' => array( + 'charset' => 'utf8mb4' + 'collate' => 'utf8mb4_unicode_ci' + ) + ), + )); + We recommend against MySQL's ``utf8`` character set, since it does not support 4-byte unicode characters, and strings containing them will be truncated. This is fixed by the `newer utf8mb4 character set`_. @@ -227,7 +272,7 @@ just a simple PHP class. .. code-block:: bash - $ php app/console doctrine:generate:entity + $ php bin/console doctrine:generate:entity .. index:: single: Doctrine; Adding mapping metadata @@ -396,7 +441,7 @@ the following command can generate these boilerplate methods automatically: .. code-block:: bash - $ php app/console doctrine:generate:entities AppBundle/Entity/Product + $ php bin/console doctrine:generate:entities AppBundle/Entity/Product This command makes sure that all the getters and setters are generated for the ``Product`` class. This is a safe command - you can run it over and @@ -437,10 +482,10 @@ mapping information) of a bundle or an entire namespace: .. code-block:: bash # generates all entities in the AppBundle - $ php app/console doctrine:generate:entities AppBundle + $ php bin/console doctrine:generate:entities AppBundle # generates all entities of bundles in the Acme namespace - $ php app/console doctrine:generate:entities Acme + $ php bin/console doctrine:generate:entities Acme .. _book-doctrine-creating-the-database-tables-schema: @@ -455,7 +500,7 @@ in your application. To do this, run: .. code-block:: bash - $ php app/console doctrine:schema:update --force + $ php bin/console doctrine:schema:update --force .. tip:: @@ -852,7 +897,7 @@ used earlier to generate the missing getter and setter methods: .. code-block:: bash - $ php app/console doctrine:generate:entities AppBundle + $ php bin/console doctrine:generate:entities AppBundle Next, add a new method - ``findAllOrderedByName()`` - to the newly generated repository class. This method will query for all the ``Product`` entities, @@ -906,7 +951,7 @@ you can let Doctrine create the class for you. .. code-block:: bash - $ php app/console doctrine:generate:entity --no-interaction \ + $ php bin/console doctrine:generate:entity --no-interaction \ --entity="AppBundle:Category" \ --fields="name:string(255)" @@ -1063,7 +1108,7 @@ methods for you: .. code-block:: bash - $ php app/console doctrine:generate:entities AppBundle + $ php bin/console doctrine:generate:entities AppBundle Ignore the Doctrine metadata for a moment. You now have two classes - ``Category`` and ``Product`` with a natural one-to-many relationship. The ``Category`` @@ -1092,7 +1137,7 @@ table, and ``product.category_id`` column, and new foreign key: .. code-block:: bash - $ php app/console doctrine:schema:update --force + $ php bin/console doctrine:schema:update --force .. note:: diff --git a/book/forms.rst b/book/forms.rst index b1c88f77da5..0fde7985056 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -80,6 +80,9 @@ from inside a controller:: use AppBundle\Entity\Task; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Form\Extension\Core\Type\TextType; + use Symfony\Component\Form\Extension\Core\Type\DateType; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; class DefaultController extends Controller { @@ -91,9 +94,9 @@ from inside a controller:: $task->setDueDate(new \DateTime('tomorrow')); $form = $this->createFormBuilder($task) - ->add('task', 'text') - ->add('dueDate', 'date') - ->add('save', 'submit', array('label' => 'Create Task')) + ->add('task', TextType::class) + ->add('dueDate', DateType::class) + ->add('save', SubmitType::class, array('label' => 'Create Task')) ->getForm(); return $this->render('default/new.html.twig', array( @@ -116,16 +119,13 @@ building the form. In this example, you've added two fields to your form - ``task`` and ``dueDate`` - corresponding to the ``task`` and ``dueDate`` properties of the ``Task`` class. -You've also assigned each a "type" (e.g. ``text``, ``date``), which, among -other things, determines which HTML form tag(s) is rendered for that field. +You've also assigned each a "type" (e.g. ``TextType`` and ``DateType``), +represented by its fully qualified class name. Among other things, it determines +which HTML form tag(s) is rendered for that field. Finally, you added a submit button with a custom label for submitting the form to the server. -.. versionadded:: 2.3 - Support for submit buttons was introduced in Symfony 2.3. Before that, you had - to add buttons to the form's HTML manually. - Symfony comes with many built-in types that will be discussed shortly (see :ref:`book-forms-type-reference`). @@ -224,9 +224,9 @@ your controller:: $task = new Task(); $form = $this->createFormBuilder($task) - ->add('task', 'text') - ->add('dueDate', 'date') - ->add('save', 'submit', array('label' => 'Create Task')) + ->add('task', TextType::class) + ->add('dueDate', DateType::class) + ->add('save', SubmitType::class, array('label' => 'Create Task')) ->getForm(); $form->handleRequest($request); @@ -241,19 +241,13 @@ your controller:: 'form' => $form->createView(), )); } - + .. caution:: Be aware that the ``createView()`` method should be called *after* ``handleRequest`` is called. Otherwise, changes done in the ``*_SUBMIT`` events aren't applied to the view (like validation errors). -.. versionadded:: 2.3 - The :method:`Symfony\\Component\\Form\\FormInterface::handleRequest` method - was introduced in Symfony 2.3. Previously, the ``$request`` was passed - to the ``submit`` method - a strategy which is deprecated and will be - removed in Symfony 3.0. For details on that method, see :ref:`cookbook-form-submit-request`. - This controller follows a common pattern for handling forms, and has three possible paths: @@ -296,18 +290,15 @@ possible paths: Submitting Forms with Multiple Buttons ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.3 - Support for buttons in forms was introduced in Symfony 2.3. - When your form contains more than one submit button, you will want to check which of the buttons was clicked to adapt the program flow in your controller. To do this, add a second button with the caption "Save and add" to your form:: $form = $this->createFormBuilder($task) - ->add('task', 'text') - ->add('dueDate', 'date') - ->add('save', 'submit', array('label' => 'Create Task')) - ->add('saveAndAdd', 'submit', array('label' => 'Save and Add')) + ->add('task', TextType::class) + ->add('dueDate', DateType::class) + ->add('save', SubmitType::class, array('label' => 'Create Task')) + ->add('saveAndAdd', SubmitType::class, array('label' => 'Save and Add')) ->getForm(); In your controller, use the button's @@ -474,10 +465,6 @@ you'll need to specify which validation group(s) your form should use:: 'validation_groups' => array('registration'), ))->add(...); -.. versionadded:: 2.7 - The ``configureOptions()`` method was introduced in Symfony 2.7. Previously, - the method was called ``setDefaultOptions()``. - If you're creating :ref:`form classes ` (a good practice), then you'll need to add the following to the ``configureOptions()`` method:: @@ -500,9 +487,6 @@ be used to validate the underlying object. Disabling Validation ~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.3 - The ability to set ``validation_groups`` to false was introduced in Symfony 2.3. - Sometimes it is useful to suppress the validation of a form altogether. For these cases you can set the ``validation_groups`` option to ``false``:: @@ -603,9 +587,6 @@ work in the book section about :ref:`validation groups createFormBuilder($task) // ... - ->add('nextStep', 'submit') - ->add('previousStep', 'submit') + ->add('nextStep', SubmitType::class) + ->add('previousStep', SubmitType::class) ->getForm(); Then, we configure the button for returning to the previous step to run @@ -626,7 +607,7 @@ so we set its ``validation_groups`` option to false:: $form = $this->createFormBuilder($task) // ... - ->add('previousStep', 'submit', array( + ->add('previousStep', SubmitType::class, array( 'validation_groups' => false, )) ->getForm(); @@ -659,11 +640,11 @@ Field Type Options Each field type has a number of options that can be used to configure it. For example, the ``dueDate`` field is currently being rendered as 3 select -boxes. However, the :doc:`date field ` can be +boxes. However, the :doc:`DateType ` can be configured to be rendered as a single text box (where the user would enter the date as a string in the box):: - ->add('dueDate', 'date', array('widget' => 'single_text')) + ->add('dueDate', DateType::class, array('widget' => 'single_text')) .. image:: /images/book/form-simple2.png :align: center @@ -681,7 +662,7 @@ the documentation for each type. :ref:`disable HTML5 validation ` or set the ``required`` option on your field to ``false``:: - ->add('dueDate', 'date', array( + ->add('dueDate', DateType::class, array( 'widget' => 'single_text', 'required' => false )) @@ -700,7 +681,7 @@ the documentation for each type. The label for the form field can be set using the ``label`` option, which can be applied to any field:: - ->add('dueDate', 'date', array( + ->add('dueDate', DateType::class, array( 'widget' => 'single_text', 'label' => 'Due Date', )) @@ -721,7 +702,7 @@ Now that you've added validation metadata to the ``Task`` class, Symfony already knows a bit about your fields. If you allow it, Symfony can "guess" the type of your field and set it up for you. In this example, Symfony can guess from the validation rules that both the ``task`` field is a normal -``text`` field and the ``dueDate`` field is a ``date`` field:: +``TextType`` field and the ``dueDate`` field is a ``DateType`` field:: public function newAction() { @@ -730,7 +711,7 @@ guess from the validation rules that both the ``task`` field is a normal $form = $this->createFormBuilder($task) ->add('task') ->add('dueDate', null, array('widget' => 'single_text')) - ->add('save', 'submit') + ->add('save', SubmitType::class) ->getForm(); } @@ -991,9 +972,9 @@ ways. If you build your form in the controller, you can use ``setAction()`` and $form = $this->createFormBuilder($task) ->setAction($this->generateUrl('target_route')) ->setMethod('GET') - ->add('task', 'text') - ->add('dueDate', 'date') - ->add('save', 'submit') + ->add('task', TextType::class) + ->add('dueDate', DateType::class) + ->add('save', SubmitType::class) ->getForm(); .. note:: @@ -1005,7 +986,10 @@ In :ref:`book-form-creating-form-classes` you will learn how to move the form building code into separate classes. When using an external form class in the controller, you can pass the action and method as form options:: - $form = $this->createForm(new TaskType(), $task, array( + use AppBundle\Form\Type\TaskType; + // ... + + $form = $this->createForm(TaskType::class, $task, array( 'action' => $this->generateUrl('target_route'), 'method' => 'GET', )); @@ -1024,7 +1008,7 @@ to the ``form()`` or the ``form_start()`` helper: start($form, array( - 'action' => $view['router']->generate('target_route'), + 'action' => $view['router']->path('target_route'), 'method' => 'GET', )) ?> @@ -1055,6 +1039,7 @@ that will house the logic for building the task form:: use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; class TaskType extends AbstractType { @@ -1063,36 +1048,21 @@ that will house the logic for building the task form:: $builder ->add('task') ->add('dueDate', null, array('widget' => 'single_text')) - ->add('save', 'submit') + ->add('save', SubmitType::class) ; } - - public function getName() - { - return 'app_task'; - } } -.. caution:: - - The ``getName()`` method returns the identifier of this form "type". These - identifiers must be unique in the application. Unless you want to override - a built-in type, they should be different from the default Symfony types - and from any type defined by a third-party bundle installed in your application. - Consider prefixing your types with ``app_`` to avoid identifier collisions. - This new class contains all the directions needed to create the task form. It can be used to quickly build a form object in the controller:: // src/AppBundle/Controller/DefaultController.php - - // add this new use statement at the top of the class use AppBundle\Form\Type\TaskType; public function newAction() { $task = ...; - $form = $this->createForm(new TaskType(), $task); + $form = $this->createForm(TaskType::class, $task); // ... } @@ -1139,7 +1109,7 @@ the choice is ultimately up to you. $builder ->add('task') ->add('dueDate', null, array('mapped' => false)) - ->add('save', 'submit') + ->add('save', SubmitType::class) ; } @@ -1159,8 +1129,8 @@ the choice is ultimately up to you. Defining your Forms as Services ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Defining your form type as a service is a good practice and makes it really -easy to use in your application. +Your form type might have some external dependencies. You can define your form +type as a service, and inject all dependencies you need. .. note:: @@ -1168,6 +1138,39 @@ easy to use in your application. :doc:`later on in this book `. Things will be more clear after reading that chapter. +You might want to use a service defined as ``app.my_service`` in your form +type. Create a constructor to your form type to receive the service:: + + // src/AppBundle/Form/Type/TaskType.php + namespace AppBundle\Form\Type; + + use App\Utility\MyService; + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; + + class TaskType extends AbstractType + { + private $myService; + + public function __construct(MyService $myService) + { + $this->myService = $myService; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + // You can now use myService. + $builder + ->add('task') + ->add('dueDate', null, array('widget' => 'single_text')) + ->add('save', SubmitType::class) + ; + } + } + +Define your form type as a service. + .. configuration-block:: .. code-block:: yaml @@ -1176,8 +1179,9 @@ easy to use in your application. services: app.form.type.task: class: AppBundle\Form\Type\TaskType + arguments: ["@app.my_service"] tags: - - { name: form.type, alias: app_task } + - { name: form.type } .. code-block:: xml @@ -1189,7 +1193,8 @@ easy to use in your application. - + + @@ -1197,43 +1202,11 @@ easy to use in your application. .. code-block:: php // src/AppBundle/Resources/config/services.php - $container - ->register( - 'app.form.type.task', - 'AppBundle\Form\Type\TaskType' - ) - ->addTag('form.type', array( - 'alias' => 'app_task', - )) - ; - -That's it! Now you can use your form type directly in a controller:: - - // src/AppBundle/Controller/DefaultController.php - // ... - - public function newAction() - { - $task = ...; - $form = $this->createForm('task', $task); + use Symfony\Component\DependencyInjection\Reference; - // ... - } - -or even use from within the form type of another form:: - - // src/AppBundle/Form/Type/ListType.php - // ... - - class ListType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - // ... - - $builder->add('someTask', 'task'); - } - } + $container->register('app.form.type.task', 'AppBundle\Form\Type\TaskType') + ->addArgument(new Reference('app.my_service')) + ->addTag('form.type') Read :ref:`form-cookbook-form-field-service` for more information. @@ -1359,11 +1332,6 @@ create a form class so that a ``Category`` object can be modified by the user:: 'data_class' => 'AppBundle\Entity\Category', )); } - - public function getName() - { - return 'category'; - } } The end goal is to allow the ``Category`` of a ``Task`` to be modified right @@ -1374,12 +1342,13 @@ class: .. code-block:: php use Symfony\Component\Form\FormBuilderInterface; + use AppBundle\Form\Type\CategoryType; public function buildForm(FormBuilderInterface $builder, array $options) { // ... - $builder->add('category', new CategoryType()); + $builder->add('category', CategoryType::class); } The fields from ``CategoryType`` can now be rendered alongside those from @@ -1426,7 +1395,7 @@ form with many ``Product`` sub-forms). This is done by using the ``collection`` field type. For more information see the ":doc:`/cookbook/form/form_collections`" cookbook -entry and the :doc:`collection ` field type reference. +entry and the :doc:`CollectionType ` reference. .. index:: single: Forms; Theming @@ -1850,10 +1819,10 @@ an array of the submitted data. This is actually really easy:: { $defaultData = array('message' => 'Type your message here'); $form = $this->createFormBuilder($defaultData) - ->add('name', 'text') - ->add('email', 'email') - ->add('message', 'textarea') - ->add('send', 'submit') + ->add('name', TextType::class) + ->add('email', EmailType::class) + ->add('message', TextareaType::class) + ->add('send', SubmitType::class) ->getForm(); $form->handleRequest($request); @@ -1914,12 +1883,13 @@ but here's a short example: use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; + use Symfony\Component\Form\Extension\Core\Type\TextType; $builder - ->add('firstName', 'text', array( + ->add('firstName', TextType::class, array( 'constraints' => new Length(array('min' => 3)), )) - ->add('lastName', 'text', array( + ->add('lastName', TextType::class, array( 'constraints' => array( new NotBlank(), new Length(array('min' => 3)), @@ -1959,7 +1929,7 @@ Learn more from the Cookbook ---------------------------- * :doc:`/cookbook/doctrine/file_uploads` -* :doc:`File Field Reference ` +* :doc:`FileType Reference ` * :doc:`Creating Custom Field Types ` * :doc:`/cookbook/form/form_customization` * :doc:`/cookbook/form/dynamic_form_modification` diff --git a/book/from_flat_php_to_symfony2.rst b/book/from_flat_php_to_symfony2.rst index 899f8280f43..6c87f84e4f4 100644 --- a/book/from_flat_php_to_symfony2.rst +++ b/book/from_flat_php_to_symfony2.rst @@ -417,7 +417,7 @@ content: { "require": { - "symfony/symfony": "2.6.*" + "symfony/symfony": "3.0.*" }, "autoload": { "files": ["model.php","controllers.php"] @@ -573,7 +573,7 @@ database and the Templating component to render a template and return a