From 90c83b214c39c26633c3f7a0d4b8541bef6c6a26 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 8 Nov 2020 14:35:47 +0100 Subject: [PATCH 1/3] Added PHP types to the Doctrine related articles --- doctrine.rst | 193 ++++++++++++++++---------- doctrine/associations.rst | 81 +++++++---- doctrine/dbal.rst | 4 +- doctrine/events.rst | 16 +-- doctrine/multiple_entity_managers.rst | 9 +- doctrine/resolve_target_entity.rst | 5 +- 6 files changed, 186 insertions(+), 122 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 21b14c2ae4c..8226bf22700 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -51,7 +51,7 @@ The database connection information is stored as an environment variable called # to use sqlite: # DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" - + # to use postgresql: # DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8" @@ -506,46 +506,60 @@ Fetching an object back out of the database is even easier. Suppose you want to be able to go to ``/product/1`` to see your new product:: // src/Controller/ProductController.php + namespace App\Controller; + + use App\Entity\Product; + use Symfony\Component\HttpFoundation\Response; // ... - /** - * @Route("/product/{id}", name="product_show") - */ - public function show($id) + class ProductController extends AbstractController { - $product = $this->getDoctrine() - ->getRepository(Product::class) - ->find($id); - - if (!$product) { - throw $this->createNotFoundException( - 'No product found for id '.$id - ); - } + /** + * @Route("/product/{id}", name="product_show") + */ + public function show(int $id): Response + { + $product = $this->getDoctrine() + ->getRepository(Product::class) + ->find($id); + + if (!$product) { + throw $this->createNotFoundException( + 'No product found for id '.$id + ); + } - return new Response('Check out this great product: '.$product->getName()); + return new Response('Check out this great product: '.$product->getName()); - // or render a template - // in the template, print things with {{ product.name }} - // return $this->render('product/show.html.twig', ['product' => $product]); + // or render a template + // in the template, print things with {{ product.name }} + // return $this->render('product/show.html.twig', ['product' => $product]); + } } Another possibility is to use the ``ProductRepository`` using Symfony's autowiring and injected by the dependency injection container:: // src/Controller/ProductController.php - // ... + namespace App\Controller; + + use App\Entity\Product; use App\Repository\ProductRepository; + use Symfony\Component\HttpFoundation\Response; + // ... - /** - * @Route("/product/{id}", name="product_show") - */ - public function show($id, ProductRepository $productRepository) + class ProductController extends AbstractController { - $product = $productRepository - ->find($id); + /** + * @Route("/product/{id}", name="product_show") + */ + public function show(int $id, ProductRepository $productRepository): Response + { + $product = $productRepository + ->find($id); - // ... + // ... + } } Try it out! @@ -611,15 +625,23 @@ for you automatically! First, install the bundle in case you don't have it: Now, simplify your controller:: // src/Controller/ProductController.php + namespace App\Controller; + use App\Entity\Product; + use App\Repository\ProductRepository; + use Symfony\Component\HttpFoundation\Response; + // ... - /** - * @Route("/product/{id}", name="product_show") - */ - public function show(Product $product) + class ProductController extends AbstractController { - // use the Product! - // ... + /** + * @Route("/product/{id}", name="product_show") + */ + public function show(Product $product): Response + { + // use the Product! + // ... + } } That's it! The bundle uses the ``{id}`` from the route to query for the ``Product`` @@ -633,26 +655,37 @@ Updating an Object Once you've fetched an object from Doctrine, you interact with it the same as with any PHP model:: - /** - * @Route("/product/edit/{id}") - */ - public function update($id) + // src/Controller/ProductController.php + namespace App\Controller; + + use App\Entity\Product; + use App\Repository\ProductRepository; + use Symfony\Component\HttpFoundation\Response; + // ... + + class ProductController extends AbstractController { - $entityManager = $this->getDoctrine()->getManager(); - $product = $entityManager->getRepository(Product::class)->find($id); + /** + * @Route("/product/edit/{id}") + */ + public function update(int $id): Response + { + $entityManager = $this->getDoctrine()->getManager(); + $product = $entityManager->getRepository(Product::class)->find($id); - if (!$product) { - throw $this->createNotFoundException( - 'No product found for id '.$id - ); - } + if (!$product) { + throw $this->createNotFoundException( + 'No product found for id '.$id + ); + } - $product->setName('New product name!'); - $entityManager->flush(); + $product->setName('New product name!'); + $entityManager->flush(); - return $this->redirectToRoute('product_show', [ - 'id' => $product->getId() - ]); + return $this->redirectToRoute('product_show', [ + 'id' => $product->getId() + ]); + } } Using Doctrine to edit an existing product consists of three steps: @@ -728,7 +761,7 @@ a new method for this to your repository:: /** * @return Product[] */ - public function findAllGreaterThanPrice($price): array + public function findAllGreaterThanPrice(int $price): array { $entityManager = $this->getEntityManager(); @@ -773,25 +806,28 @@ based on PHP conditions):: // src/Repository/ProductRepository.php // ... - public function findAllGreaterThanPrice($price, $includeUnavailableProducts = false): array + class ProductRepository extends ServiceEntityRepository { - // automatically knows to select Products - // the "p" is an alias you'll use in the rest of the query - $qb = $this->createQueryBuilder('p') - ->where('p.price > :price') - ->setParameter('price', $price) - ->orderBy('p.price', 'ASC'); - - if (!$includeUnavailableProducts) { - $qb->andWhere('p.available = TRUE'); - } + public function findAllGreaterThanPrice(int $price, bool $includeUnavailableProducts = false): array + { + // automatically knows to select Products + // the "p" is an alias you'll use in the rest of the query + $qb = $this->createQueryBuilder('p') + ->where('p.price > :price') + ->setParameter('price', $price) + ->orderBy('p.price', 'ASC'); + + if (!$includeUnavailableProducts) { + $qb->andWhere('p.available = TRUE'); + } - $query = $qb->getQuery(); + $query = $qb->getQuery(); - return $query->execute(); + return $query->execute(); - // to get just one result: - // $product = $query->setMaxResults(1)->getOneOrNullResult(); + // to get just one result: + // $product = $query->setMaxResults(1)->getOneOrNullResult(); + } } Querying with SQL @@ -802,20 +838,23 @@ In addition, you can query directly with SQL if you need to:: // src/Repository/ProductRepository.php // ... - public function findAllGreaterThanPrice($price): array + class ProductRepository extends ServiceEntityRepository { - $conn = $this->getEntityManager()->getConnection(); - - $sql = ' - SELECT * FROM product p - WHERE p.price > :price - ORDER BY p.price ASC - '; - $stmt = $conn->prepare($sql); - $stmt->execute(['price' => $price]); - - // returns an array of arrays (i.e. a raw data set) - return $stmt->fetchAllAssociative(); + public function findAllGreaterThanPrice(int $price): array + { + $conn = $this->getEntityManager()->getConnection(); + + $sql = ' + SELECT * FROM product p + WHERE p.price > :price + ORDER BY p.price ASC + '; + $stmt = $conn->prepare($sql); + $stmt->execute(['price' => $price]); + + // returns an array of arrays (i.e. a raw data set) + return $stmt->fetchAllAssociative(); + } } With SQL, you will get back raw data, not objects (unless you use the `NativeQuery`_ diff --git a/doctrine/associations.rst b/doctrine/associations.rst index 8bdddab536b..4a2fafb6467 100644 --- a/doctrine/associations.rst +++ b/doctrine/associations.rst @@ -327,7 +327,7 @@ Now you can see this new code in action! Imagine you're inside a controller:: /** * @Route("/product", name="product") */ - public function index() + public function index(): Response { $category = new Category(); $category->setName('Computer Peripherals'); @@ -378,20 +378,26 @@ When you need to fetch associated objects, your workflow looks like it did before. First, fetch a ``$product`` object and then access its related ``Category`` object:: + // src/Controller/ProductController.php + namespace App\Controller; + use App\Entity\Product; // ... - public function show($id) + class ProductController extends AbstractController { - $product = $this->getDoctrine() - ->getRepository(Product::class) - ->find($id); + public function show(int $id): Response + { + $product = $this->getDoctrine() + ->getRepository(Product::class) + ->find($id); - // ... + // ... - $categoryName = $product->getCategory()->getName(); + $categoryName = $product->getCategory()->getName(); - // ... + // ... + } } In this example, you first query for a ``Product`` object based on the product's @@ -411,15 +417,21 @@ the category (i.e. it's "lazily loaded"). Because we mapped the optional ``OneToMany`` side, you can also query in the other direction:: - public function showProducts($id) + // src/Controller/ProductController.php + + // ... + class ProductController extends AbstractController { - $category = $this->getDoctrine() - ->getRepository(Category::class) - ->find($id); + public function showProducts(int $id): Response + { + $category = $this->getDoctrine() + ->getRepository(Category::class) + ->find($id); - $products = $category->getProducts(); + $products = $category->getProducts(); - // ... + // ... + } } In this case, the same things occur: you first query for a single ``Category`` @@ -475,18 +487,23 @@ can avoid the second query by issuing a join in the original query. Add the following method to the ``ProductRepository`` class:: // src/Repository/ProductRepository.php - public function findOneByIdJoinedToCategory($productId) + + // ... + class ProductRepository extends ServiceEntityRepository { - $entityManager = $this->getEntityManager(); + public function findOneByIdJoinedToCategory(int $productId): ?Product + { + $entityManager = $this->getEntityManager(); - $query = $entityManager->createQuery( - 'SELECT p, c - FROM App\Entity\Product p - INNER JOIN p.category c - WHERE p.id = :id' - )->setParameter('id', $productId); + $query = $entityManager->createQuery( + 'SELECT p, c + FROM App\Entity\Product p + INNER JOIN p.category c + WHERE p.id = :id' + )->setParameter('id', $productId); - return $query->getOneOrNullResult(); + return $query->getOneOrNullResult(); + } } This will *still* return an array of ``Product`` objects. But now, when you call @@ -495,15 +512,21 @@ This will *still* return an array of ``Product`` objects. But now, when you call Now, you can use this method in your controller to query for a ``Product`` object and its related ``Category`` in one query:: - public function show($id) + // src/Controller/ProductController.php + + // ... + class ProductController extends AbstractController { - $product = $this->getDoctrine() - ->getRepository(Product::class) - ->findOneByIdJoinedToCategory($id); + public function show(int $id): Response + { + $product = $this->getDoctrine() + ->getRepository(Product::class) + ->findOneByIdJoinedToCategory($id); - $category = $product->getCategory(); + $category = $product->getCategory(); - // ... + // ... + } } .. _associations-inverse-side: diff --git a/doctrine/dbal.rst b/doctrine/dbal.rst index 3ddb98d837f..80c145d3d6a 100644 --- a/doctrine/dbal.rst +++ b/doctrine/dbal.rst @@ -48,10 +48,12 @@ object:: namespace App\Controller; use Doctrine\DBAL\Driver\Connection; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Response; class UserController extends AbstractController { - public function index(Connection $connection) + public function index(Connection $connection): Response { $users = $connection->fetchAll('SELECT * FROM users'); diff --git a/doctrine/events.rst b/doctrine/events.rst index 1ce0bfa8ac0..309d10a7e12 100644 --- a/doctrine/events.rst +++ b/doctrine/events.rst @@ -74,7 +74,7 @@ define a callback for the ``prePersist`` Doctrine event: /** * @ORM\PrePersist */ - public function setCreatedAtValue() + public function setCreatedAtValue(): void { $this->createdAt = new \DateTime(); } @@ -132,7 +132,7 @@ do so, define a listener for the ``postPersist`` Doctrine event:: { // the listener methods receive an argument which gives you access to // both the entity object of the event and the entity manager itself - public function postPersist(LifecycleEventArgs $args) + public function postPersist(LifecycleEventArgs $args): void { $entity = $args->getObject(); @@ -241,7 +241,7 @@ define a listener for the ``postUpdate`` Doctrine event:: { // the entity listener methods receive two arguments: // the entity instance and the lifecycle event - public function postUpdate(User $user, LifecycleEventArgs $event) + public function postUpdate(User $user, LifecycleEventArgs $event): void { // ... do something to notify the changes } @@ -365,7 +365,7 @@ want to log all the database activity. To do so, define a subscriber for the { // this method can only return the event names; you cannot define a // custom method name to execute when each event triggers - public function getSubscribedEvents() + public function getSubscribedEvents(): array { return [ Events::postPersist, @@ -377,22 +377,22 @@ want to log all the database activity. To do so, define a subscriber for the // callback methods must be called exactly like the events they listen to; // they receive an argument of type LifecycleEventArgs, which gives you access // to both the entity object of the event and the entity manager itself - public function postPersist(LifecycleEventArgs $args) + public function postPersist(LifecycleEventArgs $args): void { $this->logActivity('persist', $args); } - public function postRemove(LifecycleEventArgs $args) + public function postRemove(LifecycleEventArgs $args): void { $this->logActivity('remove', $args); } - public function postUpdate(LifecycleEventArgs $args) + public function postUpdate(LifecycleEventArgs $args): void { $this->logActivity('update', $args); } - private function logActivity(string $action, LifecycleEventArgs $args) + private function logActivity(string $action, LifecycleEventArgs $args): void { $entity = $args->getObject(); diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst index faffc480877..ba3475dbfbc 100644 --- a/doctrine/multiple_entity_managers.rst +++ b/doctrine/multiple_entity_managers.rst @@ -234,12 +234,11 @@ the default entity manager (i.e. ``default``) is returned:: namespace App\Controller; // ... - use Doctrine\ORM\EntityManagerInterface; class UserController extends AbstractController { - public function index(EntityManagerInterface $entityManager) + public function index(EntityManagerInterface $entityManager): Response { // These methods also return the default entity manager, but it's preferred // to get it by injecting EntityManagerInterface in the action method @@ -250,6 +249,8 @@ the default entity manager (i.e. ``default``) is returned:: // Both of these return the "customer" entity manager $customerEntityManager = $this->getDoctrine()->getManager('customer'); $customerEntityManager = $this->get('doctrine.orm.customer_entity_manager'); + + // ... } } @@ -268,7 +269,7 @@ The same applies to repository calls:: class UserController extends AbstractController { - public function index() + public function index(): Response { // Retrieves a repository managed by the "default" em $products = $this->getDoctrine() @@ -287,6 +288,8 @@ The same applies to repository calls:: ->getRepository(Customer::class, 'customer') ->findAll() ; + + // ... } } diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst index 36038fd9f3c..765f5d187ce 100644 --- a/doctrine/resolve_target_entity.rst +++ b/doctrine/resolve_target_entity.rst @@ -96,10 +96,7 @@ An InvoiceSubjectInterface:: // will need to access on the subject so that you can // be sure that you have access to those methods. - /** - * @return string - */ - public function getName(); + public function getName(): string; } Next, you need to configure the listener, which tells the DoctrineBundle From aec1016d94db1de37eaedb7f8567046168d0805a Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 8 Nov 2020 14:57:22 +0100 Subject: [PATCH 2/3] Added PHP types to the Form related articles --- form/create_custom_field_type.rst | 18 ++-- form/create_form_type_extension.rst | 8 +- form/data_based_validation.rst | 6 +- form/data_mappers.rst | 8 +- form/data_transformers.rst | 28 +++--- form/direct_submit.rst | 3 +- form/disabling_validation.rst | 2 +- form/dynamic_form_modification.rst | 31 ++++--- form/embedded.rst | 10 +- form/events.rst | 10 +- form/form_collections.rst | 102 +++++++++++---------- form/form_themes.rst | 10 +- form/inherit_data_option.rst | 12 +-- form/type_guesser.rst | 14 +-- form/use_empty_data.rst | 6 +- form/validation_group_service_resolver.rst | 8 +- form/validation_groups.rst | 2 +- form/without_class.rst | 43 +++++---- forms.rst | 100 +++++++++++--------- 19 files changed, 224 insertions(+), 197 deletions(-) diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst index 58e265fd2ad..59816006041 100644 --- a/form/create_custom_field_type.rst +++ b/form/create_custom_field_type.rst @@ -38,7 +38,7 @@ By convention they are stored in the ``src/Form/Type/`` directory:: class ShippingType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'choices' => [ @@ -49,7 +49,7 @@ By convention they are stored in the ``src/Form/Type/`` directory:: ]); } - public function getParent() + public function getParent(): string { return ChoiceType::class; } @@ -82,7 +82,7 @@ Now you can add this form type when :doc:`creating Symfony forms `:: class OrderType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder // ... @@ -168,7 +168,7 @@ in the postal address. For the moment, all fields are of type ``TextType``:: { // ... - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('addressLine1', TextType::class, [ @@ -209,7 +209,7 @@ correctly rendered in any template:: class OrderType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder // ... @@ -254,7 +254,7 @@ to define, validate and process their values:: { // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { // this defines the available options and their default values when // they are not configured explicitly when using the form type @@ -293,7 +293,7 @@ Now you can configure these options when using the form type:: class OrderType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder // ... @@ -320,7 +320,7 @@ The last step is to use these options when building the form:: { // ... - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... @@ -473,7 +473,7 @@ defined by the form or be completely independent:: // ... - public function buildView(FormView $view, FormInterface $form, array $options) + public function buildView(FormView $view, FormInterface $form, array $options): void { // pass the form type option directly to the template $view->vars['isExtendedAddress'] = $options['is_extended_address']; diff --git a/form/create_form_type_extension.rst b/form/create_form_type_extension.rst index 59b1c06e9fe..55d3d34776f 100644 --- a/form/create_form_type_extension.rst +++ b/form/create_form_type_extension.rst @@ -114,7 +114,7 @@ the database:: // ... - public function getWebPath() + public function getWebPath(): string { // ... $webPath being the full image URL, to be used in templates @@ -149,13 +149,13 @@ For example:: return [FileType::class]; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { // makes it legal for FileType fields to have an image_property option $resolver->setDefined(['image_property']); } - public function buildView(FormView $view, FormInterface $form, array $options) + public function buildView(FormView $view, FormInterface $form, array $options): void { if (isset($options['image_property'])) { // this will be whatever class/entity is bound to your form (e.g. Media) @@ -221,7 +221,7 @@ next to the file field. For example:: class MediaType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('name', TextType::class) diff --git a/form/data_based_validation.rst b/form/data_based_validation.rst index 383883ba91f..226284db439 100644 --- a/form/data_based_validation.rst +++ b/form/data_based_validation.rst @@ -12,7 +12,7 @@ to an array callback:: use Symfony\Component\OptionsResolver\OptionsResolver; // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'validation_groups' => [ @@ -32,7 +32,7 @@ example). You can also define whole logic inline by using a ``Closure``:: use Symfony\Component\OptionsResolver\OptionsResolver; // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'validation_groups' => function (FormInterface $form) { @@ -56,7 +56,7 @@ of the entity as well you have to adjust the option as follows:: use Symfony\Component\OptionsResolver\OptionsResolver; // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'validation_groups' => function (FormInterface $form) { diff --git a/form/data_mappers.rst b/form/data_mappers.rst index 15e66ce54b3..c14eabd7683 100644 --- a/form/data_mappers.rst +++ b/form/data_mappers.rst @@ -98,7 +98,7 @@ in your form type:: /** * @param Color|null $viewData */ - public function mapDataToForms($viewData, $forms) + public function mapDataToForms($viewData, $forms): void { // there is no data yet, so nothing to prepopulate if (null === $viewData) { @@ -119,7 +119,7 @@ in your form type:: $forms['blue']->setData($viewData->getBlue()); } - public function mapFormsToData($forms, &$viewData) + public function mapFormsToData($forms, &$viewData): void { /** @var FormInterface[] $forms */ $forms = iterator_to_array($forms); @@ -158,7 +158,7 @@ method:: final class ColorType extends AbstractType implements DataMapperInterface { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('red', IntegerType::class, [ @@ -177,7 +177,7 @@ method:: ; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { // when creating a new color, the initial data should be null $resolver->setDefault('empty_data', null); diff --git a/form/data_transformers.rst b/form/data_transformers.rst index 4a087a0aaff..aa0e88789bf 100644 --- a/form/data_transformers.rst +++ b/form/data_transformers.rst @@ -40,12 +40,12 @@ Suppose you have a Task form with a tags ``text`` type:: // ... class TaskType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('tags', TextType::class); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Task::class, @@ -72,7 +72,7 @@ class:: class TaskType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('tags', TextType::class); @@ -136,7 +136,7 @@ Start by setting up the text field like normal:: // ... class TaskType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('description', TextareaType::class) @@ -144,7 +144,7 @@ Start by setting up the text field like normal:: ; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Task::class, @@ -188,9 +188,8 @@ to and from the issue number and the ``Issue`` object:: * Transforms an object (issue) to a string (number). * * @param Issue|null $issue - * @return string */ - public function transform($issue) + public function transform($issue): string { if (null === $issue) { return ''; @@ -203,10 +202,9 @@ to and from the issue number and the ``Issue`` object:: * Transforms a string (number) to an object (issue). * * @param string $issueNumber - * @return Issue|null * @throws TransformationFailedException if object (issue) is not found. */ - public function reverseTransform($issueNumber) + public function reverseTransform($issueNumber): ?Issue { // no issue number? It's optional, so that's ok if (!$issueNumber) { @@ -273,7 +271,7 @@ and type-hint the new class:: $this->transformer = $transformer; } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('description', TextareaType::class) @@ -306,7 +304,7 @@ end-user error message in the data transformer using the { // ... - public function reverseTransform($issueNumber) + public function reverseTransform($issueNumber): ?Issue { // ... @@ -394,19 +392,19 @@ First, create the custom field type class:: $this->transformer = $transformer; } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->addModelTransformer($this->transformer); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'invalid_message' => 'The selected issue does not exist', ]); } - public function getParent() + public function getParent(): string { return TextType::class; } @@ -427,7 +425,7 @@ As long as you're using :ref:`autowire ` and class TaskType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('description', TextareaType::class) diff --git a/form/direct_submit.rst b/form/direct_submit.rst index ef6897611ad..876ad3964b1 100644 --- a/form/direct_submit.rst +++ b/form/direct_submit.rst @@ -11,9 +11,10 @@ to detect when the form has been submitted. However, you can also use the control over when exactly your form is submitted and what data is passed to it:: use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; // ... - public function new(Request $request) + public function new(Request $request): Response { $task = new Task(); $form = $this->createForm(TaskType::class, $task); diff --git a/form/disabling_validation.rst b/form/disabling_validation.rst index f23cc73dbbf..2844d0c865d 100644 --- a/form/disabling_validation.rst +++ b/form/disabling_validation.rst @@ -9,7 +9,7 @@ these cases you can set the ``validation_groups`` option to ``false``:: use Symfony\Component\OptionsResolver\OptionsResolver; - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'validation_groups' => false, diff --git a/form/dynamic_form_modification.rst b/form/dynamic_form_modification.rst index 69339480248..279b5b4118d 100644 --- a/form/dynamic_form_modification.rst +++ b/form/dynamic_form_modification.rst @@ -45,13 +45,13 @@ a bare form class looks like:: class ProductType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('name'); $builder->add('price'); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Product::class, @@ -92,7 +92,7 @@ creating that particular field is delegated to an event listener:: class ProductType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('price'); @@ -109,7 +109,7 @@ object is new (e.g. hasn't been persisted to the database). Based on that, the event listener might look like the following:: // ... - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { @@ -151,14 +151,14 @@ you can also move the logic for creating the ``name`` field to an class AddNameFieldSubscriber implements EventSubscriberInterface { - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { // Tells the dispatcher that you want to listen on the form.pre_set_data // event and that the preSetData method should be called. return [FormEvents::PRE_SET_DATA => 'preSetData']; } - public function preSetData(FormEvent $event) + public function preSetData(FormEvent $event): void { $product = $event->getData(); $form = $event->getForm(); @@ -179,7 +179,7 @@ Great! Now use that in your form class:: class ProductType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('price'); @@ -217,7 +217,7 @@ Using an event listener, your form might look like this:: class FriendMessageFormType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('subject', TextType::class) @@ -274,7 +274,7 @@ security helper to fill in the listener logic:: $this->security = $security; } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('subject', TextType::class) @@ -337,10 +337,12 @@ and :doc:`tag it ` with the ``form.type`` tag. In a controller, create the form like normal:: use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; class FriendMessageController extends AbstractController { - public function new(Request $request) + public function new(Request $request): Response { $form = $this->createForm(FriendMessageFormType::class); @@ -351,7 +353,7 @@ In a controller, create the form like normal:: You can also embed the form type into another form:: // inside some other "form type" class - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('message', FriendMessageFormType::class); } @@ -384,7 +386,7 @@ sport like this:: class SportMeetupType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('sport', EntityType::class, [ @@ -448,7 +450,7 @@ The type would now look like:: class SportMeetupType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('sport', EntityType::class, [ @@ -515,11 +517,12 @@ your application. Assume that you have a sport meetup creation controller:: use App\Form\Type\SportMeetupType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; // ... class MeetupController extends AbstractController { - public function create(Request $request) + public function create(Request $request): Response { $meetup = new SportMeetup(); $form = $this->createForm(SportMeetupType::class, $meetup); diff --git a/form/embedded.rst b/form/embedded.rst index 9da8104b143..787580a41d1 100644 --- a/form/embedded.rst +++ b/form/embedded.rst @@ -46,12 +46,12 @@ Next, add a new ``category`` property to the ``Task`` class:: // ... - public function getCategory() + public function getCategory(): ?Category { return $this->category; } - public function setCategory(Category $category = null) + public function setCategory(?Category $category) { $this->category = $category; } @@ -76,12 +76,12 @@ create a form class so that a ``Category`` object can be modified by the user:: class CategoryType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('name'); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Category::class, @@ -97,7 +97,7 @@ class:: use App\Form\CategoryType; use Symfony\Component\Form\FormBuilderInterface; - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... diff --git a/form/events.rst b/form/events.rst index 5620ec96f46..0f2e26e2775 100644 --- a/form/events.rst +++ b/form/events.rst @@ -308,7 +308,7 @@ callback for better readability:: // ... class SubscriptionType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('username', TextType::class) @@ -320,7 +320,7 @@ callback for better readability:: ; } - public function onPreSetData(FormEvent $event) + public function onPreSetData(FormEvent $event): void { // ... } @@ -347,7 +347,7 @@ Consider the following example of a form event subscriber:: class AddEmailFieldListener implements EventSubscriberInterface { - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ FormEvents::PRE_SET_DATA => 'onPreSetData', @@ -355,7 +355,7 @@ Consider the following example of a form event subscriber:: ]; } - public function onPreSetData(FormEvent $event) + public function onPreSetData(FormEvent $event): void { $user = $event->getData(); $form = $event->getForm(); @@ -367,7 +367,7 @@ Consider the following example of a form event subscriber:: } } - public function onPreSubmit(FormEvent $event) + public function onPreSubmit(FormEvent $event): void { $user = $event->getData(); $form = $event->getForm(); diff --git a/form/form_collections.rst b/form/form_collections.rst index 405ffed53e4..cd1f66a7a8f 100644 --- a/form/form_collections.rst +++ b/form/form_collections.rst @@ -15,6 +15,7 @@ Let's start by creating a ``Task`` entity:: namespace App\Entity; use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\Common\Collections\Collection; class Task { @@ -26,17 +27,17 @@ Let's start by creating a ``Task`` entity:: $this->tags = new ArrayCollection(); } - public function getDescription() + public function getDescription(): string { return $this->description; } - public function setDescription($description) + public function setDescription(string $description): void { $this->description = $description; } - public function getTags() + public function getTags(): Collection { return $this->tags; } @@ -58,12 +59,12 @@ objects:: { private $name; - public function getName() + public function getName(): string { return $this->name; } - public function setName($name) + public function setName(string $name): void { $this->name = $name; } @@ -81,12 +82,12 @@ Then, create a form class so that a ``Tag`` object can be modified by the user:: class TagType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('name'); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Tag::class, @@ -110,7 +111,7 @@ inside the task form itself:: class TaskType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('description'); @@ -120,7 +121,7 @@ inside the task form itself:: ]); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Task::class, @@ -138,10 +139,11 @@ In your controller, you'll create a new form from the ``TaskType``:: use App\Form\TaskType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; class TaskController extends AbstractController { - public function new(Request $request) + public function new(Request $request): Response { $task = new Task(); @@ -224,7 +226,7 @@ it will receive an *unknown* number of tags. Otherwise, you'll see a // ... - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... @@ -367,12 +369,12 @@ for the tags in the ``Task`` class:: { // ... - public function addTag(Tag $tag) + public function addTag(Tag $tag): void { $this->tags->add($tag); } - public function removeTag(Tag $tag) + public function removeTag(Tag $tag): void { // ... } @@ -383,7 +385,7 @@ Next, add a ``by_reference`` option to the ``tags`` field and set it to ``false` // src/Form/TaskType.php // ... - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... @@ -484,7 +486,7 @@ you will learn about next!). // src/Entity/Task.php // ... - public function addTag(Tag $tag) + public function addTag(Tag $tag): void { // for a many-to-many association: $tag->addTask($this); @@ -501,7 +503,7 @@ you will learn about next!). // src/Entity/Tag.php // ... - public function addTask(Task $task) + public function addTask(Task $task): void { if (!$this->tasks->contains($task)) { $this->tasks->add($task); @@ -521,7 +523,7 @@ Start by adding the ``allow_delete`` option in the form Type:: // src/Form/TaskType.php // ... - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... @@ -540,7 +542,7 @@ Now, you need to put some code into the ``removeTag()`` method of ``Task``:: { // ... - public function removeTag(Tag $tag) + public function removeTag(Tag $tag): void { $this->tags->removeElement($tag); } @@ -617,52 +619,56 @@ the relationship between the removed ``Tag`` and ``Task`` object. is handling the "update" of your Task:: // src/Controller/TaskController.php + + // ... use App\Entity\Task; use Doctrine\Common\Collections\ArrayCollection; - // ... - public function edit($id, Request $request, EntityManagerInterface $entityManager) + class TaskController extends AbstractController { - if (null === $task = $entityManager->getRepository(Task::class)->find($id)) { - throw $this->createNotFoundException('No task found for id '.$id); - } + public function edit($id, Request $request, EntityManagerInterface $entityManager): Response + { + if (null === $task = $entityManager->getRepository(Task::class)->find($id)) { + throw $this->createNotFoundException('No task found for id '.$id); + } - $originalTags = new ArrayCollection(); + $originalTags = new ArrayCollection(); - // Create an ArrayCollection of the current Tag objects in the database - foreach ($task->getTags() as $tag) { - $originalTags->add($tag); - } + // Create an ArrayCollection of the current Tag objects in the database + foreach ($task->getTags() as $tag) { + $originalTags->add($tag); + } - $editForm = $this->createForm(TaskType::class, $task); + $editForm = $this->createForm(TaskType::class, $task); - $editForm->handleRequest($request); + $editForm->handleRequest($request); - if ($editForm->isSubmitted() && $editForm->isValid()) { - // remove the relationship between the tag and the Task - foreach ($originalTags as $tag) { - if (false === $task->getTags()->contains($tag)) { - // remove the Task from the Tag - $tag->getTasks()->removeElement($task); + if ($editForm->isSubmitted() && $editForm->isValid()) { + // remove the relationship between the tag and the Task + foreach ($originalTags as $tag) { + if (false === $task->getTags()->contains($tag)) { + // remove the Task from the Tag + $tag->getTasks()->removeElement($task); - // if it was a many-to-one relationship, remove the relationship like this - // $tag->setTask(null); + // if it was a many-to-one relationship, remove the relationship like this + // $tag->setTask(null); - $entityManager->persist($tag); + $entityManager->persist($tag); - // if you wanted to delete the Tag entirely, you can also do that - // $entityManager->remove($tag); + // if you wanted to delete the Tag entirely, you can also do that + // $entityManager->remove($tag); + } } - } - $entityManager->persist($task); - $entityManager->flush(); + $entityManager->persist($task); + $entityManager->flush(); - // redirect back to some edit page - return $this->redirectToRoute('task_edit', ['id' => $id]); - } + // redirect back to some edit page + return $this->redirectToRoute('task_edit', ['id' => $id]); + } - // render some form template + // ... render some form template + } } As you can see, adding and removing the elements correctly can be tricky. diff --git a/form/form_themes.rst b/form/form_themes.rst index 3811b042c28..1b5a3594a2d 100644 --- a/form/form_themes.rst +++ b/form/form_themes.rst @@ -266,7 +266,7 @@ form. You can also define this value explicitly with the ``block_name`` option:: use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... @@ -290,7 +290,7 @@ field without having to :doc:`create a custom form type add('name', TextType::class, [ 'block_prefix' => 'wrapped_text', @@ -316,7 +316,7 @@ following complex example where a ``TaskManagerType`` has a collection of class TaskManagerType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options = []) + public function buildForm(FormBuilderInterface $builder, array $options = []): void { // ... $builder->add('taskLists', CollectionType::class, [ @@ -328,7 +328,7 @@ following complex example where a ``TaskManagerType`` has a collection of class TaskListType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options = []) + public function buildForm(FormBuilderInterface $builder, array $options = []): void { // ... $builder->add('tasks', CollectionType::class, [ @@ -339,7 +339,7 @@ following complex example where a ``TaskManagerType`` has a collection of class TaskType { - public function buildForm(FormBuilderInterface $builder, array $options = []) + public function buildForm(FormBuilderInterface $builder, array $options = []): void { $builder->add('name'); // ... diff --git a/form/inherit_data_option.rst b/form/inherit_data_option.rst index 3321ab2153a..f4161a21111 100644 --- a/form/inherit_data_option.rst +++ b/form/inherit_data_option.rst @@ -52,7 +52,7 @@ Start with building two forms for these entities, ``CompanyType`` and ``Customer class CompanyType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('name', TextType::class) @@ -71,7 +71,7 @@ Start with building two forms for these entities, ``CompanyType`` and ``Customer class CustomerType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('firstName', TextType::class) @@ -94,7 +94,7 @@ for that:: class LocationType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('address', TextareaType::class) @@ -103,7 +103,7 @@ for that:: ->add('country', TextType::class); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'inherit_data' => true, @@ -131,7 +131,7 @@ Finally, make this work by adding the location form to your two original forms:: use App\Entity\Company; // ... - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... @@ -148,7 +148,7 @@ Finally, make this work by adding the location form to your two original forms:: use App\Entity\Customer; // ... - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // ... diff --git a/form/type_guesser.rst b/form/type_guesser.rst index f990aad4115..49488e524ba 100644 --- a/form/type_guesser.rst +++ b/form/type_guesser.rst @@ -40,22 +40,24 @@ Start by creating the class and these methods. Next, you'll learn how to fill ea namespace App\Form\TypeGuesser; use Symfony\Component\Form\FormTypeGuesserInterface; + use Symfony\Component\Form\Guess\TypeGuess; + use Symfony\Component\Form\Guess\ValueGuess; class PHPDocTypeGuesser implements FormTypeGuesserInterface { - public function guessType($class, $property) + public function guessType($class, $property): ?TypeGuess { } - public function guessRequired($class, $property) + public function guessRequired($class, $property): ?ValueGuess { } - public function guessMaxLength($class, $property) + public function guessMaxLength($class, $property): ?ValueGuess { } - public function guessPattern($class, $property) + public function guessPattern($class, $property): ?ValueGuess { } } @@ -94,7 +96,7 @@ With this knowledge, you can implement the ``guessType()`` method of the class PHPDocTypeGuesser implements FormTypeGuesserInterface { - public function guessType($class, $property) + public function guessType($class, $property): ?TypeGuess { $annotations = $this->readPhpDocAnnotations($class, $property); @@ -129,7 +131,7 @@ With this knowledge, you can implement the ``guessType()`` method of the } } - protected function readPhpDocAnnotations($class, $property) + protected function readPhpDocAnnotations(string $class, string $property): array { $reflectionProperty = new \ReflectionProperty($class, $property); $phpdoc = $reflectionProperty->getDocComment(); diff --git a/form/use_empty_data.rst b/form/use_empty_data.rst index 6a567286094..c2cba15ad7f 100644 --- a/form/use_empty_data.rst +++ b/form/use_empty_data.rst @@ -9,7 +9,7 @@ form class. This empty data set would be used if you submit your form, but haven't called ``setData()`` on your form or passed in data when you created your form. For example, in a controller:: - public function index() + public function index(): Response { $blog = ...; @@ -61,7 +61,7 @@ that constructor with no arguments:: } // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'empty_data' => new Blog($this->someDependency), @@ -96,7 +96,7 @@ The closure must accept a ``FormInterface`` instance as the first argument:: use Symfony\Component\OptionsResolver\OptionsResolver; // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'empty_data' => function (FormInterface $form) { diff --git a/form/validation_group_service_resolver.rst b/form/validation_group_service_resolver.rst index e497a7556df..ad741fbdb3a 100644 --- a/form/validation_group_service_resolver.rst +++ b/form/validation_group_service_resolver.rst @@ -23,11 +23,7 @@ parameter:: $this->service2 = $service2; } - /** - * @param FormInterface $form - * @return array - */ - public function __invoke(FormInterface $form) + public function __invoke(FormInterface $form): array { $groups = []; @@ -56,7 +52,7 @@ Then in your form, inject the resolver and set it as the ``validation_groups``:: } // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'validation_groups' => $this->groupResolver, diff --git a/form/validation_groups.rst b/form/validation_groups.rst index 2dfe2889de9..a215ed02aba 100644 --- a/form/validation_groups.rst +++ b/form/validation_groups.rst @@ -20,7 +20,7 @@ following to the ``configureOptions()`` method:: use Symfony\Component\OptionsResolver\OptionsResolver; - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ // ... diff --git a/form/without_class.rst b/form/without_class.rst index 50efc1dbcc7..85838d77ce9 100644 --- a/form/without_class.rst +++ b/form/without_class.rst @@ -12,28 +12,35 @@ But sometimes, you may want to use a form without a class, and get back an array of the submitted data. The ``getData()`` method allows you to do exactly that:: - // make sure you've imported the Request namespace above the class + // src/Controller/ContactController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; // ... - public function contact(Request $request) + class ContactController extends AbstractController { - $defaultData = ['message' => 'Type your message here']; - $form = $this->createFormBuilder($defaultData) - ->add('name', TextType::class) - ->add('email', EmailType::class) - ->add('message', TextareaType::class) - ->add('send', SubmitType::class) - ->getForm(); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - // data is an array with "name", "email", and "message" keys - $data = $form->getData(); + public function contact(Request $request): Response + { + $defaultData = ['message' => 'Type your message here']; + $form = $this->createFormBuilder($defaultData) + ->add('name', TextType::class) + ->add('email', EmailType::class) + ->add('message', TextareaType::class) + ->add('send', SubmitType::class) + ->getForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // data is an array with "name", "email", and "message" keys + $data = $form->getData(); + } + + // ... render the form } - - // ... render the form } By default, a form actually assumes that you want to work with arrays of @@ -85,7 +92,7 @@ but here's a short example:: use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('firstName', TextType::class, [ diff --git a/forms.rst b/forms.rst index 6923660a016..12013e8fbfc 100644 --- a/forms.rst +++ b/forms.rst @@ -49,22 +49,22 @@ following ``Task`` class:: protected $task; protected $dueDate; - public function getTask() + public function getTask(): string { return $this->task; } - public function setTask($task) + public function setTask(string $task): void { $this->task = $task; } - public function getDueDate() + public function getDueDate(): ?\DateTime { return $this->dueDate; } - public function setDueDate(\DateTime $dueDate = null) + public function setDueDate(?\DateTime $dueDate): void { $this->dueDate = $dueDate; } @@ -122,10 +122,11 @@ use the ``createFormBuilder()`` helper:: use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; class TaskController extends AbstractController { - public function new(Request $request) + public function new(Request $request): Response { // creates a task object and initializes some data for this example $task = new Task(); @@ -178,7 +179,7 @@ implements the interface and provides some utilities:: class TaskType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('task', TextType::class) @@ -206,7 +207,7 @@ use the ``createForm()`` helper (otherwise, use the ``create()`` method of the class TaskController extends AbstractController { - public function new() + public function new(): Response { // creates a task object and initializes some data for this example $task = new Task(); @@ -241,7 +242,7 @@ the ``data_class`` option by adding the following to your form type class:: { // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Task::class, @@ -265,10 +266,11 @@ to build another object with the visual representation of the form:: use App\Form\Type\TaskType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; class TaskController extends AbstractController { - public function new(Request $request) + public function new(Request $request): Response { $task = new Task(); // ... @@ -374,34 +376,39 @@ Processing a form means to translate user-submitted data back to the properties of an object. To make this happen, the submitted data from the user must be written into the form object:: + // src/Controller/TaskController.php + // ... use Symfony\Component\HttpFoundation\Request; - public function new(Request $request) + class TaskController extends AbstractController { - // just setup a fresh $task object (remove the example data) - $task = new Task(); + public function new(Request $request): Response + { + // just setup a fresh $task object (remove the example data) + $task = new Task(); - $form = $this->createForm(TaskType::class, $task); + $form = $this->createForm(TaskType::class, $task); - $form->handleRequest($request); - if ($form->isSubmitted() && $form->isValid()) { - // $form->getData() holds the submitted values - // but, the original `$task` variable has also been updated - $task = $form->getData(); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + // $form->getData() holds the submitted values + // but, the original `$task` variable has also been updated + $task = $form->getData(); - // ... perform some action, such as saving the task to the database - // for example, if Task is a Doctrine entity, save it! - // $entityManager = $this->getDoctrine()->getManager(); - // $entityManager->persist($task); - // $entityManager->flush(); + // ... perform some action, such as saving the task to the database + // for example, if Task is a Doctrine entity, save it! + // $entityManager = $this->getDoctrine()->getManager(); + // $entityManager->persist($task); + // $entityManager->flush(); - return $this->redirectToRoute('task_success'); - } + return $this->redirectToRoute('task_success'); + } - return $this->render('task/new.html.twig', [ - 'form' => $form->createView(), - ]); + return $this->render('task/new.html.twig', [ + 'form' => $form->createView(), + ]); + } } This controller follows a common pattern for handling forms and has three @@ -534,7 +541,7 @@ object. { // ... - public static function loadValidatorMetadata(ClassMetadata $metadata) + public static function loadValidatorMetadata(ClassMetadata $metadata): void { $metadata->addPropertyConstraint('task', new NotBlank()); @@ -571,7 +578,7 @@ argument of ``createForm()``:: class TaskController extends AbstractController { - public function new() + public function new(): Response { $task = new Task(); // use some PHP logic to decide if this form field is required or not @@ -599,7 +606,7 @@ options they accept using the ``configureOptions()`` method:: { // ... - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ // ..., @@ -623,7 +630,7 @@ Now you can use this new form option inside the ``buildForm()`` method:: class TaskType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder // ... @@ -699,6 +706,7 @@ use the ``setAction()`` and ``setMethod()`` methods to change this:: // src/Controller/TaskController.php namespace App\Controller; + // ... use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -706,7 +714,7 @@ use the ``setAction()`` and ``setMethod()`` methods to change this:: class TaskController extends AbstractController { - public function new() + public function new(): Response { // ... @@ -727,10 +735,11 @@ When building the form in a class, pass the action and method as form options:: use App\Form\TaskType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + // ... class TaskController extends AbstractController { - public function new() + public function new(): Response { // ... @@ -775,10 +784,11 @@ method:: use App\Form\TaskType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + // ... class TaskController extends AbstractController { - public function new() + public function new(): Response { $task = ...; $form = $this->get('form.factory')->createNamed('my_name', TaskType::class, $task); @@ -839,7 +849,7 @@ pass ``null`` to it, to enable Symfony's "guessing mechanism":: class TaskType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder // if you don't define field options, you can omit the second argument @@ -897,16 +907,20 @@ If you need extra fields in the form that won't be stored in the object (for example to add an *"I agree with these terms"* checkbox), set the ``mapped`` option to ``false`` in those fields:: + // ... use Symfony\Component\Form\FormBuilderInterface; - public function buildForm(FormBuilderInterface $builder, array $options) + class TaskType extends AbstractType { - $builder - ->add('task') - ->add('dueDate') - ->add('agreeTerms', CheckboxType::class, ['mapped' => false]) - ->add('save', SubmitType::class) - ; + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('task') + ->add('dueDate') + ->add('agreeTerms', CheckboxType::class, ['mapped' => false]) + ->add('save', SubmitType::class) + ; + } } These "unmapped fields" can be set and accessed in a controller with:: From 8f8621d99c344a3937f505ca12fade3a6e95f04a Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 8 Nov 2020 15:05:59 +0100 Subject: [PATCH 3/3] Added PHP types to the DI related articles --- service_container.rst | 4 ++-- service_container/autowiring.rst | 19 +++++++++++-------- service_container/calls.rst | 4 ++-- service_container/compiler_passes.rst | 4 ++-- service_container/configurators.rst | 8 ++++---- service_container/factories.rst | 4 ++-- service_container/injection_types.rst | 6 +++--- service_container/optional_dependencies.rst | 2 +- service_container/parent_services.rst | 2 +- .../service_subscribers_locators.rst | 12 ++++++------ service_container/synthetic_services.rst | 2 +- service_container/tags.rst | 18 ++++++++++-------- 12 files changed, 45 insertions(+), 40 deletions(-) diff --git a/service_container.rst b/service_container.rst index ff80ac27a14..09e26cd4d0b 100644 --- a/service_container.rst +++ b/service_container.rst @@ -340,7 +340,7 @@ you can type-hint the new ``SiteUpdateManager`` class and use it:: // src/Controller/SiteController.php namespace App\Controller; - + // ... use App\Service\SiteUpdateManager; @@ -378,7 +378,7 @@ example, suppose you want to make the admin email configurable: + private $adminEmail; - public function __construct(MessageGenerator $messageGenerator, MailerInterface $mailer) - + public function __construct(MessageGenerator $messageGenerator, MailerInterface $mailer, $adminEmail) + + public function __construct(MessageGenerator $messageGenerator, MailerInterface $mailer, string $adminEmail) { // ... + $this->adminEmail = $adminEmail; diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index 167fb4562f4..04058c6b9ac 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -29,7 +29,7 @@ Start by creating a ROT13 transformer class:: class Rot13Transformer { - public function transform($value) + public function transform(string $value): string { return str_rot13($value); } @@ -41,6 +41,7 @@ And now a Twitter client using this transformer:: namespace App\Service; use App\Util\Rot13Transformer; + // ... class TwitterClient { @@ -51,7 +52,7 @@ And now a Twitter client using this transformer:: $this->transformer = $transformer; } - public function tweet($user, $key, $status) + public function tweet(User $user, string $key, string $status): void { $transformedStatus = $this->transformer->transform($status); @@ -129,6 +130,8 @@ Now, you can use the ``TwitterClient`` service immediately in a controller:: use App\Service\TwitterClient; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class DefaultController extends AbstractController @@ -136,7 +139,7 @@ Now, you can use the ``TwitterClient`` service immediately in a controller:: /** * @Route("/tweet", methods={"POST"}) */ - public function tweet(TwitterClient $twitterClient) + public function tweet(TwitterClient $twitterClient, Request $request): Response { // fetch $user, $key, $status from the POST'ed data @@ -288,7 +291,7 @@ To follow this best practice, suppose you decide to create a ``TransformerInterf interface TransformerInterface { - public function transform($value); + public function transform(string $value): string; } Then, you update ``Rot13Transformer`` to implement it:: @@ -388,7 +391,7 @@ Suppose you create a second class - ``UppercaseTransformer`` that implements class UppercaseTransformer implements TransformerInterface { - public function transform($value) + public function transform(string $value): string { return strtoupper($value); } @@ -426,7 +429,7 @@ the injection:: $this->transformer = $shoutyTransformer; } - public function toot($user, $key, $status) + public function toot(User $user, string $key, string $status): void { $transformedStatus = $this->transformer->transform($status); @@ -565,12 +568,12 @@ to inject the ``logger`` service, and decide to use setter-injection:: /** * @required */ - public function setLogger(LoggerInterface $logger) + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; } - public function transform($value) + public function transform(string $value): string { $this->logger->info('Transforming '.$value); // ... diff --git a/service_container/calls.rst b/service_container/calls.rst index 00069a2ccb2..78418aadf13 100644 --- a/service_container/calls.rst +++ b/service_container/calls.rst @@ -22,7 +22,7 @@ example:: { private $logger; - public function setLogger(LoggerInterface $logger) + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; } @@ -97,7 +97,7 @@ instead of mutating the object they were called on:: /** * @return static */ - public function withLogger(LoggerInterface $logger) + public function withLogger(LoggerInterface $logger): self { $new = clone $this; $new->logger = $logger; diff --git a/service_container/compiler_passes.rst b/service_container/compiler_passes.rst index 4d959e93dc6..79f666a4237 100644 --- a/service_container/compiler_passes.rst +++ b/service_container/compiler_passes.rst @@ -52,7 +52,7 @@ and process the services inside the ``process()`` method:: // ... - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { // in this method you can manipulate the service container: // for example, changing some container service: @@ -81,7 +81,7 @@ method in the extension):: class MyBundle extends Bundle { - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { parent::build($container); diff --git a/service_container/configurators.rst b/service_container/configurators.rst index 92c1afc5794..5331d0ba7ac 100644 --- a/service_container/configurators.rst +++ b/service_container/configurators.rst @@ -28,7 +28,7 @@ You start defining a ``NewsletterManager`` class like this:: { private $enabledFormatters; - public function setEnabledFormatters(array $enabledFormatters) + public function setEnabledFormatters(array $enabledFormatters): void { $this->enabledFormatters = $enabledFormatters; } @@ -45,7 +45,7 @@ and also a ``GreetingCardManager`` class:: { private $enabledFormatters; - public function setEnabledFormatters(array $enabledFormatters) + public function setEnabledFormatters(array $enabledFormatters): void { $this->enabledFormatters = $enabledFormatters; } @@ -65,7 +65,7 @@ in the application:: { // ... - public function getEnabledFormatters() + public function getEnabledFormatters(): array { // code to configure which formatters to use $enabledFormatters = [...]; @@ -92,7 +92,7 @@ to create a configurator class to configure these instances:: $this->formatterManager = $formatterManager; } - public function configure(EmailFormatterAwareInterface $emailManager) + public function configure(EmailFormatterAwareInterface $emailManager): void { $emailManager->setEnabledFormatters( $this->formatterManager->getEnabledFormatters() diff --git a/service_container/factories.rst b/service_container/factories.rst index f6ccd5a1198..9f01e7dc7a6 100644 --- a/service_container/factories.rst +++ b/service_container/factories.rst @@ -26,7 +26,7 @@ object by calling the static ``createNewsletterManager()`` method:: class NewsletterManagerStaticFactory { - public static function createNewsletterManager() + public static function createNewsletterManager(): NewsletterManager { $newsletterManager = new NewsletterManager(); @@ -180,7 +180,7 @@ factory service can be used as a callback:: // ... class InvokableNewsletterManagerFactory { - public function __invoke() + public function __invoke(): NewsletterManager { $newsletterManager = new NewsletterManager(); diff --git a/service_container/injection_types.rst b/service_container/injection_types.rst index 097540bd8f6..6b7b74d1d24 100644 --- a/service_container/injection_types.rst +++ b/service_container/injection_types.rst @@ -130,7 +130,7 @@ by cloning the original service, this approach allows you to make a service immu * @required * @return static */ - public function withMailer(MailerInterface $mailer) + public function withMailer(MailerInterface $mailer): self { $new = clone $this; $new->mailer = $mailer; @@ -224,7 +224,7 @@ that accepts the dependency:: // src/Mail/NewsletterManager.php namespace App\Mail; - + // ... class NewsletterManager { @@ -233,7 +233,7 @@ that accepts the dependency:: /** * @required */ - public function setMailer(MailerInterface $mailer) + public function setMailer(MailerInterface $mailer): void { $this->mailer = $mailer; } diff --git a/service_container/optional_dependencies.rst b/service_container/optional_dependencies.rst index ca702176341..b981877a942 100644 --- a/service_container/optional_dependencies.rst +++ b/service_container/optional_dependencies.rst @@ -113,7 +113,7 @@ In YAML, the special ``@?`` syntax tells the service container that the dependency is optional. The ``NewsletterManager`` must also be rewritten by adding a ``setLogger()`` method:: - public function setLogger(LoggerInterface $logger) + public function setLogger(LoggerInterface $logger): void { // ... } diff --git a/service_container/parent_services.rst b/service_container/parent_services.rst index 561721a2a8a..de0d5658b15 100644 --- a/service_container/parent_services.rst +++ b/service_container/parent_services.rst @@ -26,7 +26,7 @@ you may have multiple repository classes which need the $this->objectManager = $objectManager; } - public function setLogger(LoggerInterface $logger) + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; } diff --git a/service_container/service_subscribers_locators.rst b/service_container/service_subscribers_locators.rst index a9579bcfcc3..2fc0ec54fb1 100644 --- a/service_container/service_subscribers_locators.rst +++ b/service_container/service_subscribers_locators.rst @@ -87,7 +87,7 @@ a PSR-11 ``ContainerInterface``:: $this->locator = $locator; } - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return [ 'App\FooCommand' => FooHandler::class, @@ -130,7 +130,7 @@ service locator:: use Psr\Log\LoggerInterface; - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return [ // ... @@ -142,7 +142,7 @@ Service types can also be keyed by a service name for internal use:: use Psr\Log\LoggerInterface; - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return [ // ... @@ -159,7 +159,7 @@ typically happens when extending ``AbstractController``:: class MyController extends AbstractController { - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return array_merge(parent::getSubscribedServices(), [ // ... @@ -176,7 +176,7 @@ errors if there's no matching service found in the service container:: use Psr\Log\LoggerInterface; - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return [ // ... @@ -395,7 +395,7 @@ will share identical locators among all the services referencing them:: use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { // ... diff --git a/service_container/synthetic_services.rst b/service_container/synthetic_services.rst index 5a3ea59d276..59869d5d7f3 100644 --- a/service_container/synthetic_services.rst +++ b/service_container/synthetic_services.rst @@ -18,7 +18,7 @@ from within the ``Kernel`` class:: { // ... - protected function initializeContainer() + protected function initializeContainer(): void { // ... $this->container->set('kernel', $this); diff --git a/service_container/tags.rst b/service_container/tags.rst index bbe7df1af6b..8bddd65c795 100644 --- a/service_container/tags.rst +++ b/service_container/tags.rst @@ -126,7 +126,7 @@ In a Symfony application, call this method in your kernel class:: { // ... - protected function build(ContainerBuilder $container) + protected function build(ContainerBuilder $container): void { $container->registerForAutoconfiguration(CustomInterface::class) ->addTag('app.custom_tag') @@ -142,7 +142,7 @@ In a Symfony bundle, call this method in the ``load()`` method of the { // ... - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $container->registerForAutoconfiguration(CustomInterface::class) ->addTag('app.custom_tag') @@ -178,7 +178,7 @@ To begin with, define the ``TransportChain`` class:: $this->transports = []; } - public function addTransport(\Swift_Transport $transport) + public function addTransport(\Swift_Transport $transport): void { $this->transports[] = $transport; } @@ -304,7 +304,7 @@ container for any services with the ``app.mail_transport`` tag:: class MailTransportPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { // always first check if the primary service is defined if (!$container->has(TransportChain::class)) { @@ -341,7 +341,7 @@ or from your kernel:: { // ... - protected function build(ContainerBuilder $container) + protected function build(ContainerBuilder $container): void { $container->addCompilerPass(new MailTransportPass()); } @@ -372,16 +372,18 @@ To begin with, change the ``TransportChain`` class:: $this->transports = []; } - public function addTransport(\Swift_Transport $transport, $alias) + public function addTransport(\Swift_Transport $transport, $alias): void { $this->transports[$alias] = $transport; } - public function getTransport($alias) + public function getTransport($alias): ?\Swift_Transport { if (array_key_exists($alias, $this->transports)) { return $this->transports[$alias]; } + + return null; } } @@ -476,7 +478,7 @@ use this, update the compiler:: class TransportCompilerPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { // ...