From 3c71b6d21e4586dc10a729f09ea1e63fbee391aa Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 20 Nov 2014 23:12:35 +0100 Subject: [PATCH 1/8] Use AppBundle instead of AcmeDemoBundle --- book/http_fundamentals.rst | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/book/http_fundamentals.rst b/book/http_fundamentals.rst index cd8d04d0de0..7ff7b54dac3 100644 --- a/book/http_fundamentals.rst +++ b/book/http_fundamentals.rst @@ -427,7 +427,7 @@ by adding an entry for ``/contact`` to your routing configuration file: # app/config/routing.yml contact: path: /contact - defaults: { _controller: AcmeDemoBundle:Main:contact } + defaults: { _controller: AppBundle:Main:contact } .. code-block:: xml @@ -439,7 +439,7 @@ by adding an entry for ``/contact`` to your routing configuration file: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeDemoBundle:Main:contact + AppBundle:Main:contact @@ -451,24 +451,18 @@ by adding an entry for ``/contact`` to your routing configuration file: $collection = new RouteCollection(); $collection->add('contact', new Route('/contact', array( - '_controller' => 'AcmeDemoBundle:Main:contact', + '_controller' => 'AppBundle:Main:contact', ))); return $collection; -.. note:: - - This example uses :doc:`YAML ` to define the routing - configuration. Routing configuration can also be written in other formats - such as XML or PHP. - When someone visits the ``/contact`` page, this route is matched, and the specified controller is executed. As you'll learn in the :doc:`routing chapter `, the ``AcmeDemoBundle:Main:contact`` string is a short syntax that points to a specific PHP method ``contactAction`` inside a class called ``MainController``:: - // src/Acme/DemoBundle/Controller/MainController.php - namespace Acme\DemoBundle\Controller; + // src/AppBundle/Controller/MainController.php + namespace AppBundle\Controller; use Symfony\Component\HttpFoundation\Response; From c8ce5074e05f5954458f6258871debf2157abc71 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 20 Nov 2014 23:12:41 +0100 Subject: [PATCH 2/8] Other minor fixes --- book/http_fundamentals.rst | 40 +++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/book/http_fundamentals.rst b/book/http_fundamentals.rst index 7ff7b54dac3..db2f9d86907 100644 --- a/book/http_fundamentals.rst +++ b/book/http_fundamentals.rst @@ -14,7 +14,7 @@ applications, while staying out of your way. Symfony is built on the best ideas from many technologies: the tools and concepts you're about to learn represent the efforts of thousands of people, over many years. In other words, you're not just learning "Symfony", you're learning the fundamentals of the -web, development best practices, and how to use many amazing new PHP libraries, +web, development best practices and how to use many amazing new PHP libraries, inside or independently of Symfony. So, get ready. True to the Symfony philosophy, this chapter begins by explaining the fundamental @@ -33,9 +33,9 @@ takes place: :align: center And while the actual language used is a bit more formal, it's still dead-simple. -HTTP is the term used to describe this simple text-based language. And no -matter how you develop on the web, the goal of your server is *always* to -understand simple text requests, and return simple text responses. +HTTP is the term used to describe this simple text-based language. No matter +how you develop on the web, the goal of your server is *always* to understand +simple text requests, and return simple text responses. Symfony is built from the ground up around that reality. Whether you realize it or not, HTTP is something you use everyday. With Symfony, you'll learn @@ -48,7 +48,7 @@ Step1: The Client Sends a Request ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Every conversation on the web starts with a *request*. The request is a text -message created by a client (e.g. a browser, an iPhone app, etc) in a +message created by a client (e.g. a browser, a smartphone app, etc) in a special format known as HTTP. The client sends that request to a server, and then waits for the response. @@ -98,7 +98,7 @@ delete a specific blog entry, for example: There are actually nine HTTP methods defined by the HTTP specification, but many of them are not widely used or supported. In reality, many modern - browsers don't support the ``PUT`` and ``DELETE`` methods. + browsers don't even support the ``PUT`` and ``DELETE`` methods. In addition to the first line, an HTTP request invariably contains other lines of information called request headers. The headers can supply a wide @@ -161,7 +161,7 @@ communication on the web. And as important and powerful as this process is, it's inescapably simple. The most important fact is this: regardless of the language you use, the -type of application you build (web, mobile, JSON API), or the development +type of application you build (web, mobile, JSON API) or the development philosophy you follow, the end goal of an application is **always** to understand each request and create and return the appropriate response. @@ -277,6 +277,7 @@ an HTTP response message. This allows your application to use an object-oriented interface to construct the response that needs to be returned to the client:: use Symfony\Component\HttpFoundation\Response; + $response = new Response(); $response->setContent('

Hello world!

'); @@ -366,12 +367,13 @@ on that value. This can get ugly quickly:: // index.php use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; + $request = Request::createFromGlobals(); $path = $request->getPathInfo(); // the URI path being requested if (in_array($path, array('', '/'))) { $response = new Response('Welcome to the homepage.'); - } elseif ($path == '/contact') { + } elseif ('/contact' === $path) { $response = new Response('Contact us'); } else { $response = new Response('Page not found.', 404); @@ -485,8 +487,8 @@ email messages. .. _symfony2-build-your-app-not-your-tools: -Symfony: Build your App, not your Tools. ----------------------------------------- +Symfony: Build your App, not your Tools +--------------------------------------- You now know that the goal of any app is to interpret each incoming request and create an appropriate response. As an application grows, it becomes more @@ -522,24 +524,21 @@ regardless of how your project is developed. To name a few: about how that request should be handled (e.g. execute the ``contactAction()`` method); -* `Form`_ - A full-featured and flexible framework for creating forms and - handling form submissions; +* `Form ` - A full-featured and flexible + framework for creating forms and handling form submissions; * `Validator`_ - A system for creating rules about data and then validating whether or not user-submitted data follows those rules; -* :doc:`ClassLoader ` - An autoloading library that allows - PHP classes to be used without needing to manually ``require`` the files - containing those classes; - * :doc:`Templating ` - A toolkit for rendering templates, handling template inheritance (i.e. a template is decorated with a layout) and performing other common template tasks; -* `Security`_ - A powerful library for handling all types of security inside - an application; +* `Security ` - A powerful library for + handling all types of security inside an application; -* `Translation`_ - A framework for translating strings in your application. +* `Translation ` - A framework for + translating strings in your application. Each and every one of these components is decoupled and can be used in *any* PHP project, regardless of whether or not you use the Symfony framework. @@ -576,8 +575,5 @@ sensible defaults. For more advanced users, the sky is the limit. .. _`List of HTTP status codes`: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes .. _`List of HTTP header fields`: http://en.wikipedia.org/wiki/List_of_HTTP_header_fields .. _`List of common media types`: http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types -.. _`Form`: https://github.com/symfony/Form .. _`Validator`: https://github.com/symfony/Validator -.. _`Security`: https://github.com/symfony/Security -.. _`Translation`: https://github.com/symfony/Translation .. _`Swift Mailer`: http://swiftmailer.org/ From de4fcf4612dcfec9efc22fa187521c4ba5c2b036 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 20 Nov 2014 23:28:42 +0100 Subject: [PATCH 3/8] Use AppBundle instead of AcmeStoreBundle --- book/doctrine.rst | 159 ++++++++++++++++++++++------------------------ 1 file changed, 75 insertions(+), 84 deletions(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index 871695ff21d..5ec35ce9338 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -32,15 +32,6 @@ The easiest way to understand how Doctrine works is to see it in action. In this section, you'll configure your database, create a ``Product`` object, persist it to the database and fetch it back out. -.. sidebar:: Code along with the Example - - If you want to follow along with the example in this chapter, create - an ``AcmeStoreBundle`` via: - - .. code-block:: bash - - $ php app/console generate:bundle --namespace=Acme/StoreBundle - Configuring the Database ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -202,17 +193,15 @@ Creating an Entity Class Suppose you're building an application where products need to be displayed. Without even thinking about Doctrine or databases, you already know that you need a ``Product`` object to represent those products. Create this class -inside the ``Entity`` directory of your ``AcmeStoreBundle``:: +inside the ``Entity`` directory of your ``AppBundle``:: - // src/Acme/StoreBundle/Entity/Product.php - namespace Acme\StoreBundle\Entity; + // src/AppBundle/Entity/Product.php + namespace AppBundle\Entity; class Product { protected $name; - protected $price; - protected $description; } @@ -258,8 +247,8 @@ in a number of different formats including YAML, XML or directly inside the .. code-block:: php-annotations - // src/Acme/StoreBundle/Entity/Product.php - namespace Acme\StoreBundle\Entity; + // src/AppBundle/Entity/Product.php + namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; @@ -294,8 +283,8 @@ in a number of different formats including YAML, XML or directly inside the .. code-block:: yaml - # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml - Acme\StoreBundle\Entity\Product: + # src/AppBundle/Resources/config/doctrine/Product.orm.yml + AppBundle\Entity\Product: type: entity table: product id: @@ -314,14 +303,14 @@ in a number of different formats including YAML, XML or directly inside the .. code-block:: xml - + - + @@ -394,7 +383,7 @@ a regular PHP class, you need to create getter and setter methods (e.g. ``getNam .. code-block:: bash - $ php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product + $ php app/console doctrine:generate:entities AppBundle/Entity/Product This command makes sure that all of the getters and setters are generated for the ``Product`` class. This is a safe command - you can run it over and @@ -435,7 +424,9 @@ mapping information) of a bundle or an entire namespace: .. code-block:: bash - $ php app/console doctrine:generate:entities AcmeStoreBundle + # generates all entities in the AppBundle + $ php app/console doctrine:generate:entities AppBundle + # generates all entities of bundles in the Acme namespace $ php app/console doctrine:generate:entities Acme .. note:: @@ -485,17 +476,16 @@ Persisting Objects to the Database Now that you have a mapped ``Product`` entity and corresponding ``product`` table, you're ready to persist data to the database. From inside a controller, this is pretty easy. Add the following method to the ``DefaultController`` -of the bundle: +of the bundle:: -.. code-block:: php - :linenos: - // src/Acme/StoreBundle/Controller/DefaultController.php + // src/AppBundle/Controller/DefaultController.php // ... - use Acme\StoreBundle\Entity\Product; + use AppBundle\Entity\Product; use Symfony\Component\HttpFoundation\Response; + // ... public function createAction() { $product = new Product(); @@ -504,6 +494,7 @@ of the bundle: $product->setDescription('Lorem ipsum dolor'); $em = $this->getDoctrine()->getManager(); + $em->persist($product); $em->flush(); @@ -526,17 +517,17 @@ of the bundle: Take a look at the previous example in more detail: -* **lines 9-12** In this section, you instantiate and work with the ``$product`` +* **lines 10-13** In this section, you instantiate and work with the ``$product`` object like any other, normal PHP object. -* **line 14** This line fetches Doctrine's *entity manager* object, which is +* **line 15** This line fetches Doctrine's *entity manager* object, which is responsible for handling the process of persisting and fetching objects to and from the database. -* **line 15** The ``persist()`` method tells Doctrine to "manage" the ``$product`` +* **line 16** The ``persist()`` method tells Doctrine to "manage" the ``$product`` object. This does not actually cause a query to be made to the database (yet). -* **line 16** When the ``flush()`` method is called, Doctrine looks through +* **line 17** When the ``flush()`` method is called, Doctrine looks through all of the objects that it's managing to see if they need to be persisted to the database. In this example, the ``$product`` object has not been persisted yet, so the entity manager executes an ``INSERT`` query and a @@ -544,12 +535,12 @@ Take a look at the previous example in more detail: .. note:: - In fact, since Doctrine is aware of all your managed entities, when you call - the ``flush()`` method, it calculates an overall changeset and executes - the queries in the correct order. It utilizes cached prepared statement to - slightly improve the performance. For example, if you persist a total of 100 - ``Product`` objects and then subsequently call ``flush()``, Doctrine will - execute 100 ``INSERT`` queries using a single prepared statement object. + In fact, since Doctrine is aware of all your managed entities, when you call + the ``flush()`` method, it calculates an overall changeset and executes + the queries in the correct order. It utilizes cached prepared statement to + slightly improve the performance. For example, if you persist a total of 100 + ``Product`` objects and then subsequently call ``flush()``, Doctrine will + execute 100 ``INSERT`` queries using a single prepared statement object. When creating or updating objects, the workflow is always the same. In the next section, you'll see how Doctrine is smart enough to automatically issue @@ -571,7 +562,7 @@ on its ``id`` value:: public function showAction($id) { $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') + ->getRepository('AppBundle:Product') ->find($id); if (!$product) { @@ -595,12 +586,12 @@ job is to help you fetch entities of a certain class. You can access the repository object for an entity class via:: $repository = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product'); + ->getRepository('AppBundle:Product'); .. note:: - The ``AcmeStoreBundle:Product`` string is a shortcut you can use anywhere - in Doctrine instead of the full class name of the entity (i.e. ``Acme\StoreBundle\Entity\Product``). + The ``AppBundle:Product`` string is a shortcut you can use anywhere + in Doctrine instead of the full class name of the entity (i.e. ``AppBundle\Entity\Product``). As long as your entity lives under the ``Entity`` namespace of your bundle, this will work. @@ -660,7 +651,7 @@ you have a route that maps a product id to an update action in a controller:: public function updateAction($id) { $em = $this->getDoctrine()->getManager(); - $product = $em->getRepository('AcmeStoreBundle:Product')->find($id); + $product = $em->getRepository('AppBundle:Product')->find($id); if (!$product) { throw $this->createNotFoundException( @@ -726,7 +717,7 @@ cost more than ``19.99``, ordered from cheapest to most expensive. You can use Doctrine's ``QueryBuilder`` for this:: $repository = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product'); + ->getRepository('AppBundle:Product'); $query = $repository->createQueryBuilder('p') ->where('p.price > :price') @@ -764,7 +755,7 @@ directly using DQL:: $em = $this->getDoctrine()->getManager(); $query = $em->createQuery( 'SELECT p - FROM AcmeStoreBundle:Product p + FROM AppBundle:Product p WHERE p.price > :price ORDER BY p.price ASC' )->setParameter('price', '19.99'); @@ -773,7 +764,7 @@ directly using DQL:: If you're comfortable with SQL, then DQL should feel very natural. The biggest difference is that you need to think in terms of "objects" instead of rows -in a database. For this reason, you select *from* the ``AcmeStoreBundle:Product`` +in a database. For this reason, you select *from* the ``AppBundle:Product`` *object* and then alias it as ``p`` (as you see, this is equal to what you already did in the previous section). @@ -796,13 +787,13 @@ To do this, add the name of the repository class to your mapping definition: .. code-block:: php-annotations - // src/Acme/StoreBundle/Entity/Product.php - namespace Acme\StoreBundle\Entity; + // src/AppBundle/Entity/Product.php + namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\ProductRepository") + * @ORM\Entity(repositoryClass="AppBundle\Entity\ProductRepository") */ class Product { @@ -811,15 +802,15 @@ To do this, add the name of the repository class to your mapping definition: .. code-block:: yaml - # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml - Acme\StoreBundle\Entity\Product: + # src/AppBundle/Resources/config/doctrine/Product.orm.yml + AppBundle\Entity\Product: type: entity - repositoryClass: Acme\StoreBundle\Entity\ProductRepository + repositoryClass: AppBundle\Entity\ProductRepository # ... .. code-block:: xml - + + name="AppBundle\Entity\Product" + repository-class="AppBundle\Entity\ProductRepository"> @@ -839,7 +830,7 @@ used earlier to generate the missing getter and setter methods: .. code-block:: bash - $ php app/console doctrine:generate:entities Acme + $ php app/console doctrine:generate:entities AppBundle Next, add a new method - ``findAllOrderedByName()`` - to the newly generated repository class. This method will query for all of the ``Product`` entities, @@ -847,8 +838,8 @@ ordered alphabetically. .. code-block:: php - // src/Acme/StoreBundle/Entity/ProductRepository.php - namespace Acme\StoreBundle\Entity; + // src/AppBundle/Entity/ProductRepository.php + namespace AppBundle\Entity; use Doctrine\ORM\EntityRepository; @@ -858,7 +849,7 @@ ordered alphabetically. { return $this->getEntityManager() ->createQuery( - 'SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC' + 'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC' ) ->getResult(); } @@ -872,7 +863,7 @@ ordered alphabetically. You can use this new method just like the default finder methods of the repository:: $em = $this->getDoctrine()->getManager(); - $products = $em->getRepository('AcmeStoreBundle:Product') + $products = $em->getRepository('AppBundle:Product') ->findAllOrderedByName(); .. note:: @@ -893,7 +884,7 @@ you can let Doctrine create the class for you. .. code-block:: bash - $ php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Category" --fields="name:string(255)" + $ php app/console doctrine:generate:entity --entity="AppBundle:Category" --fields="name:string(255)" This task generates the ``Category`` entity for you, with an ``id`` field, a ``name`` field and the associated getter and setter functions. @@ -908,7 +899,7 @@ To relate the ``Category`` and ``Product`` entities, start by creating a .. code-block:: php-annotations - // src/Acme/StoreBundle/Entity/Category.php + // src/AppBundle/Entity/Category.php // ... use Doctrine\Common\Collections\ArrayCollection; @@ -930,8 +921,8 @@ To relate the ``Category`` and ``Product`` entities, start by creating a .. code-block:: yaml - # src/Acme/StoreBundle/Resources/config/doctrine/Category.orm.yml - Acme\StoreBundle\Entity\Category: + # src/AppBundle/Resources/config/doctrine/Category.orm.yml + AppBundle\Entity\Category: type: entity # ... oneToMany: @@ -942,14 +933,14 @@ To relate the ``Category`` and ``Product`` entities, start by creating a .. code-block:: xml - + - + + - + getDoctrine() - ->getRepository('AcmeStoreBundle:Product') + ->getRepository('AppBundle:Product') ->find($id); $categoryName = $product->getCategory()->getName(); @@ -1162,7 +1153,7 @@ You can also query in the other direction:: public function showProductAction($id) { $category = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Category') + ->getRepository('AppBundle:Category') ->find($id); $products = $category->getProducts(); @@ -1183,12 +1174,12 @@ to the given ``Category`` object via their ``category_id`` value. example:: $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') + ->getRepository('AppBundle:Product') ->find($id); $category = $product->getCategory(); - // prints "Proxies\AcmeStoreBundleEntityCategoryProxy" + // prints "Proxies\AppBundleEntityCategoryProxy" echo get_class($category); This proxy object extends the true ``Category`` object, and looks and @@ -1220,12 +1211,12 @@ Of course, if you know up front that you'll need to access both objects, you can avoid the second query by issuing a join in the original query. Add the following method to the ``ProductRepository`` class:: - // src/Acme/StoreBundle/Entity/ProductRepository.php + // src/AppBundle/Entity/ProductRepository.php public function findOneByIdJoinedToCategory($id) { $query = $this->getEntityManager() ->createQuery( - 'SELECT p, c FROM AcmeStoreBundle:Product p + 'SELECT p, c FROM AppBundle:Product p JOIN p.category c WHERE p.id = :id' )->setParameter('id', $id); @@ -1243,7 +1234,7 @@ object and its related ``Category`` with just one query:: public function showAction($id) { $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') + ->getRepository('AppBundle:Product') ->findOneByIdJoinedToCategory($id); $category = $product->getCategory(); @@ -1304,7 +1295,7 @@ the current date, only when the entity is first persisted (i.e. inserted): .. code-block:: php-annotations - // src/Acme/StoreBundle/Entity/Product.php + // src/AppBundle/Entity/Product.php /** * @ORM\PrePersist @@ -1316,8 +1307,8 @@ the current date, only when the entity is first persisted (i.e. inserted): .. code-block:: yaml - # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml - Acme\StoreBundle\Entity\Product: + # src/AppBundle/Resources/config/doctrine/Product.orm.yml + AppBundle\Entity\Product: type: entity # ... lifecycleCallbacks: @@ -1325,14 +1316,14 @@ the current date, only when the entity is first persisted (i.e. inserted): .. code-block:: xml - + - + From 4ef1ef313400fa44e65a1f96c0f1a759ac8d61f7 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 20 Nov 2014 23:37:08 +0100 Subject: [PATCH 4/8] Apply best practices to forms --- book/forms.rst | 152 +++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 86 deletions(-) diff --git a/book/forms.rst b/book/forms.rst index 27135c4f817..c7dc5489047 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -26,13 +26,12 @@ display "tasks". Because your users will need to edit and create tasks, you're going to need to build a form. But before you begin, first focus on the generic ``Task`` class that represents and stores the data for a single task:: - // src/Acme/TaskBundle/Entity/Task.php - namespace Acme\TaskBundle\Entity; + // src/AppBundle/Entity/Task.php + namespace AppBundle\Entity; class Task { protected $task; - protected $dueDate; public function getTask() @@ -56,16 +55,6 @@ going to need to build a form. But before you begin, first focus on the generic } } -.. note:: - - If you're coding along with this example, create the ``AcmeTaskBundle`` - first by running the following command (and accepting all of the default - options): - - .. code-block:: bash - - $ php app/console generate:bundle --namespace=Acme/TaskBundle - This class is a "plain-old-PHP-object" because, so far, it has nothing to do with Symfony or any other library. It's quite simply a normal PHP object that directly solves a problem inside *your* application (i.e. the need to @@ -84,11 +73,11 @@ render the actual HTML form. In Symfony, this is done by building a form object and then rendering it in a template. For now, this can all be done from inside a controller:: - // src/Acme/TaskBundle/Controller/DefaultController.php - namespace Acme\TaskBundle\Controller; + // src/AppBundle/Controller/DefaultController.php + namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Acme\TaskBundle\Entity\Task; + use AppBundle\Entity\Task; use Symfony\Component\HttpFoundation\Request; class DefaultController extends Controller @@ -106,7 +95,7 @@ from inside a controller:: ->add('save', 'submit', array('label' => 'Create Task')) ->getForm(); - return $this->render('AcmeTaskBundle:Default:new.html.twig', array( + return $this->render('Default/new.html.twig', array( 'form' => $form->createView(), )); } @@ -154,13 +143,13 @@ helper functions: .. code-block:: html+jinja - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} + {# app/Resources/views/Default/new.html.twig #} {{ form(form) }} .. code-block:: html+php - + form($form) ?> @@ -339,8 +328,8 @@ object. .. code-block:: yaml - # Acme/TaskBundle/Resources/config/validation.yml - Acme\TaskBundle\Entity\Task: + # AppBundle/Resources/config/validation.yml + AppBundle\Entity\Task: properties: task: - NotBlank: ~ @@ -350,7 +339,7 @@ object. .. code-block:: php-annotations - // Acme/TaskBundle/Entity/Task.php + // AppBundle/Entity/Task.php use Symfony\Component\Validator\Constraints as Assert; class Task @@ -369,14 +358,14 @@ object. .. code-block:: xml - + - + @@ -389,7 +378,7 @@ object. .. code-block:: php - // Acme/TaskBundle/Entity/Task.php + // AppBundle/Entity/Task.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Type; @@ -435,14 +424,12 @@ corresponding errors printed out with the form. .. code-block:: html+jinja - {# src/Acme/DemoBundle/Resources/views/Default/new.html.twig #} - + {# app/Resources/views/Default/new.html.twig #} {{ form(form, {'attr': {'novalidate': 'novalidate'}}) }} .. code-block:: html+php - - + form($form, array( 'attr' => array('novalidate' => 'novalidate'), )) ?> @@ -525,7 +512,7 @@ to an array callback:: { $resolver->setDefaults(array( 'validation_groups' => array( - 'Acme\AcmeBundle\Entity\Client', + 'AppBundle\Entity\Client', 'determineValidationGroups', ), )); @@ -748,7 +735,7 @@ of code. Of course, you'll usually need much more flexibility when rendering: .. code-block:: html+jinja - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} + {# app/Resources/views/Default/new.html.twig #} {{ form_start(form) }} {{ form_errors(form) }} @@ -758,7 +745,7 @@ of code. Of course, you'll usually need much more flexibility when rendering: .. code-block:: html+php - + start($form) ?> errors($form) ?> @@ -970,14 +957,14 @@ to the ``form()`` or the ``form_start()`` helper: .. code-block:: html+jinja - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} + {# app/Resources/views/Default/new.html.twig #} {{ form(form, {'action': path('target_route'), 'method': 'GET'}) }} {{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }} .. code-block:: html+php - + form($form, array( 'action' => $view['router']->generate('target_route'), 'method' => 'GET', @@ -1010,8 +997,8 @@ However, a better practice is to build the form in a separate, standalone PHP class, which can then be reused anywhere in your application. Create a new class that will house the logic for building the task form:: - // src/Acme/TaskBundle/Form/Type/TaskType.php - namespace Acme\TaskBundle\Form\Type; + // src/AppBundle/Form/Type/TaskType.php + namespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -1036,10 +1023,10 @@ This new class contains all the directions needed to create the task form (note that the ``getName()`` method should return a unique identifier for this form "type"). It can be used to quickly build a form object in the controller:: - // src/Acme/TaskBundle/Controller/DefaultController.php + // src/AppBundle/Controller/DefaultController.php // add this new use statement at the top of the class - use Acme\TaskBundle\Form\Type\TaskType; + use AppBundle\Form\Type\TaskType; public function newAction() { @@ -1058,7 +1045,7 @@ the choice is ultimately up to you. .. sidebar:: Setting the ``data_class`` Every form needs to know the name of the class that holds the underlying - data (e.g. ``Acme\TaskBundle\Entity\Task``). Usually, this is just guessed + data (e.g. ``AppBundle\Entity\Task``). Usually, this is just guessed based off of the object passed to the second argument to ``createForm`` (i.e. ``$task``). Later, when you begin embedding forms, this will no longer be sufficient. So, while not always necessary, it's generally a @@ -1070,7 +1057,7 @@ the choice is ultimately up to you. public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Task', + 'data_class' => 'AppBundle\Entity\Task', )); } @@ -1121,16 +1108,16 @@ easy to use in your application. .. code-block:: yaml - # src/Acme/TaskBundle/Resources/config/services.yml + # src/AppBundle/Resources/config/services.yml services: acme_demo.form.type.task: - class: Acme\TaskBundle\Form\Type\TaskType + class: AppBundle\Form\Type\TaskType tags: - { name: form.type, alias: task } .. code-block:: xml - + + class="AppBundle\Form\Type\TaskType"> @@ -1148,11 +1135,11 @@ easy to use in your application. .. code-block:: php - // src/Acme/TaskBundle/Resources/config/services.php + // src/AppBundle/Resources/config/services.php $container ->register( 'acme_demo.form.type.task', - 'Acme\TaskBundle\Form\Type\TaskType' + 'AppBundle\Form\Type\TaskType' ) ->addTag('form.type', array( 'alias' => 'task', @@ -1161,7 +1148,7 @@ easy to use in your application. That's it! Now you can use your form type directly in a controller:: - // src/Acme/TaskBundle/Controller/DefaultController.php + // src/AppBundle/Controller/DefaultController.php // ... public function newAction() @@ -1174,7 +1161,7 @@ That's it! Now you can use your form type directly in a controller:: or even use from within the form type of another form:: - // src/Acme/TaskBundle/Form/Type/ListType.php + // src/AppBundle/Form/Type/ListType.php // ... class ListType extends AbstractType @@ -1242,8 +1229,8 @@ Embedding a Single Object Suppose that each ``Task`` belongs to a simple ``Category`` object. Start, of course, by creating the ``Category`` object:: - // src/Acme/TaskBundle/Entity/Category.php - namespace Acme\TaskBundle\Entity; + // src/AppBundle/Entity/Category.php + namespace AppBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; @@ -1264,7 +1251,7 @@ Next, add a new ``category`` property to the ``Task`` class:: // ... /** - * @Assert\Type(type="Acme\TaskBundle\Entity\Category") + * @Assert\Type(type="AppBundle\Entity\Category") * @Assert\Valid() */ protected $category; @@ -1291,8 +1278,8 @@ Next, add a new ``category`` property to the ``Task`` class:: Now that your application has been updated to reflect the new requirements, create a form class so that a ``Category`` object can be modified by the user:: - // src/Acme/TaskBundle/Form/Type/CategoryType.php - namespace Acme\TaskBundle\Form\Type; + // src/AppBundle/Form/Type/CategoryType.php + namespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -1308,7 +1295,7 @@ create a form class so that a ``Category`` object can be modified by the user:: public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Category', + 'data_class' => 'AppBundle\Entity\Category', )); } @@ -1412,7 +1399,7 @@ do this, create a new template file that will store the new markup: .. code-block:: html+jinja - {# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #} + {# app/Resources/views/Form/fields.html.twig #} {% block form_row %} {% spaceless %}
@@ -1425,7 +1412,7 @@ do this, create a new template file that will store the new markup: .. code-block:: html+php - +
label($form, $label) ?> errors($form) ?> @@ -1441,19 +1428,19 @@ renders the form: .. code-block:: html+jinja - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} - {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %} + {# app/Resources/views/Default/new.html.twig #} + {% form_theme form 'Form/fields.html.twig' %} - {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' 'AcmeTaskBundle:Form:fields2.html.twig' %} + {% form_theme form 'Form/fields.html.twig' 'Form/fields2.html.twig' %} - + {# ... render the form #} .. code-block:: html+php - - setTheme($form, array('AcmeTaskBundle:Form')) ?> + + setTheme($form, array('Form')) ?> - setTheme($form, array('AcmeTaskBundle:Form', 'AcmeTaskBundle:Form')) ?> + setTheme($form, array('Form', 'Form2')) ?> @@ -1474,14 +1461,6 @@ To customize any portion of a form, you just need to override the appropriate fragment. Knowing exactly which block or file to override is the subject of the next section. -.. code-block:: html+jinja - - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} - - {% form_theme form with 'AcmeTaskBundle:Form:fields.html.twig' %} - - {% form_theme form with ['AcmeTaskBundle:Form:fields.html.twig', 'AcmeTaskBundle:Form:fields2.html.twig'] %} - For a more extensive discussion, see :doc:`/cookbook/form/form_customization`. .. index:: @@ -1496,9 +1475,10 @@ In Symfony, every part of a form that is rendered - HTML form elements, errors, labels, etc. - is defined in a base theme, which is a collection of blocks in Twig and a collection of template files in PHP. -In Twig, every block needed is defined in a single template file (`form_div_layout.html.twig`_) -that lives inside the `Twig Bridge`_. Inside this file, you can see every block -needed to render a form and every default field type. +In Twig, every block needed is defined in a single template file (e.g. +`form_div_layout.html.twig`_) that lives inside the `Twig Bridge`_. Inside this +file, you can see every block needed to render a form and every default field +type. In PHP, the fragments are individual template files. By default they are located in the `Resources/views/Form` directory of the framework bundle (`view on GitHub`_). @@ -1529,7 +1509,7 @@ are 4 possible *parts* of a form that can be rendered: .. note:: - There are actually 2 other *parts* - ``rows`` and ``rest`` - + There are actually 2 other *parts* - ``rows`` and ``rest`` - but you should rarely if ever need to worry about overriding them. By knowing the field type (e.g. ``textarea``) and which part you want to @@ -1588,7 +1568,7 @@ file: twig: form: resources: - - 'AcmeTaskBundle:Form:fields.html.twig' + - 'Form/fields.html.twig' # ... .. code-block:: xml @@ -1603,7 +1583,7 @@ file: - AcmeTaskBundle:Form:fields.html.twig + Form/fields.html.twig @@ -1615,7 +1595,7 @@ file: $container->loadFromExtension('twig', array( 'form' => array( 'resources' => array( - 'AcmeTaskBundle:Form:fields.html.twig', + 'Form/fields.html.twig', ), ), // ... @@ -1631,7 +1611,7 @@ to define form output. .. code-block:: html+jinja - {% extends '::base.html.twig' %} + {% extends 'base.html.twig' %} {# import "_self" as the form theme #} {% form_theme form _self %} @@ -1661,7 +1641,7 @@ to define form output. PHP ... -To automatically include the customized templates from the ``Acme/TaskBundle/Resources/views/Form`` +To automatically include the customized templates from the ``app/Resources/views/Form`` directory created earlier in *all* templates, modify your application configuration file: @@ -1674,7 +1654,7 @@ file: templating: form: resources: - - 'AcmeTaskBundle:Form' + - 'Form' # ... .. code-block:: xml @@ -1690,7 +1670,7 @@ file: - AcmeTaskBundle:Form + Form @@ -1704,15 +1684,15 @@ file: 'templating' => array( 'form' => array( 'resources' => array( - 'AcmeTaskBundle:Form', + 'Form', ), ), ) // ... )); -Any fragments inside the ``Acme/TaskBundle/Resources/views/Form`` directory -are now used globally to define form output. +Any fragments inside the ``app/Resources/views/Form`` directory are now used +globally to define form output. .. index:: single: Forms; CSRF protection @@ -1752,7 +1732,7 @@ The CSRF token can be customized on a form-by-form basis. For example:: public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Task', + 'data_class' => 'AppBundle\Entity\Task', 'csrf_protection' => true, 'csrf_field_name' => '_token', // a unique key to help generate the secret token From a29f9fbe38c7ca85fe906dbd2e07d82dfeeb3d25 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 20 Nov 2014 23:46:25 +0100 Subject: [PATCH 5/8] Don't use form() helper --- book/forms.rst | 58 ++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/book/forms.rst b/book/forms.rst index c7dc5489047..e05a1abc066 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -144,14 +144,16 @@ helper functions: .. code-block:: html+jinja {# app/Resources/views/Default/new.html.twig #} - - {{ form(form) }} + {{ form_start(form) }} + {{ form_widget(form) }} + {{ form_end(form) }} .. code-block:: html+php - - form($form) ?> + start($form) ?> + widget($form) ?> + end($form) ?> .. image:: /images/book/form-simple.png :align: center @@ -162,12 +164,24 @@ helper functions: the same URL that it was displayed in. You will learn later how to change the request method and the target URL of the form. -That's it! By printing ``form(form)``, each field in the form is rendered, along -with a label and error message (if there is one). The ``form`` function also -surrounds everything in the necessary HTML ``
`` tag. As easy as this is, -it's not very flexible (yet). Usually, you'll want to render each form field -individually so you can control how the form looks. You'll learn how to do -that in the ":ref:`form-rendering-template`" section. +That's it! Just three lines are needed to render the complete form: + +* ``form_start(form)`` - Renders the start tag of the form, including the + correct enctype attributes when using file uploads; + +* ``form_widget(form)`` - Renders all fields, along with a label and error + message (if there is one) input element; + +* ``form_end()`` - Renders the end tag of the form and any fields that have not + yet been rendered, in case you rendered each field yourself. This is useful + for rendering hidden fields and taking advantage of the automatic + :ref:`CSRF Protection `. + +.. seealso:: + + As easy as this is, it's not very flexible (yet). Usually, you'll want to + render each form field individually so you can control how the form looks. + You'll learn how to do that in the ":ref:`form-rendering-template`" section. Before moving on, notice how the rendered ``task`` input field has the value of the ``task`` property from the ``$task`` object (i.e. "Write a blog post"). @@ -753,25 +767,20 @@ of code. Of course, you'll usually need much more flexibility when rendering: row($form['dueDate']) ?> end($form) ?> -Take a look at each part: - -* ``form_start(form)`` - Renders the start tag of the form. +You already know the ``form_start()`` and ``form_end()`` functions, but what do +the other functions do? * ``form_errors(form)`` - Renders any errors global to the whole form (field-specific errors are displayed next to each field); * ``form_row(form.dueDate)`` - Renders the label, any errors, and the HTML form widget for the given field (e.g. ``dueDate``) inside, by default, a - ``div`` element; - -* ``form_end()`` - Renders the end tag of the form and any fields that have not - yet been rendered. This is useful for rendering hidden fields and taking - advantage of the automatic :ref:`CSRF Protection `. + ``div`` element. The majority of the work is done by the ``form_row`` helper, which renders -the label, errors and HTML form widget of each field inside a ``div`` tag -by default. In the :ref:`form-theming` section, you'll learn how the ``form_row`` -output can be customized on many different levels. +the label and HTML form widget of each field inside a ``div`` tag by default. +In the :ref:`form-theming` section, you'll learn how the ``form_row`` output +can be customized on many different levels. .. tip:: @@ -958,18 +967,11 @@ to the ``form()`` or the ``form_start()`` helper: .. code-block:: html+jinja {# app/Resources/views/Default/new.html.twig #} - {{ form(form, {'action': path('target_route'), 'method': 'GET'}) }} - {{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }} .. code-block:: html+php - form($form, array( - 'action' => $view['router']->generate('target_route'), - 'method' => 'GET', - )) ?> - start($form, array( 'action' => $view['router']->generate('target_route'), 'method' => 'GET', From 2a97453495cff1292e3f9a8e26302f570d83804f Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 20 Nov 2014 23:46:38 +0100 Subject: [PATCH 6/8] Minor tweak --- book/forms.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/book/forms.rst b/book/forms.rst index e05a1abc066..91d901f7647 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -12,8 +12,9 @@ learning the most important features of the form library along the way. .. note:: The Symfony Form component is a standalone library that can be used outside - of Symfony projects. For more information, see the `Symfony Form component`_ - on GitHub. + of Symfony projects. For more information, see the + :doc:`Form component documentation ` on + GitHub. .. index:: single: Forms; Create a simple form From fdc460d3c8d38cd395dded9910dae2e5bde539f7 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 20 Nov 2014 23:46:46 +0100 Subject: [PATCH 7/8] Minor standard fix for best practices guide --- best_practices/forms.rst | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/best_practices/forms.rst b/best_practices/forms.rst index 6d70561e914..91910a5582b 100644 --- a/best_practices/forms.rst +++ b/best_practices/forms.rst @@ -15,9 +15,7 @@ Building Forms The Form component allows you to build forms right inside your controller code. Honestly, unless you need to reuse the form somewhere else, that's totally fine. But for organize and reuse, we recommend that you define each -form in its own PHP class: - -.. code-block:: php +form in its own PHP class:: namespace AppBundle\Form; @@ -51,9 +49,7 @@ form in its own PHP class: } } -To use the class, use ``createForm`` and instantiate the new class: - -.. code-block:: php +To use the class, use ``createForm`` and instantiate the new class:: use AppBundle\Form\PostType; // ... @@ -108,9 +104,7 @@ directly in your form class, this would effectively limit the scope of that form This form *may* have been designed for creating posts, but if you wanted to reuse it for editing posts, the button label would be wrong. Instead, -some developers configure form buttons in the controller: - -.. code-block:: php +some developers configure form buttons in the controller:: namespace AppBundle\Controller\Admin; From 5ee97915cf8b0a92545f005d7a47a13186275c94 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Tue, 25 Nov 2014 08:42:42 +0100 Subject: [PATCH 8/8] Some fixes --- book/forms.rst | 12 ++++++------ book/http_fundamentals.rst | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/book/forms.rst b/book/forms.rst index 91d901f7647..dab0a3137f3 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -168,10 +168,10 @@ helper functions: That's it! Just three lines are needed to render the complete form: * ``form_start(form)`` - Renders the start tag of the form, including the - correct enctype attributes when using file uploads; + correct enctype attribute when using file uploads; -* ``form_widget(form)`` - Renders all fields, along with a label and error - message (if there is one) input element; +* ``form_widget(form)`` - Renders all of the fields, which includes the field + element itself, a label and any validation error messages for the field; * ``form_end()`` - Renders the end tag of the form and any fields that have not yet been rendered, in case you rendered each field yourself. This is useful @@ -779,9 +779,9 @@ the other functions do? ``div`` element. The majority of the work is done by the ``form_row`` helper, which renders -the label and HTML form widget of each field inside a ``div`` tag by default. -In the :ref:`form-theming` section, you'll learn how the ``form_row`` output -can be customized on many different levels. +the label, errors and HTML form widget of each field inside a ``div`` tag by +default. In the :ref:`form-theming` section, you'll learn how the ``form_row`` +output can be customized on many different levels. .. tip:: diff --git a/book/http_fundamentals.rst b/book/http_fundamentals.rst index db2f9d86907..eaadacff92f 100644 --- a/book/http_fundamentals.rst +++ b/book/http_fundamentals.rst @@ -524,7 +524,7 @@ regardless of how your project is developed. To name a few: about how that request should be handled (e.g. execute the ``contactAction()`` method); -* `Form ` - A full-featured and flexible +* :doc:`Form ` - A full-featured and flexible framework for creating forms and handling form submissions; * `Validator`_ - A system for creating rules about data and then validating @@ -534,10 +534,10 @@ regardless of how your project is developed. To name a few: templates, handling template inheritance (i.e. a template is decorated with a layout) and performing other common template tasks; -* `Security ` - A powerful library for +* :doc:`Security ` - A powerful library for handling all types of security inside an application; -* `Translation ` - A framework for +* :doc:`Translation ` - A framework for translating strings in your application. Each and every one of these components is decoupled and can be used in *any*